├── .env.example
├── .eslintignore
├── .eslintrc.cjs
├── .github
└── dependabot.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── drizzle.config.ts
├── inlang.config.js
├── languages
├── de.json
├── en.json
└── es.json
├── migrations
├── 0000_closed_golden_guardian.sql
└── meta
│ ├── 0000_snapshot.json
│ └── _journal.json
├── package.json
├── pnpm-lock.yaml
├── postcss.config.cjs
├── src
├── app.d.ts
├── app.html
├── app.postcss
├── hooks.server.ts
├── lib
│ ├── _helpers
│ │ ├── convertNameToInitials.ts
│ │ ├── getAllUrlParams.ts
│ │ ├── parseMessage.ts
│ │ └── parseTrack.ts
│ ├── components
│ │ ├── BottomBar.svelte
│ │ ├── SignoutForm.svelte
│ │ ├── footer.svelte
│ │ ├── logo.svelte
│ │ ├── navigation.svelte
│ │ ├── sign-in.svelte
│ │ └── sign-up.svelte
│ ├── config
│ │ ├── constants.ts
│ │ ├── email-messages.ts
│ │ └── zod-schemas.ts
│ ├── server
│ │ ├── db
│ │ │ ├── client.ts
│ │ │ └── schema.ts
│ │ ├── email-send.ts
│ │ ├── log.ts
│ │ ├── lucia.ts
│ │ └── tokens.ts
│ ├── stores.ts
│ └── utils
│ │ └── string.ts
├── routes
│ ├── (legal)
│ │ ├── +layout@.svelte
│ │ ├── privacy
│ │ │ └── +page.svelte
│ │ └── terms
│ │ │ └── +page.svelte
│ ├── (protected)
│ │ ├── +layout.server.ts
│ │ ├── +layout.svelte
│ │ ├── dashboard
│ │ │ └── +page.svelte
│ │ └── profile
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ ├── +error.svelte
│ ├── +layout.server.ts
│ ├── +layout.svelte
│ ├── +layout.ts
│ ├── +page.svelte
│ ├── auth
│ │ ├── +layout.svelte
│ │ ├── email-verification
│ │ │ ├── +page.server.ts
│ │ │ ├── +page.svelte
│ │ │ └── [token]
│ │ │ │ └── +server.ts
│ │ ├── password
│ │ │ ├── reset
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ └── success
│ │ │ │ │ └── +page.svelte
│ │ │ └── update-[token]
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ └── success
│ │ │ │ └── +page.svelte
│ │ ├── sign-in
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ │ ├── sign-out
│ │ │ └── +page.server.ts
│ │ └── sign-up
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ └── inlang
│ │ └── [language].json
│ │ └── +server.ts
└── theme.postcss
├── static
└── favicon.png
├── svelte.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
/.env.example:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
2 |
3 | # SMTP config
4 | FROM_EMAIL=
5 | SMTP_HOST=
6 | SMTP_PORT=
7 | SMTP_SECURE=
8 | SMTP_USER=
9 | SMTP_PASS=
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:svelte/recommended',
7 | 'prettier'
8 | ],
9 | parser: '@typescript-eslint/parser',
10 | plugins: ['@typescript-eslint'],
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020,
14 | extraFileExtensions: ['.svelte']
15 | },
16 | env: {
17 | browser: true,
18 | es2017: true,
19 | node: true
20 | },
21 | overrides: [
22 | {
23 | files: ['*.svelte'],
24 | parser: 'svelte-eslint-parser',
25 | parserOptions: {
26 | parser: '@typescript-eslint/parser'
27 | }
28 | }
29 | ]
30 | };
31 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 | .vscode*
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | resolution-mode=highest
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "pluginSearchDirs": ["."],
8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Akash Agarwal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Newer Template Available
2 | Created a newer template featuring Svelte 5 and shadcn-svelte, along with all dependencies updated to their latest versions.
3 |
4 | - [Sveltekit Template 2024](https://github.com/ak4zh/sveltekit-template)
5 |
6 | # SLIDE: SvelteKit + Lucia + i18n using inlang + Drizzle + TailwindCSS using Skeleton
7 |
8 | - Multi Tenant configured
9 | - [Lucia](https://lucia-auth.com/) for authentication
10 | - [inlang](https://inlang.com) for language translation
11 | - [Drizzle ORM](https://orm.drizzle.team/) for database connectivity and type safety
12 | - [Skeleton](https://www.skeleton.dev) for ui elements
13 | - [Lucide](https://lucide.dev) for icons
14 | - [Zod](https://zod.dev)
15 | - [Superforms](https://superforms.vercel.app) to handle form validation and management.
16 |
17 | Highly inspired from [Sveltekit Auth Starter](https://github.com/delay/sveltekit-auth-starter) which uses prisma, I wanted one with drizzle ORM and a [BottomBar](https://github.com/delay/sveltekit-auth-starter/pull/10) as I prefer to use bottom bar in mobile views.
18 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "drizzle-kit";
2 | import * as dotenv from "dotenv";
3 | dotenv.config();
4 |
5 | export default {
6 | schema: "./src/lib/server/db/schema.ts",
7 | out: "./migrations",
8 | connectionString: process.env.DATABASE_URL,
9 | } satisfies Config;
--------------------------------------------------------------------------------
/inlang.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type { import("@inlang/core/config").DefineConfig }
3 | */
4 | export async function defineConfig(env) {
5 | const { default: jsonPlugin } = await env.$import(
6 | 'https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@2/dist/index.js'
7 | );
8 | const { default: sdkPlugin } = await env.$import(
9 | 'https://cdn.jsdelivr.net/npm/@inlang/sdk-js-plugin@0.11.8/dist/index.js'
10 | );
11 |
12 | return {
13 | referenceLanguage: 'en',
14 | plugins: [
15 | jsonPlugin({
16 | pathPattern: './languages/{language}.json'
17 | }),
18 | sdkPlugin({
19 | languageNegotiation: {
20 | strategies: [{ type: 'localStorage' }]
21 | }
22 | })
23 | ]
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/languages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "signin": "Anmelden",
3 | "signup": "Registrieren",
4 | "signout": "Abmelden",
5 | "forgotPassword": "Passwort vergessen?",
6 | "contact": "Kontakt",
7 | "privacy": "Datenschutz",
8 | "terms": "Nutzungsbedingungen",
9 | "email": "E-Mail-Adresse",
10 | "password": "Passwort",
11 | "firstName": "Vorname",
12 | "lastName": "Nachname",
13 | "profile": "Profil",
14 | "home": "Startseite",
15 | "dashboard": "Armaturenbrett",
16 | "auth": {
17 | "password": {
18 | "reset": {
19 | "success": {
20 | "emailSent": "Passwort zurücksetzen E-Mail gesendet",
21 | "checkEmail": "Überprüfen Sie Ihr E-Mail-Konto auf einen Link zum Zurücksetzen Ihres Passworts. Wenn er nicht innerhalb weniger Minuten angezeigt wird, überprüfen Sie Ihren Spam-Ordner."
22 | },
23 | "resetProblem": "Problem beim Zurücksetzen des Passworts",
24 | "sendResetEmail": "Passwort-Zurücksetzen-E-Mail senden"
25 | },
26 | "update": {
27 | "success": {
28 | "updated": "Passwort erfolgreich aktualisiert"
29 | },
30 | "changePassword": "Ändern Sie Ihr Passwort",
31 | "passwordProblem": "Problem beim Passwortwechsel",
32 | "updatePassword": "Passwort aktualisieren"
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/languages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "signin": "Sign In",
3 | "signinProblem": "Sign In Problem",
4 | "signup": "Sign Up",
5 | "signout": "Sign Out",
6 | "forgotPassword": "Forgot Password?",
7 | "contact": "Contact",
8 | "privacy": "Privacy",
9 | "terms": "Terms",
10 | "email": "Email Address",
11 | "password": "Password",
12 | "firstName": "First Name",
13 | "lastName": "Last Name",
14 | "profile": "Profile",
15 | "home": "Home",
16 | "dashboard": "Dashboard",
17 | "auth": {
18 | "password": {
19 | "reset": {
20 | "success": {
21 | "emailSent": "Password Reset Email Sent",
22 | "checkEmail": "Check your email account for a link to reset your password. If it doesn’t appear within a few minutes, check your spam folder."
23 | },
24 | "resetProblem": "Reset Password Problem",
25 | "sendResetEmail": "Send Password Reset Email"
26 | },
27 | "update": {
28 | "success": {
29 | "updated": "Password updated successfully"
30 | },
31 | "changePassword": "Change Your Password",
32 | "passwordProblem": "Change Password Problem",
33 | "updatePassword": "Update Password"
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/languages/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "signin": "Iniciar sesión",
3 | "signup": "Registrarse",
4 | "signout": "Cerrar sesión",
5 | "forgotPassword": "¿Olvidaste tu contraseña?",
6 | "contact": "Contacto",
7 | "privacy": "Privacidad",
8 | "terms": "Términos",
9 | "email": "Dirección de correo electrónico",
10 | "password": "Contraseña",
11 | "firstName": "Nombre",
12 | "lastName": "Apellido",
13 | "profile": "Perfil",
14 | "home": "Inicio",
15 | "dashboard": "Panel",
16 | "auth": {
17 | "password": {
18 | "reset": {
19 | "success": {
20 | "emailSent": "Correo electrónico de restablecimiento de contraseña enviado",
21 | "checkEmail": "Verifique su cuenta de correo electrónico para obtener un enlace para restablecer su contraseña. Si no aparece en unos minutos, verifique su carpeta de correo no deseado."
22 | },
23 | "resetProblem": "Problema al restablecer la contraseña",
24 | "sendResetEmail": "Enviar correo electrónico de restablecimiento de contraseña"
25 | },
26 | "update": {
27 | "success": {
28 | "updated": "Contraseña actualizada correctamente"
29 | },
30 | "changePassword": "Cambiar contraseña",
31 | "passwordProblem": "Problema al cambiar la contraseña",
32 | "updatePassword": "Actualizar contraseña"
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/migrations/0000_closed_golden_guardian.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "email_verification_token" (
2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3 | "user_id" uuid NOT NULL,
4 | "expires" bigint
5 | );
6 | --> statement-breakpoint
7 | CREATE TABLE IF NOT EXISTS "auth_key" (
8 | "id" varchar(255) PRIMARY KEY NOT NULL,
9 | "user_id" uuid NOT NULL,
10 | "hashed_password" varchar(255),
11 | "expires" bigint
12 | );
13 | --> statement-breakpoint
14 | CREATE TABLE IF NOT EXISTS "password_reset_token" (
15 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
16 | "user_id" uuid NOT NULL,
17 | "expires" bigint
18 | );
19 | --> statement-breakpoint
20 | CREATE TABLE IF NOT EXISTS "auth_session" (
21 | "id" varchar(128) PRIMARY KEY NOT NULL,
22 | "user_id" uuid NOT NULL,
23 | "active_expires" bigint NOT NULL,
24 | "idle_expires" bigint NOT NULL
25 | );
26 | --> statement-breakpoint
27 | CREATE TABLE IF NOT EXISTS "auth_user" (
28 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
29 | "role" text DEFAULT 'user' NOT NULL,
30 | "created_at" timestamp DEFAULT now() NOT NULL,
31 | "first_name" text,
32 | "last_name" text,
33 | "domain" text NOT NULL,
34 | "email" text NOT NULL,
35 | "email_verified" boolean DEFAULT false NOT NULL
36 | );
37 | --> statement-breakpoint
38 | CREATE UNIQUE INDEX IF NOT EXISTS "email_domain_idx" ON "auth_user" ("email","domain");--> statement-breakpoint
39 | DO $$ BEGIN
40 | ALTER TABLE "email_verification_token" ADD CONSTRAINT "email_verification_token_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE no action ON UPDATE no action;
41 | EXCEPTION
42 | WHEN duplicate_object THEN null;
43 | END $$;
44 | --> statement-breakpoint
45 | DO $$ BEGIN
46 | ALTER TABLE "auth_key" ADD CONSTRAINT "auth_key_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE no action ON UPDATE no action;
47 | EXCEPTION
48 | WHEN duplicate_object THEN null;
49 | END $$;
50 | --> statement-breakpoint
51 | DO $$ BEGIN
52 | ALTER TABLE "password_reset_token" ADD CONSTRAINT "password_reset_token_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE no action ON UPDATE no action;
53 | EXCEPTION
54 | WHEN duplicate_object THEN null;
55 | END $$;
56 | --> statement-breakpoint
57 | DO $$ BEGIN
58 | ALTER TABLE "auth_session" ADD CONSTRAINT "auth_session_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE no action ON UPDATE no action;
59 | EXCEPTION
60 | WHEN duplicate_object THEN null;
61 | END $$;
62 |
--------------------------------------------------------------------------------
/migrations/meta/0000_snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "dialect": "pg",
4 | "id": "95f7d7d3-a7f7-43f0-bfa0-c73ad35d2c2c",
5 | "prevId": "00000000-0000-0000-0000-000000000000",
6 | "tables": {
7 | "email_verification_token": {
8 | "name": "email_verification_token",
9 | "schema": "",
10 | "columns": {
11 | "id": {
12 | "name": "id",
13 | "type": "uuid",
14 | "primaryKey": true,
15 | "notNull": true,
16 | "default": "gen_random_uuid()"
17 | },
18 | "user_id": {
19 | "name": "user_id",
20 | "type": "uuid",
21 | "primaryKey": false,
22 | "notNull": true
23 | },
24 | "expires": {
25 | "name": "expires",
26 | "type": "bigint",
27 | "primaryKey": false,
28 | "notNull": false
29 | }
30 | },
31 | "indexes": {},
32 | "foreignKeys": {
33 | "email_verification_token_user_id_auth_user_id_fk": {
34 | "name": "email_verification_token_user_id_auth_user_id_fk",
35 | "tableFrom": "email_verification_token",
36 | "tableTo": "auth_user",
37 | "columnsFrom": [
38 | "user_id"
39 | ],
40 | "columnsTo": [
41 | "id"
42 | ],
43 | "onDelete": "no action",
44 | "onUpdate": "no action"
45 | }
46 | },
47 | "compositePrimaryKeys": {},
48 | "uniqueConstraints": {}
49 | },
50 | "auth_key": {
51 | "name": "auth_key",
52 | "schema": "",
53 | "columns": {
54 | "id": {
55 | "name": "id",
56 | "type": "varchar(255)",
57 | "primaryKey": true,
58 | "notNull": true
59 | },
60 | "user_id": {
61 | "name": "user_id",
62 | "type": "uuid",
63 | "primaryKey": false,
64 | "notNull": true
65 | },
66 | "hashed_password": {
67 | "name": "hashed_password",
68 | "type": "varchar(255)",
69 | "primaryKey": false,
70 | "notNull": false
71 | },
72 | "expires": {
73 | "name": "expires",
74 | "type": "bigint",
75 | "primaryKey": false,
76 | "notNull": false
77 | }
78 | },
79 | "indexes": {},
80 | "foreignKeys": {
81 | "auth_key_user_id_auth_user_id_fk": {
82 | "name": "auth_key_user_id_auth_user_id_fk",
83 | "tableFrom": "auth_key",
84 | "tableTo": "auth_user",
85 | "columnsFrom": [
86 | "user_id"
87 | ],
88 | "columnsTo": [
89 | "id"
90 | ],
91 | "onDelete": "no action",
92 | "onUpdate": "no action"
93 | }
94 | },
95 | "compositePrimaryKeys": {},
96 | "uniqueConstraints": {}
97 | },
98 | "password_reset_token": {
99 | "name": "password_reset_token",
100 | "schema": "",
101 | "columns": {
102 | "id": {
103 | "name": "id",
104 | "type": "uuid",
105 | "primaryKey": true,
106 | "notNull": true,
107 | "default": "gen_random_uuid()"
108 | },
109 | "user_id": {
110 | "name": "user_id",
111 | "type": "uuid",
112 | "primaryKey": false,
113 | "notNull": true
114 | },
115 | "expires": {
116 | "name": "expires",
117 | "type": "bigint",
118 | "primaryKey": false,
119 | "notNull": false
120 | }
121 | },
122 | "indexes": {},
123 | "foreignKeys": {
124 | "password_reset_token_user_id_auth_user_id_fk": {
125 | "name": "password_reset_token_user_id_auth_user_id_fk",
126 | "tableFrom": "password_reset_token",
127 | "tableTo": "auth_user",
128 | "columnsFrom": [
129 | "user_id"
130 | ],
131 | "columnsTo": [
132 | "id"
133 | ],
134 | "onDelete": "no action",
135 | "onUpdate": "no action"
136 | }
137 | },
138 | "compositePrimaryKeys": {},
139 | "uniqueConstraints": {}
140 | },
141 | "auth_session": {
142 | "name": "auth_session",
143 | "schema": "",
144 | "columns": {
145 | "id": {
146 | "name": "id",
147 | "type": "varchar(128)",
148 | "primaryKey": true,
149 | "notNull": true
150 | },
151 | "user_id": {
152 | "name": "user_id",
153 | "type": "uuid",
154 | "primaryKey": false,
155 | "notNull": true
156 | },
157 | "active_expires": {
158 | "name": "active_expires",
159 | "type": "bigint",
160 | "primaryKey": false,
161 | "notNull": true
162 | },
163 | "idle_expires": {
164 | "name": "idle_expires",
165 | "type": "bigint",
166 | "primaryKey": false,
167 | "notNull": true
168 | }
169 | },
170 | "indexes": {},
171 | "foreignKeys": {
172 | "auth_session_user_id_auth_user_id_fk": {
173 | "name": "auth_session_user_id_auth_user_id_fk",
174 | "tableFrom": "auth_session",
175 | "tableTo": "auth_user",
176 | "columnsFrom": [
177 | "user_id"
178 | ],
179 | "columnsTo": [
180 | "id"
181 | ],
182 | "onDelete": "no action",
183 | "onUpdate": "no action"
184 | }
185 | },
186 | "compositePrimaryKeys": {},
187 | "uniqueConstraints": {}
188 | },
189 | "auth_user": {
190 | "name": "auth_user",
191 | "schema": "",
192 | "columns": {
193 | "id": {
194 | "name": "id",
195 | "type": "uuid",
196 | "primaryKey": true,
197 | "notNull": true,
198 | "default": "gen_random_uuid()"
199 | },
200 | "role": {
201 | "name": "role",
202 | "type": "text",
203 | "primaryKey": false,
204 | "notNull": true,
205 | "default": "'user'"
206 | },
207 | "created_at": {
208 | "name": "created_at",
209 | "type": "timestamp",
210 | "primaryKey": false,
211 | "notNull": true,
212 | "default": "now()"
213 | },
214 | "first_name": {
215 | "name": "first_name",
216 | "type": "text",
217 | "primaryKey": false,
218 | "notNull": false
219 | },
220 | "last_name": {
221 | "name": "last_name",
222 | "type": "text",
223 | "primaryKey": false,
224 | "notNull": false
225 | },
226 | "domain": {
227 | "name": "domain",
228 | "type": "text",
229 | "primaryKey": false,
230 | "notNull": true
231 | },
232 | "email": {
233 | "name": "email",
234 | "type": "text",
235 | "primaryKey": false,
236 | "notNull": true
237 | },
238 | "email_verified": {
239 | "name": "email_verified",
240 | "type": "boolean",
241 | "primaryKey": false,
242 | "notNull": true,
243 | "default": false
244 | }
245 | },
246 | "indexes": {
247 | "email_domain_idx": {
248 | "name": "email_domain_idx",
249 | "columns": [
250 | "email",
251 | "domain"
252 | ],
253 | "isUnique": true
254 | }
255 | },
256 | "foreignKeys": {},
257 | "compositePrimaryKeys": {},
258 | "uniqueConstraints": {}
259 | }
260 | },
261 | "enums": {},
262 | "schemas": {},
263 | "_meta": {
264 | "schemas": {},
265 | "tables": {},
266 | "columns": {}
267 | }
268 | }
--------------------------------------------------------------------------------
/migrations/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "dialect": "pg",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "5",
8 | "when": 1692811865662,
9 | "tag": "0000_closed_golden_guardian",
10 | "breakpoints": true
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slide",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
12 | "format": "prettier --plugin-search-dir . --write .",
13 | "generate": "drizzle-kit generate:pg --out migrations --schema src/lib/server/db/schema.ts"
14 | },
15 | "devDependencies": {
16 | "@inlang/core": "^0.9.2",
17 | "@inlang/sdk-js": "^0.11.8",
18 | "@skeletonlabs/skeleton": "2.6.0",
19 | "@skeletonlabs/tw-plugin": "0.3.0",
20 | "@sveltejs/adapter-auto": "^3.0.0",
21 | "@sveltejs/kit": "^2.0.0",
22 | "@sveltejs/vite-plugin-svelte": "^3.0.1",
23 | "@tailwindcss/forms": "^0.5.7",
24 | "@tailwindcss/typography": "^0.5.10",
25 | "@types/node": "^20.10.4",
26 | "@types/nodemailer": "^6.4.14",
27 | "@types/pg": "^8.10.9",
28 | "@typescript-eslint/eslint-plugin": "^6.14.0",
29 | "@typescript-eslint/parser": "^6.14.0",
30 | "autoprefixer": "^10.4.16",
31 | "dotenv": "^16.3.1",
32 | "drizzle-kit": "^0.20.6",
33 | "eslint": "^8.56.0",
34 | "eslint-config-prettier": "^9.1.0",
35 | "eslint-plugin-svelte3": "^4.0.0",
36 | "postcss": "^8.4.32",
37 | "prettier": "^3.1.1",
38 | "prettier-plugin-svelte": "^3.1.2",
39 | "svelte": "^4.2.8",
40 | "svelte-check": "^3.6.2",
41 | "sveltekit-superforms": "1.12.0",
42 | "tailwindcss": "^3.3.6",
43 | "tslib": "^2.6.2",
44 | "typescript": "^5.3.3",
45 | "vite": "^5.0.10",
46 | "vite-plugin-tailwind-purgecss": "^0.2.0",
47 | "zod": "^3.22.4"
48 | },
49 | "type": "module",
50 | "dependencies": {
51 | "@lucia-auth/adapter-postgresql": "^2.0.2",
52 | "drizzle-orm": "^0.29.1",
53 | "drizzle-zod": "^0.5.1",
54 | "lucia": "^2.7.5",
55 | "lucide-svelte": "^0.298.0",
56 | "nodemailer": "^6.9.7",
57 | "pg": "^8.11.3"
58 | }
59 | }
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // src/app.d.ts
2 | declare global {
3 | namespace App {
4 | interface Locals {
5 | auth: import('lucia-auth').AuthRequest;
6 | user: Lucia.UserAttributes;
7 | startTimer: number;
8 | error: string;
9 | errorId: string;
10 | errorStackTrace: string;
11 | message: unknown;
12 | track: unknown;
13 | }
14 | interface Error {
15 | code?: string;
16 | errorId?: string;
17 | }
18 | }
19 | }
20 |
21 | ///
22 | declare global {
23 | namespace Lucia {
24 | type Auth = import('$lib/lucia').Auth;
25 | type DatabaseSessionAttributes = {};
26 | type DatabaseUserAttributes = {
27 | role: string;
28 | first_name: string;
29 | last_name: string;
30 | domain: string;
31 | email: string;
32 | email_verified: boolean;
33 | };
34 | }
35 | }
36 |
37 | // THIS IS IMPORTANT!!!
38 | export {};
39 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app.postcss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | @tailwind variants;
5 |
6 | /*place global styles here */
7 | html,
8 | body {
9 | @apply h-full overflow-hidden;
10 | }
11 |
--------------------------------------------------------------------------------
/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { auth } from '$lib/server/lucia';
2 | import { redirect, type Handle } from '@sveltejs/kit';
3 | import type { HandleServerError } from '@sveltejs/kit';
4 | import log from '$lib/server/log';
5 | import { db } from "$lib/server/db/client";
6 | import { migrate } from "drizzle-orm/node-postgres/migrator";
7 |
8 | await migrate(db, { migrationsFolder: "./migrations" });
9 |
10 | export const handleError: HandleServerError = async ({ error, event }) => {
11 | const errorId = crypto.randomUUID();
12 |
13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14 | //@ts-ignore
15 | event.locals.error = error?.toString() || undefined;
16 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17 | //@ts-ignore
18 | event.locals.errorStackTrace = error?.stack || undefined;
19 | event.locals.errorId = errorId;
20 | log(500, event);
21 |
22 | return {
23 | message: 'An unexpected error occurred.',
24 | errorId
25 | };
26 | };
27 |
28 | export const handle: Handle = async ({ event, resolve }) => {
29 | const startTimer = Date.now();
30 | event.locals.startTimer = startTimer;
31 |
32 | event.locals.auth = auth.handleRequest(event);
33 | if (event.locals?.auth) {
34 | const session = await event.locals.auth.validate();
35 | const user = session?.user;
36 | event.locals.user = user;
37 | if (event.route.id?.startsWith('/(protected)')) {
38 | if (!user) redirect(302, '/auth/sign-in');
39 | if (!user.emailVerified) redirect(302, '/auth/email-verification');
40 | }
41 | }
42 |
43 | const response = await resolve(event);
44 | log(response.status, event);
45 | return response;
46 | };
47 |
--------------------------------------------------------------------------------
/src/lib/_helpers/convertNameToInitials.ts:
--------------------------------------------------------------------------------
1 | export default function convertNameToInitials(firstName: string, lastName: string): string {
2 | const firstInitial = Array.from(firstName)[0];
3 | const lastInitial = Array.from(lastName)[0];
4 | return `${firstInitial}${lastInitial}`;
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/_helpers/getAllUrlParams.ts:
--------------------------------------------------------------------------------
1 | export default async function getAllUrlParams(url: string): Promise