generated from pantheon/chaos
Initial commit
This commit is contained in:
commit
a9734dc4a5
49 changed files with 2790 additions and 0 deletions
14
.editorconfig
Normal file
14
.editorconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
|
||||
[*.{js,ts}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[{*.{yml,mjs,json}}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
27
.env
Normal file
27
.env
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
NPM_TOKEN=
|
||||
|
||||
##########################
|
||||
# APP VARIABLES #
|
||||
##########################
|
||||
APP_NAME=chaos
|
||||
|
||||
##########################
|
||||
# SECRETS #
|
||||
##########################
|
||||
SECRETS_PASSWORD=secret_do_not_commit_or_change_this_create_.env.local_instead
|
||||
SECRETS_SALT=secret_do_not_commit_or_change_this_create_.env.local_instead
|
||||
SECRETS_IV_POSITION=secret_do_not_commit_or_change_this_create_.env.local_instead
|
||||
|
||||
##########################
|
||||
# DATABASE #
|
||||
##########################
|
||||
DB_USER=${APP_NAME}
|
||||
DB_PASS=test123
|
||||
DATABASE_URL="postgres://${APP_NAME}:${DB_PASS}@localhost:5432/${APP_NAME}"
|
||||
DIRECT_URL="postgres://${APP_NAME}:${DB_PASS}@localhost:5432/${APP_NAME}"
|
||||
|
||||
##########################
|
||||
# AUTHENTICATION #
|
||||
##########################
|
||||
PUBLIC_CLERK_PUBLISHABLE_KEY=secret_do_not_commit_or_change_this_create_.env.local_instead
|
||||
CLERK_SECRET_KEY=secret_do_not_commit_or_change_this_create_.env.local_instead
|
||||
46
.github/workflows/deploy.yml
vendored
Normal file
46
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy-app:
|
||||
name: Deploy ${{ env.GITHUB_REPOSITORY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.GITHUB_SERVER_URL }}
|
||||
username: ${{ env.GITHUB_REPOSITORY_OWNER }}
|
||||
password: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: git.palko.ca/${{ env.GITHUB_REPOSITORY }}:latest
|
||||
deploy-storybook:
|
||||
name: Deploy ${{ env.GITHUB_REPOSITORY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.GITHUB_SERVER_URL }}
|
||||
username: ${{ env.GITHUB_REPOSITORY_OWNER }}
|
||||
password: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
file: Dockerfile.storybook
|
||||
tags: git.palko.ca/${{ env.GITHUB_REPOSITORY }}-storybook:latest
|
||||
27
.github/workflows/pr.yml
vendored
Normal file
27
.github/workflows/pr.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
name: PR Gate
|
||||
on:
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
jobs:
|
||||
checks:
|
||||
name: Checks
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Bun Setup
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.tool-versions'
|
||||
- name: Install
|
||||
run: bun install
|
||||
- name: Lint
|
||||
run: bun lint
|
||||
- name: Build
|
||||
run: bun run build
|
||||
- name: Svelte Check
|
||||
run: bun check
|
||||
- name: Prisma Check
|
||||
run: bun prisma:validate
|
||||
- name: Test
|
||||
run: bun run test:unit
|
||||
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
test-results
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea
|
||||
|
||||
# Storybook
|
||||
storybook-static
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
*/dev.db
|
||||
*/dev.db-journal
|
||||
|
||||
# Env files
|
||||
.env.*
|
||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
*.md
|
||||
|
||||
21
.prettierrc
Normal file
21
.prettierrc
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"endOfLine": "lf",
|
||||
"arrowParens": "always",
|
||||
"jsxSingleQuote": false,
|
||||
"semi": true,
|
||||
"quoteProps": "as-needed",
|
||||
"tabWidth": 4,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
19
.scripts/db.ts
Normal file
19
.scripts/db.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const appName = Bun.env.APP_NAME;
|
||||
|
||||
const execCommand = promisify(exec);
|
||||
const command = `docker compose -p ${appName} -f devops/docker-compose.dev.yml up -d && docker compose -p ${appName} -f devops/docker-compose.dev.yml -f devops/docker-compose.wait.yml run --rm wait -c "${appName}-database:5432"`;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execCommand(command, { env: Bun.env });
|
||||
if (stderr) {
|
||||
console.error(stderr);
|
||||
}
|
||||
console.log(stdout);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
17
.storybook/main.js
Normal file
17
.storybook/main.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/** @type { import('@storybook/sveltekit').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
|
||||
addons: [
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/addon-styling-webpack',
|
||||
'@storybook/addon-svelte-csf',
|
||||
'@storybook/addon-themes',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/sveltekit',
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
3
.storybook/preview.css
Normal file
3
.storybook/preview.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
28
.storybook/preview.js
Normal file
28
.storybook/preview.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import './preview.css';
|
||||
|
||||
/** @type { import('@storybook/svelte').Preview } */
|
||||
const preview = {
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
withThemeByDataAttribute({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
night: 'night',
|
||||
},
|
||||
defaultTheme: 'dark',
|
||||
attributeName: 'data-theme',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
|
@ -0,0 +1 @@
|
|||
bun 1.2.5
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
FROM oven/bun:1.2-alpine AS build
|
||||
|
||||
WORKDIR /opt/build
|
||||
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile \
|
||||
&& bun run build
|
||||
|
||||
FROM node:18-alpine3.21
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
COPY ./package.json ./package.json
|
||||
COPY --from=build --chown=1000:1000 /opt/build/node_modules/ /opt/app/node_modules/
|
||||
COPY --from=build --chown=1000:1000 /opt/build/build /opt/app
|
||||
|
||||
ENTRYPOINT [ "node", "index.js" ]
|
||||
11
Dockerfile.storybook
Normal file
11
Dockerfile.storybook
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
FROM oven/bun:1.2-alpine AS build
|
||||
|
||||
WORKDIR /opt/build
|
||||
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile \
|
||||
&& bun run build-storybook
|
||||
|
||||
FROM nginx:alpine3.21
|
||||
|
||||
COPY --from=build --chown=1000:1000 /opt/build/storybook-static /usr/share/nginx/html
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 pantheon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# svelte-template
|
||||
|
||||
3
bunfig.toml
Normal file
3
bunfig.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[install.scopes]
|
||||
# registry with token
|
||||
"@pantheon" = { token = "$NPM_TOKEN", url = "https://git.palko.ca/api/packages/pantheon/npm/" }
|
||||
10
devops/docker-compose.dev.yml
Normal file
10
devops/docker-compose.dev.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
name: ${APP_NAME:?error}
|
||||
services:
|
||||
database:
|
||||
hostname: '${APP_NAME:?error}-database'
|
||||
image: 'postgres:12-alpine'
|
||||
ports:
|
||||
- '5432:5432'
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:?error}
|
||||
POSTGRES_PASSWORD: ${DB_PASS:?error}
|
||||
3
devops/docker-compose.wait.yml
Normal file
3
devops/docker-compose.wait.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
services:
|
||||
wait:
|
||||
image: dokku/wait
|
||||
0
e2e/demo.test.ts
Normal file
0
e2e/demo.test.ts
Normal file
55
eslint.config.js
Normal file
55
eslint.config.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import prettier from 'eslint-config-prettier';
|
||||
import js from '@eslint/js';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript-eslint';
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default ts.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector:
|
||||
'CallExpression:matches([callee.object.object.name="prisma"], [callee.object.object.name="prismaTransactionClient"], [callee.object.object.name="transactionClient"]):matches([callee.property.name="findFirst"], [callee.property.name="findMany"], [callee.property.name="updateMany"], [callee.property.name="deleteMany"], [callee.property.name="count"], [callee.property.name="aggregate"], [callee.property.name="groupBy"]):not(:has(ObjectExpression > Property[key.name="where"] > ObjectExpression > Property[key.name="tenantId"]))',
|
||||
message:
|
||||
'Please filter on the current tenant when using findFirst, findMany, updateMany, deleteMany, count, aggregate or groupBy.',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
3
messages/en.json
Normal file
3
messages/en.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"$schema": "https://inlang.com/schema/inlang-message-format"
|
||||
}
|
||||
98
package.json
Normal file
98
package.json
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"name": "chaos",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"postinstall": "prisma generate",
|
||||
"dev": "bun database:up && bun prisma:generate && vite dev",
|
||||
"build": "vite build",
|
||||
"build-storybook": "storybook build",
|
||||
"database:up": "bun .scripts/db.ts && bun prisma:push",
|
||||
"database:down": "docker compose -p hestia -f devops/docker-compose.dev.yml down",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"generate-secret": "bun ./scripts/generate-secret.ts",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test:unit": "vitest",
|
||||
"test": "bun run test:unit -- --run && bun run test:e2e",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"test:e2e": "playwright test",
|
||||
"prisma:dev": "prisma migrate dev",
|
||||
"prisma:format": "prisma format",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:push": "prisma db push",
|
||||
"prisma:reset": "prisma migrate reset --force",
|
||||
"prisma:studio": "prisma studio",
|
||||
"prisma:validate": "prisma validate",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.2",
|
||||
"@eslint/compat": "^1.2.3",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@storybook/addon-essentials": "^8.5.0",
|
||||
"@storybook/addon-interactions": "^8.5.0",
|
||||
"@storybook/addon-styling-webpack": "^1.0.1",
|
||||
"@storybook/addon-svelte-csf": "^5.0.0-next.13",
|
||||
"@storybook/addon-themes": "^8.5.0",
|
||||
"@storybook/blocks": "^8.5.0",
|
||||
"@storybook/svelte": "^8.5.0",
|
||||
"@storybook/sveltekit": "^8.5.0",
|
||||
"@storybook/test": "^8.5.0",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.15.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/bun": "^1.1.15",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"daisyui": "^4.12.22",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
"globals": "^15.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.3.0",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.2.6",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"prisma": "6.3.1",
|
||||
"storybook": "^8.5.0",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^2.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/backend": "1.21.4",
|
||||
"@clerk/themes": "^2.2.3",
|
||||
"@inlang/paraglide-sveltekit": "^0.15.0",
|
||||
"@pantheon/theia": "^1.0.0",
|
||||
"@pothos/core": "^4.3.0",
|
||||
"@pothos/plugin-prisma": "^4.4.0",
|
||||
"@prisma/client": "6.3.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"clerk-sveltekit": "https://pkg.pr.new/wobsoriano/clerk-sveltekit@ca15d4e",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"eslint_d": "^14.3.0",
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-yoga": "^5.10.4",
|
||||
"lucide-svelte": "^0.469.0",
|
||||
"pino": "^9.5.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"twilio": "^5.4.0",
|
||||
"zod": "^3.24.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{json,yml,yaml,css}": "prettier --write",
|
||||
"*.{js,ts,svelte}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
0
playwright.config.ts
Normal file
0
playwright.config.ts
Normal file
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
13
prisma/schema.prisma
Normal file
13
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
generator pothos {
|
||||
provider = "prisma-pothos-types"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_URL")
|
||||
}
|
||||
1
project.inlang/.gitignore
vendored
Normal file
1
project.inlang/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
cache
|
||||
1
project.inlang/project_id
Normal file
1
project.inlang/project_id
Normal file
|
|
@ -0,0 +1 @@
|
|||
0562ab5d57c0427c0e2a28e7393a4cb0f7bf5e59a8474a5f59ebaabdda3caf02
|
||||
17
project.inlang/settings.json
Normal file
17
project.inlang/settings.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@1/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@1/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@1/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@1/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@1/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@2/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@0/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.messageFormat": {
|
||||
"pathPattern": "./messages/{languageTag}.json"
|
||||
},
|
||||
"locales": ["en"],
|
||||
"baseLocale": "en"
|
||||
}
|
||||
7
src/app.css
Normal file
7
src/app.css
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
:root {
|
||||
@apply text-base-content;
|
||||
}
|
||||
24
src/app.d.ts
vendored
Normal file
24
src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
|
||||
import type { Tenant, User } from '@prisma/client';
|
||||
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
auth: {
|
||||
userId?: string;
|
||||
orgId?: string | null;
|
||||
sessionId?: string;
|
||||
};
|
||||
user: User;
|
||||
tenant: Tenant;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
12
src/app.html
Normal file
12
src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
7
src/demo.spec.ts
Normal file
7
src/demo.spec.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
5
src/hooks.server.ts
Normal file
5
src/hooks.server.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { validateSession } from '$lib/server/middleware';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { withClerkHandler } from 'clerk-sveltekit/server';
|
||||
|
||||
export const handle = sequence(withClerkHandler(), validateSession());
|
||||
4
src/lib/i18n/index.ts
Normal file
4
src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import * as runtime from '$lib/paraglide/runtime';
|
||||
import { createI18n } from '@inlang/paraglide-sveltekit';
|
||||
export * as messages from '$lib/paraglide/messages';
|
||||
export const i18n = createI18n(runtime);
|
||||
0
src/lib/index.ts
Normal file
0
src/lib/index.ts
Normal file
34
src/lib/server/logger/index.ts
Normal file
34
src/lib/server/logger/index.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { NODE_ENV } from '$env/static/private';
|
||||
import { type YogaLogger } from 'graphql-yoga';
|
||||
import pino from 'pino';
|
||||
|
||||
export const logger = pino({
|
||||
// Only use pino-pretty when NOT production
|
||||
...(NODE_ENV !== 'production' && {
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const yogaLogger: YogaLogger = {
|
||||
debug(...args) {
|
||||
// @ts-expect-error types dont match
|
||||
logger.debug(...args);
|
||||
},
|
||||
info(...args) {
|
||||
// @ts-expect-error types dont match
|
||||
logger.info(...args);
|
||||
},
|
||||
warn(...args) {
|
||||
// @ts-expect-error types dont match
|
||||
logger.warn(...args);
|
||||
},
|
||||
error(...args) {
|
||||
// @ts-expect-error types dont match
|
||||
logger.error(...args);
|
||||
},
|
||||
};
|
||||
1
src/lib/server/middleware/auth/index.ts
Normal file
1
src/lib/server/middleware/auth/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './validateSesssion';
|
||||
105
src/lib/server/middleware/auth/validateSesssion.ts
Normal file
105
src/lib/server/middleware/auth/validateSesssion.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { logger } from '$lib/server/logger';
|
||||
import { prisma } from '$lib/server/prisma';
|
||||
import { type Handle } from '@sveltejs/kit';
|
||||
import { clerkClient } from 'clerk-sveltekit/server';
|
||||
|
||||
const publicRoutes = ['/login'];
|
||||
|
||||
const loginRedirect = () =>
|
||||
new Response(null, {
|
||||
status: 307,
|
||||
headers: {
|
||||
location: '/login',
|
||||
},
|
||||
});
|
||||
|
||||
async function findOrCreateTenant(tenantClerkId: string) {
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: {
|
||||
clerkOrganizationId: tenantClerkId,
|
||||
},
|
||||
});
|
||||
|
||||
if (tenant) {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
const organization = await clerkClient.organizations.getOrganization({
|
||||
organizationId: tenantClerkId,
|
||||
});
|
||||
|
||||
return await prisma.tenant.create({
|
||||
data: {
|
||||
clerkOrganizationId: tenantClerkId,
|
||||
name: organization.name,
|
||||
slug: organization.slug ?? `tenant-${tenantClerkId}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function validateSession(): Handle {
|
||||
return async ({ event: { locals, url, ...rest }, resolve }) => {
|
||||
// Public route? LET THEM PASS!
|
||||
if (url !== null && publicRoutes.includes(url.pathname)) {
|
||||
return resolve({ locals, url, ...rest });
|
||||
}
|
||||
|
||||
// No session, redirect!
|
||||
if (!locals.auth.sessionId) {
|
||||
return loginRedirect();
|
||||
}
|
||||
|
||||
// No user, revoke session and redirect!
|
||||
if (!locals.auth.userId) {
|
||||
await clerkClient.sessions.revokeSession(locals.auth.sessionId);
|
||||
return loginRedirect();
|
||||
}
|
||||
|
||||
// No org, signout and redirect!
|
||||
if (!locals.auth.orgId) {
|
||||
await clerkClient.sessions.revokeSession(locals.auth.sessionId);
|
||||
return loginRedirect();
|
||||
}
|
||||
|
||||
// Make sure that a tenant exists for the clerk org
|
||||
const tenant = await findOrCreateTenant(locals.auth.orgId);
|
||||
|
||||
// Make sure a user exists for the clerk user
|
||||
const clerkUser = await clerkClient.users.getUser(locals.auth.userId);
|
||||
|
||||
let user = await prisma.user.findFirst({
|
||||
where: {
|
||||
clerkId: clerkUser.id,
|
||||
tenantId: tenant.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
if (clerkUser.emailAddresses.length === 0) {
|
||||
logger.error('User has no email address');
|
||||
await clerkClient.sessions.revokeSession(locals.auth.sessionId);
|
||||
|
||||
return loginRedirect();
|
||||
}
|
||||
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
clerkId: clerkUser.id,
|
||||
email: clerkUser.emailAddresses[0].emailAddress,
|
||||
name: clerkUser.fullName ?? '',
|
||||
tenantId: tenant.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (clerkUser.fullName === null) {
|
||||
logger.warn(`User {${user.id}} has no name!`);
|
||||
}
|
||||
}
|
||||
|
||||
// Load user and tenant into locals
|
||||
locals.user = user;
|
||||
locals.tenant = tenant;
|
||||
|
||||
return resolve({ locals, url, ...rest });
|
||||
};
|
||||
}
|
||||
17
src/lib/server/prisma/index.ts
Normal file
17
src/lib/server/prisma/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { logger } from '../logger';
|
||||
|
||||
export const prisma = new PrismaClient({
|
||||
log: [
|
||||
{ emit: 'event', level: 'query' },
|
||||
{ emit: 'event', level: 'info' },
|
||||
],
|
||||
});
|
||||
|
||||
prisma.$on('query', (event) => {
|
||||
logger.debug(`Query [${event.duration}ms]: ${event.query}`);
|
||||
});
|
||||
|
||||
prisma.$on('info', (event) => {
|
||||
logger.info(event.message);
|
||||
});
|
||||
20
src/routes/+error.svelte
Normal file
20
src/routes/+error.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { messages } from '$lib/i18n';
|
||||
</script>
|
||||
|
||||
<main class="flex h-full w-full flex-col items-center justify-center gap-8">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-5xl font-bold tracking-widest text-white">
|
||||
{page.status}
|
||||
</h1>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<h1 class="text-2xl font-bold">
|
||||
{page.error?.message}
|
||||
</h1>
|
||||
</div>
|
||||
<button onclick={() => goto('/')} class="btn btn-outline btn-neutral btn-wide"
|
||||
>{messages.error_page_go_home()}</button
|
||||
>
|
||||
</main>
|
||||
23
src/routes/+layout.svelte
Normal file
23
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import ClerkProvider from 'clerk-sveltekit/client/ClerkProvider.svelte';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import { ParaglideJS } from '@inlang/paraglide-sveltekit';
|
||||
import '../app.css';
|
||||
import { PUBLIC_CLERK_PUBLISHABLE_KEY } from '$env/static/public';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<ParaglideJS {i18n}>
|
||||
<ClerkProvider publishableKey={PUBLIC_CLERK_PUBLISHABLE_KEY}>
|
||||
<div class="layout">
|
||||
{@render children()}
|
||||
</div>
|
||||
</ClerkProvider>
|
||||
</ParaglideJS>
|
||||
|
||||
<style>
|
||||
.layout {
|
||||
@apply h-screen w-screen p-8;
|
||||
}
|
||||
</style>
|
||||
0
src/routes/+page.svelte
Normal file
0
src/routes/+page.svelte
Normal file
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
21
svelte.config.js
Normal file
21
svelte.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://svelte.dev/docs/kit/integrations
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
version: {
|
||||
name: '1.0.0-alpha',
|
||||
},
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
29
tailwind.config.ts
Normal file
29
tailwind.config.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import typography from '@tailwindcss/typography';
|
||||
import daisyui from 'daisyui';
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
display: ['Baskervville SC'],
|
||||
},
|
||||
animation: {
|
||||
fade: 'fadeIn .5s ease-in-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
from: { opacity: '0' },
|
||||
to: { opacity: '1' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [typography, daisyui],
|
||||
daisyui: {
|
||||
logs: false,
|
||||
},
|
||||
} satisfies Config;
|
||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
import { paraglide } from '@inlang/paraglide-sveltekit/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
paraglide({
|
||||
project: './project.inlang',
|
||||
outdir: './src/lib/paraglide',
|
||||
}),
|
||||
],
|
||||
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue