diff --git a/ctw-kit/package.json b/ctw-kit/package.json index a6f3967..da9080f 100644 --- a/ctw-kit/package.json +++ b/ctw-kit/package.json @@ -1,6 +1,6 @@ { "name": "ctw-kit", - "version": "1.0.33", + "version": "1.0.34", "description": "Custom components and utilities for Svelte and TailwindCSS (DaisyUI)", "type": "module", "main": "./dist/index.js", diff --git a/ctw-kit/src/lib/components/Toggle/Toggle.svelte b/ctw-kit/src/lib/components/Toggle/Toggle.svelte new file mode 100644 index 0000000..7c219c5 --- /dev/null +++ b/ctw-kit/src/lib/components/Toggle/Toggle.svelte @@ -0,0 +1,96 @@ + + + + + diff --git a/ctw-kit/src/lib/index.ts b/ctw-kit/src/lib/index.ts index 8171734..5f1e4ae 100644 --- a/ctw-kit/src/lib/index.ts +++ b/ctw-kit/src/lib/index.ts @@ -6,6 +6,7 @@ export { default as TiltContent } from './components/TiltContent/TiltContent.sve export { default as ThemeChange } from './components/ThemeChange/ThemeChange.svelte'; export { default as SendEmail } from './components/Emails/SendEmail.svelte'; export { default as FeedbackButton } from './components/Feedback/FeedbackButton.svelte'; +export { default as Toggle } from './components/Toggle/Toggle.svelte'; // Types export type { SiteSettings } from './types/types'; diff --git a/portfolio/src/routes/work/+page.svelte b/portfolio/src/routes/work/+page.svelte deleted file mode 100644 index 04ea913..0000000 --- a/portfolio/src/routes/work/+page.svelte +++ /dev/null @@ -1,243 +0,0 @@ - - -
-
-

- {#if $activeCategory === 'Blog'} - Engineering Blog - {:else if $activeCategory === 'Project'} - Work Projects - {:else if $activeCategory === 'Digital Garden'} - 🌱 Digital Garden - {:else if $activeTag} - {$activeTag} - {:else} - All Work - {/if} - ({$filteredPosts.length}) -

- {#if hasFilters} - - {/if} -
- - -
- -
- {#each globalCategories as { name, count }} - - - {/each} -
- -
- - {#if globalTags.length > 0} -
- - -
- {/if} -
-
- - {#each Object.keys($postsByYear).reverse() as year} -

- {year} -

-
- {#each $postsByYear[year] as post} - + {#each availableRoles as role} + + {/each} + +``` + +## Security Considerations + +1. **Role Security**: +- Roles stored in database and JWT +- Server-side validation for all role changes +- Type-safe role handling prevents errors + +2. **JWT Security**: +- Roles included in signed tokens +- Cannot be tampered with +- Validated on server + +## Environment Setup + +Required environment variables: +```env +AUTH_SECRET=your-secret-key +GOOGLE_ID=your-google-client-id +GOOGLE_SECRET=your-google-client-secret +RESEND_API_KEY=your-resend-api-key +DATABASE_URL=your-postgres-connection-string +``` + +## Conclusion + +This authentication system provides a secure and efficient solution with: +- Simplified role management (single role per user) +- Type-safe role handling +- Efficient JWT-based session management +- Clear separation of client and server concerns +- Maintainable and scalable design diff --git a/top-sveltekit/pg-auth-electricsql/README.md b/top-sveltekit/pg-auth-electricsql/README.md new file mode 100644 index 0000000..e30302f --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/README.md @@ -0,0 +1,141 @@ +# Ctwhome top-sveltekit +Unified full-stack with SvelteKit (Svelte 5), TailwindCSS, DaisyUI, AuthJS, ElectricSQL Postgres, and more.\ +by [ctwhome](https://ctwhome.com) + +## Goal of the template +1. **Local-first architecture**: Data lives locally and syncs with the database seamlessly. + +2. **Simplicity in code**: Update Svelte stores locally and let syncing happen in the background. + +3. **Future scalability**: Add real-time sync, offline-first capabilities, and avoid duplicating effort with local storage or API requests. + +4. **Role-Based Access Control**: Server-side authorization with built-in role management. + + +## Installation and running locally +```bash +bunx degit ctwhome/top-sveltekit +``` + +```bash +bunx degit ctwhome/top-sveltekit newName --mode=git # --mode-git if cloning a private repo +``` + +Libraries to install for auth: +```bash +bun add pg node-pg-migrate dotenv @auth/sveltekit @auth/pg-adapter +``` + +## Database Migrations +The project uses node-pg-migrate with a simplified three-digit numbering format (001, 002, etc.). + +Creating a new migration: +```bash +bun migrate create my_migration_name +``` +This will create a new migration file like `migrations/002_my_migration_name.sql` + +Running migrations: +```bash +# Development +bun migrate up + +# Production +bun migrate:production +``` + +## Setup Steps: +1. Install the dependencies with `bun install` +2. Generate the google OAUTH credentials for auth.js +3. ![alt text](./static/image.png) +4. Copy the .env.local.example file to .env.local +5. Fill in the .env.local file with your own values +6. Copy the .env.example file to .env +7. Run `npx auth secret` to generate a secret key for the auth.js adapter +8. Run migrations with `bun migrate up`. This will: + - Create necessary tables including users, roles, and user_roles + - Set up authentication tables for auth.js + - Create default roles (admin and user) + - Create two example users: alice (admin) and bob (user) + +## Authentication & Authorization + +The template uses Auth.js with PostgreSQL adapter for session management and a server-side gatekeeper for Role-Based Access Control (RBAC). Here's how it works: + +### Roles +- By default, any authenticated user is treated as having the 'user' role +- Additional roles (like 'admin') can be explicitly assigned through the `roles` and `user_roles` tables +- Users can have multiple explicit roles +- Users without any explicit roles are automatically treated as regular users + +### Protecting Routes +You can protect routes based on roles in two ways: + +1. Using the protectRoute middleware directly: +```typescript +// In your +page.server.ts or +server.ts +import { protectRoute } from '$lib/server/gatekeeper'; + +// For admin-only routes +export const handle = sequence(handleAuth, protectRoute('admin')); + +// For authenticated users (no specific role required) +export const handle = sequence(handleAuth, protectRoute('user')); +``` + +2. Using the provided example for route-based protection: +```typescript +// In hooks.server.ts +export const protectAdminRoutes: Handle = async ({ event, resolve }) => { + if (event.url.pathname.startsWith('/admin')) { + return protectRoute('admin')({ event, resolve }); + } + return resolve(event); +}; + +export const handle = sequence(handleAuth, protectRoute(), protectAdminRoutes); +``` + +### Accessing User Roles +User roles are automatically added to the session and can be accessed in your routes and components: + +```typescript +// In your +page.svelte +let { session } = await parent(); +$: userRoles = session?.user?.roles || ['user']; // Defaults to ['user'] if no explicit roles + +// Check if user has admin role +$: isAdmin = userRoles.includes('admin'); +``` + +## Development + +Run locally: +```bash +bun dev +``` + +## Updating fork +1. Add remote from the original repository in your forked repository: +```bash +git remote add upstream git://github.com/ctwhome/top-sveltekit.git +git fetch upstream +``` + +2. Updating your fork from the original repo to keep up with their changes: +```bash +git pull upstream main +``` + +Start the development server on [http://localhost:5173](http://localhost:5173) + +```bash +bun dev +``` + +## Production + +Build the application for production: + +```bash +bun build diff --git a/top-sveltekit/pg-auth-electricsql/bun.lockb b/top-sveltekit/pg-auth-electricsql/bun.lockb new file mode 100755 index 0000000..6ed1aa6 Binary files /dev/null and b/top-sveltekit/pg-auth-electricsql/bun.lockb differ diff --git a/top-sveltekit/pg-auth-electricsql/example .env b/top-sveltekit/pg-auth-electricsql/example .env new file mode 100644 index 0000000..daec1f4 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/example .env @@ -0,0 +1,38 @@ +# TO ENABLE ROW LEVEL SECURITY IT IS IMPORTANT TO CREATE THE USER ROLE THAT WILL BE ACCESSED LATER IN THE CONNECTION + +# ``` +# CREATE USER top-sveltekit WITH PASSWORD 'top-sveltekit'; +# GRANT SELECT, INSERT, UPDATE, DELETE ON chats TO top-sveltekit; +# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO top-sveltekit; +# ``` + + + +AUTH_GOOGLE_ID=10... +AUTH_GOOGLE_SECRET=GO... + +# Production database +# DATABASE_URL_PROD=postgres:// + +# Dev database pg-node +DATABASE_URL=postgres:// + + +# DATABASE CONNECTIONS FOR AUTH.JS ADAPTER run with `npx auth secret` +AUTH_SECRET= # Added by `npx auth`. Read more: https://cli.authjs.dev + +DB_HOST= +DB_PORT= +DB_USER= +DB_NAME= +DB_PASSWORD= +DB_SSL=false +MAX_CLIENTS=20 +IDLE_TIMEOUT_MILLIS=30000 +CONNECTION_TIMEOUT_MILLIS=2000 + + + +# Resend API key +AUTH_RESEND_KEY = re_... + diff --git a/top-sveltekit/pg-auth-electricsql/example .env.local b/top-sveltekit/pg-auth-electricsql/example .env.local new file mode 100644 index 0000000..504a654 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/example .env.local @@ -0,0 +1,23 @@ +# TO ENABLE ROW LEVEL SECURITY IT IS IMPORTANT TO CREATE THE USER ROLE THAT WILL BE ACCESSED LATER IN THE CONNECTION + +# ``` +# CREATE USER top-sveltekit WITH PASSWORD 'top-sveltekit'; +# GRANT SELECT, INSERT, UPDATE, DELETE ON chats TO top-sveltekit; +# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO top-sveltekit; +# ``` + + + + +# Connection with the local database running on the local machine +# Dev database pg-node + +PUBLIC_LOCALHOST=true + +DATABASE_URL=postgres://postgres:postgres@localhost:5432/topsveltekit + +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_NAME=topsveltekit diff --git a/top-sveltekit/pg-auth-electricsql/mdsvex.config.js b/top-sveltekit/pg-auth-electricsql/mdsvex.config.js new file mode 100644 index 0000000..c328681 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/mdsvex.config.js @@ -0,0 +1,85 @@ +import { visit } from 'unist-util-visit' + +import autolinkHeadings from 'rehype-autolink-headings' +import slugPlugin from 'rehype-slug' + +import relativeImages from 'mdsvex-relative-images' +// import remarkHeadings from '@vcarl/remark-headings' +import remarkExternalLinks from 'remark-external-links'; +import readingTime from 'remark-reading-time'; + +// import remarkToc from 'remark-toc' + + +export default { + extensions: ['.svx', '.md'], + smartypants: { + dashes: 'oldschool' + }, + layout: { + _: "/src/lib/markdown-layouts/default.svelte", // Default layout for markdown files + blog: "/src/lib/markdown-layouts/blog.svelte", + project: "/src/lib/markdown-layouts/project.svelte", + }, + remarkPlugins: [ + videos, + relativeImages, + // remarkToc, + // headings, + // adds a `readingTime` frontmatter attribute + readingTime, + // external links open in a new tab + [remarkExternalLinks, { target: '_blank', rel: 'noopener' }], + ], + rehypePlugins: [ + slugPlugin, + [ + autolinkHeadings, { behavior: 'wrap' } + ] + ] +} + +/** + * Adds support to video files in markdown image links + */ +function videos() { + const extensions = ['mp4', 'webm'] + return function transformer(tree) { + visit(tree, 'image', (node) => { + if (extensions.some((ext) => node.url.endsWith(ext))) { + node.type = 'html' + node.value = ` + + + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/EmailLoginForm.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/EmailLoginForm.svelte new file mode 100644 index 0000000..2b206ba --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/EmailLoginForm.svelte @@ -0,0 +1,96 @@ + + +
+
+
+ + +
+ +
+ + +
+ + {#if error} + + {/if} + + +
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/GoogleLoginButton.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/GoogleLoginButton.svelte new file mode 100644 index 0000000..8ef6452 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/GoogleLoginButton.svelte @@ -0,0 +1,45 @@ + + +
+ + + {#if error} + + {/if} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LogOutButton.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LogOutButton.svelte new file mode 100644 index 0000000..05a21d5 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LogOutButton.svelte @@ -0,0 +1,10 @@ + + + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginButton.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginButton.svelte new file mode 100644 index 0000000..1f62921 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginButton.svelte @@ -0,0 +1,70 @@ + + +
+ + {#if $page.data.session} + + + {:else} +
+
+ + + +
+
+ {/if} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginForm.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginForm.svelte new file mode 100644 index 0000000..c7ba27d --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/LoginForm.svelte @@ -0,0 +1,51 @@ + + +
+

+ {isRegistering ? 'Create Account' : 'Login Access'} +

+ +
+ {#if isRegistering} + + {:else} +
+ +
OR
+ +
+ {/if} + +
+ {#if isRegistering} + Already have an account? + + {:else} + Don't have an account? + + {/if} +
+
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/MagicLinkForm.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/MagicLinkForm.svelte new file mode 100644 index 0000000..fd7ea9e --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/MagicLinkForm.svelte @@ -0,0 +1,64 @@ + + +
+ {#if magicLinkSent} +
Magic link sent! Please check your email.
+ {:else} +
+ +
+ + {#if error} +
{error}
+ {/if} + + + {/if} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/RegisterForm.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/RegisterForm.svelte new file mode 100644 index 0000000..3c892b0 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/RegisterForm.svelte @@ -0,0 +1,170 @@ + + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {#if error} + + {/if} + + {#if success} + + {/if} + + +
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/types.ts b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/types.ts new file mode 100644 index 0000000..8a220d6 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/types.ts @@ -0,0 +1,22 @@ +export interface AuthResult { + ok?: boolean; + error?: string; + url?: string; + status?: number; +} + +export interface RegisterResponse { + error?: string; + user?: { + id: string; + email: string; + name?: string; + }; +} + +export interface AuthError { + message: string; + code?: string; +} + +export type AuthProvider = 'credentials' | 'google' | 'magic-link'; diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/utils.ts b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/utils.ts new file mode 100644 index 0000000..c79a726 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/Login/utils.ts @@ -0,0 +1,32 @@ +import type { AuthError } from './types'; + +export function closeLoginModal() { + const modalCheckbox = document.getElementById('login-modal') as HTMLInputElement; + if (modalCheckbox) { + modalCheckbox.checked = false; + } +} + +export function getAuthErrorMessage(error: unknown): string { + if (typeof error === 'string') return error; + if (error && typeof error === 'object' && 'message' in error) { + return (error as AuthError).message; + } + return 'An unexpected error occurred'; +} + +export function validatePassword(password: string): string | null { + if (password.length < 8) { + return 'Password must be at least 8 characters long'; + } + // Add more password validation rules as needed + return null; +} + +export function validateEmail(email: string): string | null { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return 'Please enter a valid email address'; + } + return null; +} diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/NativeFileApi.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/NativeFileApi.svelte new file mode 100644 index 0000000..551a96a --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/NativeFileApi.svelte @@ -0,0 +1,201 @@ + + + +
+

Access to the native file system API

+ + + + + + + + + + + + +
Open directory
+
+
{JSON.stringify(numberFiles, null, 2)}
+ {#each Object.values(tree) as { name, handle }} +
fileClicked(handle)}> + {name} +
+ + + + + + + + + + + + + + + + {/each} +
{JSON.stringify(tree, null, 2)}
+
+ +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/ProfilePicture.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/ProfilePicture.svelte new file mode 100644 index 0000000..35f6868 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/ProfilePicture.svelte @@ -0,0 +1,27 @@ + + + + + J. Gonzalez Ctwhome profile picture +
+
J. Gonzalez - Ctw
+
{subtitle}
+
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SEO.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SEO.svelte new file mode 100644 index 0000000..703f708 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SEO.svelte @@ -0,0 +1,21 @@ + + + + {title} + + + + + + + + + + + + + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SideMenu.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SideMenu.svelte new file mode 100644 index 0000000..4980586 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SideMenu.svelte @@ -0,0 +1,128 @@ + + +
+ +
+ + + + + {#if $isMenuOpen} +
+ {/if} +
+ + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SocialIcons.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SocialIcons.svelte new file mode 100644 index 0000000..71083c7 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/SocialIcons.svelte @@ -0,0 +1,67 @@ + + + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/TiltContent.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/TiltContent.svelte new file mode 100644 index 0000000..d46db95 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/TiltContent.svelte @@ -0,0 +1,38 @@ + + +
+ + {@render children?.()} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/feedback/FeedbackButton.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/feedback/FeedbackButton.svelte new file mode 100644 index 0000000..ee7dcd5 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/feedback/FeedbackButton.svelte @@ -0,0 +1,133 @@ + + +{#if showButton} + +{/if} + + + + + diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/footerMain.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/footerMain.svelte new file mode 100644 index 0000000..d568f69 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/footerMain.svelte @@ -0,0 +1,73 @@ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + +
+ Design + should have a meaning. +
+ Engineering + should have a purpose. +
+ And ultimately, + Technology + should empower people. +
+
+ +
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/themeChamge/DaisyUIThemeSwitcher.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/themeChamge/DaisyUIThemeSwitcher.svelte new file mode 100644 index 0000000..8bc5783 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/themeChamge/DaisyUIThemeSwitcher.svelte @@ -0,0 +1,74 @@ + + +
+
+ + + + + +
+ +
diff --git a/portfolio/src/lib/components/themeChamge/themes.json b/top-sveltekit/pg-auth-electricsql/src/lib/components/ui/themeChamge/themes.json similarity index 100% rename from portfolio/src/lib/components/themeChamge/themes.json rename to top-sveltekit/pg-auth-electricsql/src/lib/components/ui/themeChamge/themes.json diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/content/README.md b/top-sveltekit/pg-auth-electricsql/src/lib/content/README.md new file mode 100644 index 0000000..98dff1d --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/content/README.md @@ -0,0 +1,22 @@ +# Instructions for the posts + +frontmatter variables: +```yaml +published: true +title: Blog 1 +description: A description here +date: 2024-01-01 +coverImage: image.avif +displayCover: true # False to only show the cover in the list +tags: + - tag 1 + - tag 2 + +layout: blog +type: blog +``` + +## Very important notes + +> The images NEED to be inside the `./images/` folder to work +> All image name MUST be unique! \ No newline at end of file diff --git a/portfolio/src/content/2024-04-26-the-future-of-image-and-video-format/images/cover.avif b/top-sveltekit/pg-auth-electricsql/src/lib/content/TEMPLATE/images/cover.avif similarity index 100% rename from portfolio/src/content/2024-04-26-the-future-of-image-and-video-format/images/cover.avif rename to top-sveltekit/pg-auth-electricsql/src/lib/content/TEMPLATE/images/cover.avif diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/content/TEMPLATE/index.md b/top-sveltekit/pg-auth-electricsql/src/lib/content/TEMPLATE/index.md new file mode 100644 index 0000000..0281886 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/content/TEMPLATE/index.md @@ -0,0 +1,18 @@ +--- +published: true +title: Title Template +description: Description + +date: 2024-01-01 + +coverImage: images/cover.avif +displayCover: true +tags: + - Tag +categories: + - Blog + - Project +layout: blog | project +--- +### Copy this template + diff --git a/portfolio/src/content/content.ts b/top-sveltekit/pg-auth-electricsql/src/lib/content/content.ts similarity index 100% rename from portfolio/src/content/content.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/content/content.ts diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/db/db.ts b/top-sveltekit/pg-auth-electricsql/src/lib/db/db.ts new file mode 100644 index 0000000..22b7c75 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/db/db.ts @@ -0,0 +1,52 @@ +// src/lib/db.ts +import { env } from "$env/dynamic/private" +import pkg from 'pg'; +const { Pool } = pkg; + +export const pool = new Pool({ + connectionString: env.DATABASE_URL, + ssl: env.DB_SSL === 'true', + max: Number(env.MAX_CLIENTS) || 20, + idleTimeoutMillis: Number(env.IDLE_TIMEOUT_MILLIS) || 30000, + connectionTimeoutMillis: Number(env.CONNECTION_TIMEOUT_MILLIS) || 2000 +}); + +// Utility function to query the database +export const query = (text: string, params?: any[]) => { + return pool.query(text, params); +}; + +// Close the pool gracefully when the application is shutting down +process.on('SIGINT', () => { + pool.end(() => { + console.log('pg pool has ended'); + process.exit(0); + }); +}); + +// Utility function to query the database +// using Parameterized queries, to avoid SQL injection +// read more here: https://node-postgres.com/features/queries +/** + * + * @param text SQL query + * @param params Parameters to be passed to the query + * @param auth Auth object from locals.auth() + * @returns json response + */ +export async function sql(text: string, params?: any[]) { + try { + const result = await query(text, params); + return result.rows; + } + catch (error) { + console.error('SQL function error:', error.message); + console.error('For query:', text); + + if (error.message.includes('not extensible')) { + console.error('Object not extensible error. Params:', params); + console.error('Query text:', text); + } + throw error; // Re-throw the error to be handled by the caller + } +}; diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/blog.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/blog.svelte new file mode 100644 index 0000000..32f4d4e --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/blog.svelte @@ -0,0 +1,13 @@ + + + + +
+ {@render children?.()} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/default.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/default.svelte new file mode 100644 index 0000000..d0dd642 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/default.svelte @@ -0,0 +1,15 @@ + + + + +
+
+ {@render children?.()} +
+
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/project.svelte b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/project.svelte new file mode 100644 index 0000000..6cc41a1 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/markdown-layouts/project.svelte @@ -0,0 +1,13 @@ + + + + +
+ {@render children?.()} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/models/menu-itmes.ts b/top-sveltekit/pg-auth-electricsql/src/lib/models/menu-itmes.ts new file mode 100644 index 0000000..cdb5485 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/models/menu-itmes.ts @@ -0,0 +1,10 @@ +export default [ + + { title: 'About', path: '/about', displayTitle: 'About' }, + + // { + // title: 'Latest Work', + // path: '/work', + // displayTitle: 'Latest Work' + // } +]; diff --git a/portfolio/src/lib/models/status-enum.ts b/top-sveltekit/pg-auth-electricsql/src/lib/models/status-enum.ts similarity index 100% rename from portfolio/src/lib/models/status-enum.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/models/status-enum.ts diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/server/gatekeeper.ts b/top-sveltekit/pg-auth-electricsql/src/lib/server/gatekeeper.ts new file mode 100644 index 0000000..39bda4b --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/server/gatekeeper.ts @@ -0,0 +1,40 @@ +import type { Handle } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; + +import { Role } from '$lib/types'; + +// Function to check if user has required role +function hasRequiredRole(userRole: string, requiredRole: Role): boolean { + // Any authenticated user has USER role + if (requiredRole === Role.USER) { + return true; + } + return userRole === requiredRole; +} + +// Middleware to protect routes based on role +export const protectRoute = (requiredRole?: Role): Handle => { + return async ({ event, resolve }) => { + const session = await event.locals.getSession(); + + // If route requires authentication and user is not logged in + if (requiredRole && !session?.user?.id) { + throw error(401, 'Unauthorized'); + } + + if (session?.user) { + // Get role from session (set in JWT token) + const userRole = session.user.roles?.[0] || Role.USER; + + // Check role if required + if (requiredRole && !hasRequiredRole(userRole, requiredRole)) { + throw error(403, 'Forbidden'); + } + + // Set role in locals for use in routes + event.locals.roles = [userRole]; + } + + return resolve(event); + }; +}; diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/siteSettings.ts b/top-sveltekit/pg-auth-electricsql/src/lib/siteSettings.ts new file mode 100644 index 0000000..06aad95 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/siteSettings.ts @@ -0,0 +1,16 @@ +export const siteSettings = { + title: 'Ctwhome - Top Sveltekit', + description: 'Web Engineering - Projects and Portfolio of J. Gonzalez', + baseUrl: 'https://ctwhome.com', + twitterHandle: '@ctwhome', + facebookAppId: '1234567890', + image: '/images/og-image.jpg', + imageAlt: 'My site image', + siteName: 'My Site', + siteLocale: 'en_US', + siteLang: 'en', + siteDirection: 'ltr', + siteThemeColor: '#000000', + siteBackgroundColor: '#ffffff', + siteKeywords: 'Web engineering, Ctw, Ctw portfolio' +} diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/stores/menu.store.ts b/top-sveltekit/pg-auth-electricsql/src/lib/stores/menu.store.ts new file mode 100644 index 0000000..0797360 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/stores/menu.store.ts @@ -0,0 +1,15 @@ +import { writable } from 'svelte/store'; + +export const isMenuOpen = writable(false); + +export function toggleMenu() { + isMenuOpen.update(state => !state); +} + +export function closeMenu() { + isMenuOpen.set(false); +} + +export function openMenu() { + isMenuOpen.set(true); +} \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/stores/toast.store.ts b/top-sveltekit/pg-auth-electricsql/src/lib/stores/toast.store.ts new file mode 100644 index 0000000..ed188c0 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/stores/toast.store.ts @@ -0,0 +1,30 @@ +import { writable } from 'svelte/store'; +import toast from 'svelte-french-toast'; + +function createToastStore() { + const { subscribe, update } = writable({}); + + return { + subscribe, + success: (message: string) => { + update(() => { + toast.success(message, { + position: 'top-center', + duration: 5000 + }); + return {}; + }); + }, + error: (message: string) => { + update(() => { + toast.error(message, { + position: 'top-center', + duration: 5000 + }); + return {}; + }); + } + }; +} + +export const toastStore = createToastStore(); \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/stores/todoStore.ts b/top-sveltekit/pg-auth-electricsql/src/lib/stores/todoStore.ts new file mode 100644 index 0000000..99819cc --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/stores/todoStore.ts @@ -0,0 +1,128 @@ +import { writable } from 'svelte/store'; + +interface Todo { + id: string; + title: string; + completed: boolean; + user_id: string; + created_at: string; + updated_at: string; +} + +interface TodoState { + todos: Todo[]; + loading: boolean; + error: string | null; +} + +function createTodoStore() { + const { subscribe, set, update } = writable({ + todos: [], + loading: false, + error: null + }); + + return { + subscribe, + fetchTodos: async () => { + update(state => ({ ...state, loading: true })); + try { + const response = await fetch('/api/todos'); + if (!response.ok) throw new Error('Failed to fetch todos'); + const data = await response.json(); + + update(state => ({ + ...state, + todos: data, + loading: false, + error: null + })); + } catch (error) { + update(state => ({ + ...state, + error: error instanceof Error ? error.message : 'An error occurred', + loading: false + })); + } + }, + + addTodo: async (title: string) => { + update(state => ({ ...state, loading: true })); + try { + const response = await fetch('/api/todos', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title }) + }); + if (!response.ok) throw new Error('Failed to add todo'); + const data = await response.json(); + + update(state => ({ + ...state, + todos: [data, ...state.todos], + loading: false, + error: null + })); + } catch (error) { + update(state => ({ + ...state, + error: error instanceof Error ? error.message : 'An error occurred', + loading: false + })); + } + }, + + toggleTodo: async (id: string, completed: boolean) => { + update(state => ({ ...state, loading: true })); + try { + const response = await fetch(`/api/todos/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ completed }) + }); + if (!response.ok) throw new Error('Failed to update todo'); + const data = await response.json(); + + update(state => ({ + ...state, + todos: state.todos.map(todo => + todo.id === id ? data : todo + ), + loading: false, + error: null + })); + } catch (error) { + update(state => ({ + ...state, + error: error instanceof Error ? error.message : 'An error occurred', + loading: false + })); + } + }, + + deleteTodo: async (id: string) => { + update(state => ({ ...state, loading: true })); + try { + const response = await fetch(`/api/todos/${id}`, { + method: 'DELETE' + }); + if (!response.ok) throw new Error('Failed to delete todo'); + + update(state => ({ + ...state, + todos: state.todos.filter(todo => todo.id !== id), + loading: false, + error: null + })); + } catch (error) { + update(state => ({ + ...state, + error: error instanceof Error ? error.message : 'An error occurred', + loading: false + })); + } + } + }; +} + +export const todoStore = createTodoStore(); diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/types.ts b/top-sveltekit/pg-auth-electricsql/src/lib/types.ts new file mode 100644 index 0000000..8a526f5 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/types.ts @@ -0,0 +1,4 @@ +export enum Role { + USER = 'user', + ADMIN = 'admin' +} diff --git a/portfolio/src/lib/utils/content.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/content.ts similarity index 100% rename from portfolio/src/lib/utils/content.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/utils/content.ts diff --git a/top-sveltekit/pg-auth-electricsql/src/lib/utils/fetchData.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/fetchData.ts new file mode 100644 index 0000000..2907f02 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/lib/utils/fetchData.ts @@ -0,0 +1,25 @@ +export const fetchData = async (url: string, method?: 'GET' | 'POST', data?: any) => { + try { + if (method === 'POST') { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + const data = await response.json(); + return data; + } + + else { + const response = await fetch(url); + const data = await response.json(); + return data; + } + + } catch (error) { + console.error('Error fetching data:', error.message); + return []; + } +}; \ No newline at end of file diff --git a/portfolio/src/lib/utils/repace-url-for-seo.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/repace-url-for-seo.ts similarity index 100% rename from portfolio/src/lib/utils/repace-url-for-seo.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/utils/repace-url-for-seo.ts diff --git a/portfolio/src/lib/utils/slugFromPath.test.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/slugFromPath.test.ts similarity index 100% rename from portfolio/src/lib/utils/slugFromPath.test.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/utils/slugFromPath.test.ts diff --git a/portfolio/src/lib/utils/slugFromPath.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/slugFromPath.ts similarity index 100% rename from portfolio/src/lib/utils/slugFromPath.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/utils/slugFromPath.ts diff --git a/portfolio/src/lib/utils/util.ts b/top-sveltekit/pg-auth-electricsql/src/lib/utils/utils.ts similarity index 100% rename from portfolio/src/lib/utils/util.ts rename to top-sveltekit/pg-auth-electricsql/src/lib/utils/utils.ts diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/+layout.server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/+layout.server.ts new file mode 100644 index 0000000..d98c23e --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/+layout.server.ts @@ -0,0 +1,17 @@ +import type { LayoutServerLoad } from "./$types" +import { redirect } from '@sveltejs/kit'; + +export const load: LayoutServerLoad = async (event) => { + const session = await event.locals.auth(); + + // Add redirections if needed + if (!session?.user && event.url.pathname === '/profile') { + throw redirect(302, '/'); + } + + return { + session + } +} +// export const prerender = true +// export const ssr = false; diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/+layout.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/+layout.svelte new file mode 100644 index 0000000..1055be3 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/+layout.svelte @@ -0,0 +1,36 @@ + + + + + +
+
+ + {#if children} + {@render children()} + {:else} + + {/if} + + {#if env.PUBLIC_LOCALHOST} +
+ dev database +
+ {/if} +
diff --git a/portfolio/src/routes/+layout.ts b/top-sveltekit/pg-auth-electricsql/src/routes/+layout.ts similarity index 100% rename from portfolio/src/routes/+layout.ts rename to top-sveltekit/pg-auth-electricsql/src/routes/+layout.ts diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/+page.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/+page.svelte new file mode 100644 index 0000000..5dadc0b --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/+page.svelte @@ -0,0 +1,24 @@ + + +{#if $page.data.session} + +{:else} +
+
+
+
+

Top-sveltekit

+

+ Experience the power of advanced AI assistants in a native-like app interface. +

+ + +
+
+
+
+{/if} diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/about/+page.md b/top-sveltekit/pg-auth-electricsql/src/routes/about/+page.md new file mode 100644 index 0000000..f087354 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/about/+page.md @@ -0,0 +1,30 @@ +--- +# layout: blog +--- + + + + +Hello! I’m J. Gonzalez, a seasoned Digital Product Designer and Web Engineer with over 15 years of experience in the field. Currently, I lead various projects as a Research Software Engineer at The Netherlands eScience Center and create my own digital tools and applications. + + + Ctw Profile + + +🔍 What I Do: +I’m a proponent of cutting-edge technologies, specializing in SvelteKit, Vite, ThreeJS, and BabylonJS, among others. My work focuses on crafting visually compelling and scientifically robust digital platforms. From corporate branding to 3D point cloud navigation systems for museums, I create seamless digital experiences that bring visions to life. + +🎯 My Approach: +As a practitioner of the Design Thinking methodology and Agile Kanban processes, I always place the user at the center of my design and development projects. I believe in the power of prototypes, wireframes, and most importantly, user feedback, to guide my designs from concept to completion. + +👨‍🔬 Research & Interests: +My background includes a Masters in Artificial Intelligence, and my research intersects human-computer interaction and behavioral sciences. I am passionate about breaking down complex problems and democratizing communication between users and organizations. + +🌱 Looking Ahead: +I look at the world with an optimistic lens, always seeking to optimize user experiences, even in everyday activities. If you’re intrigued by what I can offer, I am open to projects and collaborative opportunities that align with my skill set. + +🤝 Let’s Collaborate: +Interested in taking your project to the next level? Get in Touch \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.server.ts new file mode 100644 index 0000000..f5d682c --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.server.ts @@ -0,0 +1,19 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ locals }) => { + const session = await locals.getSession(); + + if (!session?.user) { + throw redirect(302, '/auth/signin'); + } + + const roles = locals.roles || []; + if (!roles.includes('admin')) { + throw redirect(302, '/'); + } + + return { + user: session.user + }; +}; diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.svelte new file mode 100644 index 0000000..a1e6c1b --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/admin/+page.svelte @@ -0,0 +1,145 @@ + + +
+
+

User Management

+
{users.length} Users
+
+ + {#if loading} +
+ +
+ {:else if error} +
+ {error} +
+ {:else} +
+ + + + + + + + + + + {#each users as user (user.id)} + + + + + + + {/each} + +
IDUserEmailRoles
{user.id} + + + {user.name || 'No name'} + + {#if user.roles.includes(Role.ADMIN)} + Admin + {/if} + {user.email} +
+ +
+ {#if user.roles.includes(Role.ADMIN)} +
+ +
+ {:else} +
+ +
+ {/if} +
+
+
+
+ {/if} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/+server.ts new file mode 100644 index 0000000..9a7d0c0 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/+server.ts @@ -0,0 +1,32 @@ +import { json, error } from '@sveltejs/kit'; +import { pool } from '$lib/db/db'; +import { Role } from '$lib/types'; +import type { RequestEvent } from './$types'; + +export async function GET({ locals }: RequestEvent) { + const session = await locals.getSession(); + if (!session?.user?.id) { + throw error(401, 'Unauthorized'); + } + + const userRole = session.user.roles?.[0]; + if (userRole !== Role.ADMIN) { + throw error(403, 'Forbidden'); + } + + try { + const result = await pool.query(` + SELECT + id, + email, + name, + ARRAY[role] as roles + FROM users + ORDER BY id + `); + + return json(result.rows); + } catch (err) { + throw error(500, 'Failed to fetch users'); + } +} diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/[userId]/roles/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/[userId]/roles/+server.ts new file mode 100644 index 0000000..2c850a7 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/admin/users/[userId]/roles/+server.ts @@ -0,0 +1,47 @@ +import { json, error } from '@sveltejs/kit'; +import { pool } from '$lib/db/db'; +import { Role } from '$lib/types'; +import type { RequestEvent } from './$types'; + +export async function PUT({ locals, params, request }: RequestEvent) { + const session = await locals.getSession(); + if (!session?.user?.id) { + throw error(401, 'Unauthorized'); + } + + const userRole = session.user.roles?.[0]; + if (userRole !== Role.ADMIN) { + throw error(403, 'Forbidden'); + } + + const userId = params.userId; + const { role: newRole, action } = await request.json(); + console.log('Updating role:', { userId, newRole, action }); + + try { + // Update the user's role + if (action === 'add') { + await pool.query( + 'UPDATE users SET role = $1 WHERE id = $2', + [newRole, userId] + ); + } else if (action === 'remove') { + await pool.query( + 'UPDATE users SET role = $1 WHERE id = $2', + [Role.USER, userId] + ); + } + + // Log the result + const updatedUser = await pool.query( + 'SELECT id, email, role FROM users WHERE id = $1', + [userId] + ); + console.log('Updated user:', updatedUser.rows[0]); + + return json({ success: true }); + } catch (err) { + console.error('Error updating user role:', err); + throw error(500, 'Failed to update user role'); + } +} diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/register/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/register/+server.ts new file mode 100644 index 0000000..0b35173 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/register/+server.ts @@ -0,0 +1,30 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { pool } from '$lib/db/db'; +import bcrypt from 'bcrypt'; + +export const POST: RequestHandler = async ({ request }) => { + const { email, password } = await request.json(); + + try { + // Check if user already exists + const existingUser = await pool.query('SELECT * FROM users WHERE email = $1', [email]); + if (existingUser.rows.length > 0) { + return json({ error: 'User already exists' }, { status: 400 }); + } + + // Hash the password + const hashedPassword = await bcrypt.hash(password, 10); + + // Insert new user + const result = await pool.query( + 'INSERT INTO users (email, password) VALUES ($1, $2) RETURNING id, email', + [email, hashedPassword] + ); + + return json({ user: result.rows[0] }); + } catch (error) { + console.error('Registration error:', error); + return json({ error: 'An error occurred during registration' }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/sendEmail/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/sendEmail/+server.ts new file mode 100644 index 0000000..8c476cb --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/sendEmail/+server.ts @@ -0,0 +1,40 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { json } from '@sveltejs/kit'; + +const AUTH_RESEND_KEY = env.AUTH_RESEND_KEY; + +export const POST: RequestHandler = async ({ request }) => { + const { subject, content } = await request.json(); + const html = ` + ${content} + `; + + try { + const response = await fetch('https://api.resend.com/emails', { + method: 'POST', + headers: { + Authorization: 'Bearer ' + AUTH_RESEND_KEY, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + from: 'feedback-top-sveltekit@ctwhome.com', + to: 'ctw@ctwhome.com', // Replace with your support email + subject: subject, + html: html, + }), + }); + + if (!response.ok) { + const errorDetails = await response.json(); + throw new Error(`Network response was not ok: ${JSON.stringify(errorDetails)}`); + } + + const data = await response.json(); + console.log('Feedback email sent successfully:', data); + return json({ success: true, message: 'Feedback sent successfully' }); + } catch (error) { + console.error('Error sending feedback email:', error); + return json({ success: false, message: 'Failed to send feedback: ' + error.message }, { status: 500 }); + } +} \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/+server.ts new file mode 100644 index 0000000..08fa798 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/+server.ts @@ -0,0 +1,44 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { query } from '$lib/db/db'; + +export const GET: RequestHandler = async ({ locals }) => { + const session = await locals.getSession(); + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + + try { + const todos = await query( + 'SELECT * FROM todos WHERE user_id = $1 ORDER BY created_at DESC', + [session.user.id] + ); + return json(todos.rows); + } catch (error) { + console.error('Error fetching todos:', error); + return new Response('Internal Server Error', { status: 500 }); + } +}; + +export const POST: RequestHandler = async ({ request, locals }) => { + const session = await locals.getSession(); + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + + try { + const { title } = await request.json(); + if (!title) { + return new Response('Title is required', { status: 400 }); + } + + const result = await query( + 'INSERT INTO todos (title, user_id) VALUES ($1, $2) RETURNING *', + [title, session.user.id] + ); + return json(result.rows[0]); + } catch (error) { + console.error('Error creating todo:', error); + return new Response('Internal Server Error', { status: 500 }); + } +}; diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/[id]/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/[id]/+server.ts new file mode 100644 index 0000000..356a993 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/todos/[id]/+server.ts @@ -0,0 +1,54 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { query } from '$lib/db/db'; + +export const PATCH: RequestHandler = async ({ params, request, locals }) => { + const session = await locals.getSession(); + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + + try { + const { completed } = await request.json(); + if (typeof completed !== 'boolean') { + return new Response('Invalid completed value', { status: 400 }); + } + + const result = await query( + 'UPDATE todos SET completed = $1 WHERE id = $2 AND user_id = $3 RETURNING *', + [completed, params.id, session.user.id] + ); + + if (result.rows.length === 0) { + return new Response('Todo not found', { status: 404 }); + } + + return json(result.rows[0]); + } catch (error) { + console.error('Error updating todo:', error); + return new Response('Internal Server Error', { status: 500 }); + } +}; + +export const DELETE: RequestHandler = async ({ params, locals }) => { + const session = await locals.getSession(); + if (!session) { + return new Response('Unauthorized', { status: 401 }); + } + + try { + const result = await query( + 'DELETE FROM todos WHERE id = $1 AND user_id = $2 RETURNING id', + [params.id, session.user.id] + ); + + if (result.rows.length === 0) { + return new Response('Todo not found', { status: 404 }); + } + + return new Response(null, { status: 204 }); + } catch (error) { + console.error('Error deleting todo:', error); + return new Response('Internal Server Error', { status: 500 }); + } +}; diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/api/user/api-key/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/api/user/api-key/+server.ts new file mode 100644 index 0000000..3627b49 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/api/user/api-key/+server.ts @@ -0,0 +1,27 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +// In a real application, you would store this in a database +let userApiKeys: Record = {}; + +export const GET: RequestHandler = async ({ locals }) => { + const session = await locals.getSession(); + if (!session?.user?.email) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + const apiKey = userApiKeys[session.user.email] || ''; + return json({ apiKey }); +}; + +export const POST: RequestHandler = async ({ request, locals }) => { + const session = await locals.getSession(); + if (!session?.user?.email) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { apiKey } = await request.json(); + userApiKeys[session.user.email] = apiKey; + + return json({ success: true }); +}; \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/auth/login/+page.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/auth/login/+page.svelte new file mode 100644 index 0000000..27439d7 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/auth/login/+page.svelte @@ -0,0 +1,22 @@ + + +
+
+
+

Sign in to your account

+

Or start your 14-day free trial

+
+ +
+
+ +
OR
+ +
+
+
+
diff --git a/portfolio/src/routes/contact/+page.server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/contact/+page.server.ts similarity index 100% rename from portfolio/src/routes/contact/+page.server.ts rename to top-sveltekit/pg-auth-electricsql/src/routes/contact/+page.server.ts diff --git a/portfolio/src/routes/contact/+page.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/contact/+page.svelte similarity index 80% rename from portfolio/src/routes/contact/+page.svelte rename to top-sveltekit/pg-auth-electricsql/src/routes/contact/+page.svelte index 4b5011f..47baa87 100644 --- a/portfolio/src/routes/contact/+page.svelte +++ b/top-sveltekit/pg-auth-electricsql/src/routes/contact/+page.svelte @@ -1,5 +1,5 @@ - diff --git a/portfolio/src/routes/privacy/+page.md b/top-sveltekit/pg-auth-electricsql/src/routes/privacy/+page.md similarity index 100% rename from portfolio/src/routes/privacy/+page.md rename to top-sveltekit/pg-auth-electricsql/src/routes/privacy/+page.md diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/profile/+page.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/profile/+page.svelte new file mode 100644 index 0000000..cab0097 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/profile/+page.svelte @@ -0,0 +1,25 @@ + + +
+ {#if $page.data.session} + +
{JSON.stringify($page.data.session, null, 2)}
+ {:else} + Not logged in + {/if} +
diff --git a/top-sveltekit/pg-auth-electricsql/src/routes/profile/UserInfo.svelte b/top-sveltekit/pg-auth-electricsql/src/routes/profile/UserInfo.svelte new file mode 100644 index 0000000..b59216c --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/src/routes/profile/UserInfo.svelte @@ -0,0 +1,43 @@ + + +
+
+
+
+ username { + if (e.target instanceof HTMLImageElement) { + console.error('Image failed to load:', e.target.src); + e.target.onerror = null; // Prevent infinite loop + e.target.src = '/images/profile.avif'; + } + }} + /> +
+
+ +
+
+
+ {$page.data?.session?.user?.name || + $page.data?.session?.user?.email?.split('@')[0] || + 'User'} +
+
+ {$page.data?.session?.user?.email || ''} +
+
+ + + + +
+
+
+
diff --git a/portfolio/src/routes/rss.xml/+server.ts b/top-sveltekit/pg-auth-electricsql/src/routes/rss.xml/+server.ts similarity index 100% rename from portfolio/src/routes/rss.xml/+server.ts rename to top-sveltekit/pg-auth-electricsql/src/routes/rss.xml/+server.ts diff --git a/portfolio/src/routes/sitemap.xml/+server.js b/top-sveltekit/pg-auth-electricsql/src/routes/sitemap.xml/+server.js similarity index 100% rename from portfolio/src/routes/sitemap.xml/+server.js rename to top-sveltekit/pg-auth-electricsql/src/routes/sitemap.xml/+server.js diff --git a/portfolio/src/routes/terms/+page.md b/top-sveltekit/pg-auth-electricsql/src/routes/terms/+page.md similarity index 100% rename from portfolio/src/routes/terms/+page.md rename to top-sveltekit/pg-auth-electricsql/src/routes/terms/+page.md diff --git a/portfolio/static/favicon.png b/top-sveltekit/pg-auth-electricsql/static/favicon.png similarity index 100% rename from portfolio/static/favicon.png rename to top-sveltekit/pg-auth-electricsql/static/favicon.png diff --git a/top-sveltekit/pg-auth-electricsql/static/image.png b/top-sveltekit/pg-auth-electricsql/static/image.png new file mode 100644 index 0000000..0ce351b Binary files /dev/null and b/top-sveltekit/pg-auth-electricsql/static/image.png differ diff --git a/portfolio/static/images/about.webp b/top-sveltekit/pg-auth-electricsql/static/images/about.webp similarity index 100% rename from portfolio/static/images/about.webp rename to top-sveltekit/pg-auth-electricsql/static/images/about.webp diff --git a/portfolio/static/images/profile.avif b/top-sveltekit/pg-auth-electricsql/static/images/profile.avif similarity index 100% rename from portfolio/static/images/profile.avif rename to top-sveltekit/pg-auth-electricsql/static/images/profile.avif diff --git a/top-sveltekit/pg-auth-electricsql/static/manifest.json b/top-sveltekit/pg-auth-electricsql/static/manifest.json new file mode 100644 index 0000000..3154b05 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/static/manifest.json @@ -0,0 +1,16 @@ + +{ + "name": "top-sveltekit", + "short_name": "top-sveltekit", + "start_url": "/", + "display": "standalone", + "background_color": "#000000", + "theme_color": "#000000", + "icons": [ + { + "src": "favicon.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/top-sveltekit/pg-auth-electricsql/svelte.config.js b/top-sveltekit/pg-auth-electricsql/svelte.config.js new file mode 100644 index 0000000..7bc393f --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/svelte.config.js @@ -0,0 +1,40 @@ +import path from 'path'; +// import adapter from '@sveltejs/adapter-node'; +import adapter from '@sveltejs/adapter-vercel'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +import { mdsvex } from "mdsvex"; +import mdsvexConfig from './mdsvex.config.js' + + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: [ + vitePreprocess(), + mdsvex(mdsvexConfig), + ], + // extensions: ['.svelte', '.md', '.svx'], + extensions: [ + '.svelte', + ...mdsvexConfig.extensions + ], + kit: { + // https://kit.svelte.dev/docs/adapter-static + adapter: adapter({ + // runtime: 'edge', + fallback: '200.html' // may differ from host to host + }), + + alias: { + // these are the aliases and paths to them + $api: path.resolve('./src/api'), + $components: path.resolve('./src/lib/components'), + $assets: path.resolve('./src/assets'), + $content: path.resolve('./src/lib/content') + } + } +}; + +export default config; diff --git a/top-sveltekit/pg-auth-electricsql/tailwind.config.js b/top-sveltekit/pg-auth-electricsql/tailwind.config.js new file mode 100644 index 0000000..9990efb --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/tailwind.config.js @@ -0,0 +1,91 @@ +import { de } from 'date-fns/locale' + +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{html,js,svelte,ts,md}'], + theme: { + extend: { + typography: { + DEFAULT: { + css: { + table: { + width: '100%', + overflowX: 'auto', + }, + + a: { + textDecoration: 'none', + '&:hover': { + textDecoration: 'underline', + }, + }, + }, + }, + }, + } + }, + plugins: [ + require('daisyui'), + require('@tailwindcss/typography'), + require('@tailwindcss/forms'), + require('@tailwindcss/container-queries') + ], + daisyui: { + themes: [ + { + + ctw: { + primary: '#ffb83d', + 'primary-focus': '#db8b00', + 'primary-content': 'black', + secondary: '#5e9c91', + 'secondary-focus': '#3e655f', + 'secondary-content': '#FFFFFF', + accent: '#37cdbe', + 'accent-focus': '#2aa79b', + 'accent-content': '#FFFFFF', + neutral: '#3d4451', + 'neutral-focus': '#2a2e37', + 'neutral-content': '#ffffff', + 'base-100': '#171717', + 'base-200': '#333', + 'base-300': '#555', + 'base-content': '#E8E8E8', + info: '#2094f3', + success: '#009485', + warning: '#FF9900', + error: '#ff5724' + } + }, + "dark", + "light", + "night", + "cupcake", + "bumblebee", + "emerald", + "corporate", + "synthwave", + "retro", + "cyberpunk", + "valentine", + "halloween", + "garden", + "forest", + "aqua", + "lofi", + "pastel", + "fantasy", + "wireframe", + "black", + "luxury", + "dracula", + "cmyk", + "autumn", + "business", + "acid", + "lemonade", + "coffee", + "winter", + ] + } +}; diff --git a/portfolio/tsconfig.json b/top-sveltekit/pg-auth-electricsql/tsconfig.json similarity index 100% rename from portfolio/tsconfig.json rename to top-sveltekit/pg-auth-electricsql/tsconfig.json diff --git a/top-sveltekit/pg-auth-electricsql/vercel.json b/top-sveltekit/pg-auth-electricsql/vercel.json new file mode 100644 index 0000000..04f80f7 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/vercel.json @@ -0,0 +1,3 @@ +{ + "regions": ["fra1"] +} \ No newline at end of file diff --git a/top-sveltekit/pg-auth-electricsql/vite.config.ts b/top-sveltekit/pg-auth-electricsql/vite.config.ts new file mode 100644 index 0000000..c6c6a83 --- /dev/null +++ b/top-sveltekit/pg-auth-electricsql/vite.config.ts @@ -0,0 +1,25 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; +import { enhancedImages } from '@sveltejs/enhanced-img'; +import Icons from 'unplugin-icons/vite' + +import VitePluginRestart from 'vite-plugin-restart'; +import { viteStaticCopy } from 'vite-plugin-static-copy' + + +export default defineConfig({ + plugins: [ + enhancedImages(), + + VitePluginRestart({ restart: ['./content/**'] }), + viteStaticCopy({ targets: [{ src: './src/lib/content/*', dest: './content/' }] }), + + sveltekit(), + Icons({ + compiler: 'svelte', + }), + ], + // optimizeDeps: { + // disabled: true, + // }, +}); diff --git a/top-sveltekit/postgres-rls/.eslintignore b/top-sveltekit/postgres-rls/.eslintignore new file mode 100644 index 0000000..e6d91c5 --- /dev/null +++ b/top-sveltekit/postgres-rls/.eslintignore @@ -0,0 +1,14 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock +*.sql \ No newline at end of file diff --git a/top-sveltekit/postgres-rls/.eslintrc.cjs b/top-sveltekit/postgres-rls/.eslintrc.cjs new file mode 100644 index 0000000..ebc1958 --- /dev/null +++ b/top-sveltekit/postgres-rls/.eslintrc.cjs @@ -0,0 +1,30 @@ +module.exports = { + root: true, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:svelte/recommended', + 'prettier' + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020, + extraFileExtensions: ['.svelte'] + }, + env: { + browser: true, + es2017: true, + node: true + }, + overrides: [ + { + files: ['*.svelte'], + parser: 'svelte-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser' + } + } + ] +}; diff --git a/top-sveltekit/postgres-rls/.gitignore b/top-sveltekit/postgres-rls/.gitignore new file mode 100644 index 0000000..0aa14da --- /dev/null +++ b/top-sveltekit/postgres-rls/.gitignore @@ -0,0 +1,19 @@ +.DS_Store +node_modules +/build +/dist +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.history +.vercel +.jampack +.idea +firebase-debug.log +firestore-emulator-data +data +logs \ No newline at end of file diff --git a/top-sveltekit/postgres-rls/.npmrc b/top-sveltekit/postgres-rls/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/top-sveltekit/postgres-rls/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/top-sveltekit/postgres-rls/.pnpm-debug.log b/top-sveltekit/postgres-rls/.pnpm-debug.log new file mode 100644 index 0000000..30f3a4c --- /dev/null +++ b/top-sveltekit/postgres-rls/.pnpm-debug.log @@ -0,0 +1,22 @@ +{ + "0 debug pnpm:scope": { + "selected": 1 + }, + "1 error pnpm": { + "code": "ELIFECYCLE", + "errno": "ENOENT", + "syscall": "spawn", + "file": "sh", + "pkgid": "top-sveltekit@0.0.1", + "stage": "build", + "script": "vite build", + "pkgname": "top-sveltekit", + "err": { + "name": "pnpm", + "message": "top-sveltekit@0.0.1 build: `vite build`\nspawn ENOENT", + "code": "ELIFECYCLE", + "stack": "pnpm: top-sveltekit@0.0.1 build: `vite build`\nspawn ENOENT\n at ChildProcess. (/usr/local/Cellar/pnpm/6.32.4/libexec/lib/node_modules/pnpm/dist/pnpm.cjs:92290:22)\n at ChildProcess.emit (node:events:519:28)\n at maybeClose (node:internal/child_process:1105:16)\n at ChildProcess._handle.onexit (node:internal/child_process:305:5)" + } + }, + "2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?" +} \ No newline at end of file diff --git a/top-sveltekit/postgres-rls/.prettierignore b/top-sveltekit/postgres-rls/.prettierignore new file mode 100644 index 0000000..53062ff --- /dev/null +++ b/top-sveltekit/postgres-rls/.prettierignore @@ -0,0 +1,14 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock +*.sql diff --git a/top-sveltekit/postgres-rls/.prettierrc b/top-sveltekit/postgres-rls/.prettierrc new file mode 100644 index 0000000..f4019a2 --- /dev/null +++ b/top-sveltekit/postgres-rls/.prettierrc @@ -0,0 +1,10 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte","prettier-plugin-tailwindcss"], + "pluginSearchDirs": ["."], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }], + "htmlWhitespaceSensitivity": "ignore" +} diff --git a/top-sveltekit/postgres-rls/.vscode/launch.json b/top-sveltekit/postgres-rls/.vscode/launch.json new file mode 100644 index 0000000..95445ed --- /dev/null +++ b/top-sveltekit/postgres-rls/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/top-sveltekit/postgres-rls/.vscode/settings.json b/top-sveltekit/postgres-rls/.vscode/settings.json new file mode 100644 index 0000000..c26eb74 --- /dev/null +++ b/top-sveltekit/postgres-rls/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "window.title": "📙 Top Sveltekit - ${activeEditorShort}", + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": [ + "javascript" + ], + "[sql]": { + "editor.formatOnSave": false + }, +} diff --git a/top-sveltekit/postgres-rls/README.md b/top-sveltekit/postgres-rls/README.md new file mode 100644 index 0000000..42fd710 --- /dev/null +++ b/top-sveltekit/postgres-rls/README.md @@ -0,0 +1,73 @@ +# Ctwhome top-sveltekit +Unified full-stack with SvelteKit (Svelte 5), TailwindCSS, DaisyUI, AuthJS, ElectricSQL Postgres, and more.\ +by [ctwhome](https://ctwhome.com) + +## Goal of the template +1. **Local-first architecture**: Data lives locally and syncs with the database seamlessly. + +2. **Simplicity in code**: Update Svelte stores locally and let syncing happen in the background. + +3. **Future scalability**: Add real-time sync, offline-first capabilities, and avoid duplicating effort with local storage or API requests. + + +## Installation and running locally +```bash +bunx degit ctwhome/top-sveltekit +``` + +``` +bunx degit ctwhome/top-sveltekit newName --mode=git # --mode-git if cloning a private repo +``` + +Libraries to install for auth: +``` +bun add pg node-pg-migrate dotenv @auth/sveltekit @auth/pg-adapter +``` + +Running Migrations: +```shell +bun migrate up +bun migrate down # with number to run back as many +``` + + + +Steps: +1. Install the dependencies with `bun install` +2. Generate the google OAUTH credentials for the auth.js. +3. ![alt text](./static/image.png) +4. copy the .env.local.example file to .env.local +5. fill in the .env.local file with your own values +6. copy the .env.example file to .env +7. run `npx auth secret` to generate a secret key for the auth.js adapter +8. Run migrations files to setup the database `bun run migrate up`. It will create the neccesary tables and functions, users and two example users alice and bob. + + +Run locally: +```bash +bun dev +``` + +## Updating fork +1. Add remote from the original repository in your forked repository: +```shell +git remote add upstream git://github.com/ctwhome/top-sveltekit.git +git fetch upstream +``` +1. Updating your fork from the original repo to keep up with their changes: + `git pull upstream main` + +Start the development server on [http://localhost:5173](http://localhost:5173) + +```bash +bun dev +``` + +## Production + +Build the application for production: + +```bash +bun build +``` + diff --git a/top-sveltekit/postgres-rls/bun.lockb b/top-sveltekit/postgres-rls/bun.lockb new file mode 100755 index 0000000..737fb83 Binary files /dev/null and b/top-sveltekit/postgres-rls/bun.lockb differ diff --git a/top-sveltekit/postgres-rls/example .env b/top-sveltekit/postgres-rls/example .env new file mode 100644 index 0000000..daec1f4 --- /dev/null +++ b/top-sveltekit/postgres-rls/example .env @@ -0,0 +1,38 @@ +# TO ENABLE ROW LEVEL SECURITY IT IS IMPORTANT TO CREATE THE USER ROLE THAT WILL BE ACCESSED LATER IN THE CONNECTION + +# ``` +# CREATE USER top-sveltekit WITH PASSWORD 'top-sveltekit'; +# GRANT SELECT, INSERT, UPDATE, DELETE ON chats TO top-sveltekit; +# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO top-sveltekit; +# ``` + + + +AUTH_GOOGLE_ID=10... +AUTH_GOOGLE_SECRET=GO... + +# Production database +# DATABASE_URL_PROD=postgres:// + +# Dev database pg-node +DATABASE_URL=postgres:// + + +# DATABASE CONNECTIONS FOR AUTH.JS ADAPTER run with `npx auth secret` +AUTH_SECRET= # Added by `npx auth`. Read more: https://cli.authjs.dev + +DB_HOST= +DB_PORT= +DB_USER= +DB_NAME= +DB_PASSWORD= +DB_SSL=false +MAX_CLIENTS=20 +IDLE_TIMEOUT_MILLIS=30000 +CONNECTION_TIMEOUT_MILLIS=2000 + + + +# Resend API key +AUTH_RESEND_KEY = re_... + diff --git a/top-sveltekit/postgres-rls/example .env.local b/top-sveltekit/postgres-rls/example .env.local new file mode 100644 index 0000000..7eefcd6 --- /dev/null +++ b/top-sveltekit/postgres-rls/example .env.local @@ -0,0 +1,23 @@ +# TO ENABLE ROW LEVEL SECURITY IT IS IMPORTANT TO CREATE THE USER ROLE THAT WILL BE ACCESSED LATER IN THE CONNECTION + +# ``` +# CREATE USER top-sveltekit WITH PASSWORD 'top-sveltekit'; +# GRANT SELECT, INSERT, UPDATE, DELETE ON chats TO top-sveltekit; +# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO top-sveltekit; +# ``` + + + + +# Connection with the local database running on the local machine +# Dev database pg-node + +PUBLIC_LOCALHOST=true + +DATABASE_URL=postgres://postgres:postgres@localhost:5432/top-sveltekit + +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_NAME=top-sveltekit diff --git a/top-sveltekit/postgres-rls/mdsvex.config.js b/top-sveltekit/postgres-rls/mdsvex.config.js new file mode 100644 index 0000000..c328681 --- /dev/null +++ b/top-sveltekit/postgres-rls/mdsvex.config.js @@ -0,0 +1,85 @@ +import { visit } from 'unist-util-visit' + +import autolinkHeadings from 'rehype-autolink-headings' +import slugPlugin from 'rehype-slug' + +import relativeImages from 'mdsvex-relative-images' +// import remarkHeadings from '@vcarl/remark-headings' +import remarkExternalLinks from 'remark-external-links'; +import readingTime from 'remark-reading-time'; + +// import remarkToc from 'remark-toc' + + +export default { + extensions: ['.svx', '.md'], + smartypants: { + dashes: 'oldschool' + }, + layout: { + _: "/src/lib/markdown-layouts/default.svelte", // Default layout for markdown files + blog: "/src/lib/markdown-layouts/blog.svelte", + project: "/src/lib/markdown-layouts/project.svelte", + }, + remarkPlugins: [ + videos, + relativeImages, + // remarkToc, + // headings, + // adds a `readingTime` frontmatter attribute + readingTime, + // external links open in a new tab + [remarkExternalLinks, { target: '_blank', rel: 'noopener' }], + ], + rehypePlugins: [ + slugPlugin, + [ + autolinkHeadings, { behavior: 'wrap' } + ] + ] +} + +/** + * Adds support to video files in markdown image links + */ +function videos() { + const extensions = ['mp4', 'webm'] + return function transformer(tree) { + visit(tree, 'image', (node) => { + if (extensions.some((ext) => node.url.endsWith(ext))) { + node.type = 'html' + node.value = ` +