feat: mvp
This commit is contained in:
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.git
|
||||
.env
|
||||
8
.env.example
Normal file
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
||||
DATABASE_URL="postgres://root:mysecretpassword@localhost:5432/local"
|
||||
|
||||
AUTH_SECRET=adf
|
||||
AUTH_TRUST_HOST=true
|
||||
AUTH_NEXTCLOUD_ID=asdf
|
||||
AUTH_NEXTCLOUD_SECRET=adsf
|
||||
AUTH_NEXTCLOUD_ISSUER="https://cloud.schreifuchs.ch"
|
||||
AUTH_TRUST_HOST=true
|
||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
9
.husky/commit-msg
Normal file
9
.husky/commit-msg
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
if ! head -1 "$1" | grep -qE "^(feat|fix|chore|docs|test|style|refactor|perf|build|ci|revert)(\(.+?\))?: .{1,}$"; then
|
||||
echo "Aborting commit. Your commit message is invalid. Please use conventional commits" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! head -1 "$1" | grep -qE "^.{1,88}$"; then
|
||||
echo "Aborting commit. Your commit message is too long." >&2
|
||||
exit 1
|
||||
fi
|
||||
2
.husky/pre-commit
Normal file
2
.husky/pre-commit
Normal file
@@ -0,0 +1,2 @@
|
||||
pnpm run format
|
||||
pnpm run lint
|
||||
9
.prettierignore
Normal file
9
.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
15
.prettierrc
Normal file
15
.prettierrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
}
|
||||
}
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM node:25-trixie AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
# RUN corepack enable
|
||||
RUN npm install -g pnpm
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y python3 build-essential
|
||||
|
||||
ENV CI=true
|
||||
ENV npm_config_build_from_source=true
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./package.json ./
|
||||
COPY ./pnpm-lock.yaml ./
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
FROM base AS build
|
||||
|
||||
COPY . /app
|
||||
|
||||
ENV DATABASE_URL=build.db
|
||||
|
||||
COPY .env.example .env
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
FROM base
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/build .
|
||||
COPY ./drizzle ./drizzle
|
||||
COPY ./drizzle.config.ts ./drizzle.config.ts
|
||||
COPY ./start.sh ./start.sh
|
||||
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "sh", "start.sh" ]
|
||||
|
||||
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Aktiteil
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `pnpm install` ), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kitk/adapters) for your target environment.
|
||||
38
compose.yaml
Normal file
38
compose.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_PASSWORD: mysecretpassword
|
||||
POSTGRES_DB: local
|
||||
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql
|
||||
frontend:
|
||||
profiles:
|
||||
- frontend
|
||||
build: .
|
||||
ports:
|
||||
- 5173:3000
|
||||
environment:
|
||||
DATABASE_URL: 'postgres://root:mysecretpassword@db:5432/local'
|
||||
ORIGIN: 'http://localhost:5173'
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
AUTH_TRUST_HOST: true
|
||||
AUTH_NEXTCLOUD_ID: ${AUTH_NEXTCLOUD_ID}
|
||||
AUTH_NEXTCLOUD_SECRET: ${AUTH_NEXTCLOUD_SECRET}
|
||||
AUTH_NEXTCLOUD_ISSUER: ${AUTH_NEXTCLOUD_ISSUER}
|
||||
|
||||
depends_on:
|
||||
- db
|
||||
develop:
|
||||
watch:
|
||||
- path: .
|
||||
action: rebuild
|
||||
target: /app
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
12
drizzle.config.ts
Normal file
12
drizzle.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/lib/server/db/schema/index.ts',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: { url: process.env.DATABASE_URL },
|
||||
verbose: true,
|
||||
strict: true,
|
||||
casing: 'snake_case'
|
||||
});
|
||||
69
drizzle/0000_busy_zarda.sql
Normal file
69
drizzle/0000_busy_zarda.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
CREATE TABLE "account" (
|
||||
"userId" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"providerAccountId" text NOT NULL,
|
||||
"refresh_token" text,
|
||||
"access_token" text,
|
||||
"expires_at" integer,
|
||||
"token_type" text,
|
||||
"scope" text,
|
||||
"id_token" text,
|
||||
"session_state" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "authenticator" (
|
||||
"credentialID" text NOT NULL,
|
||||
"userId" text NOT NULL,
|
||||
"providerAccountId" text NOT NULL,
|
||||
"credentialPublicKey" text NOT NULL,
|
||||
"counter" integer NOT NULL,
|
||||
"credentialDeviceType" text NOT NULL,
|
||||
"credentialBackedUp" boolean NOT NULL,
|
||||
"transports" text,
|
||||
CONSTRAINT "authenticator_credentialID_unique" UNIQUE("credentialID")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "session" (
|
||||
"sessionToken" text PRIMARY KEY NOT NULL,
|
||||
"userId" text NOT NULL,
|
||||
"expires" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "verificationToken" (
|
||||
"identifier" text NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"expires" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text,
|
||||
"email" text,
|
||||
"emailVerified" timestamp,
|
||||
"image" text,
|
||||
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "akti" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"summary" text NOT NULL,
|
||||
"body" text NOT NULL,
|
||||
"user_id" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "rating" (
|
||||
"akti_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"rating" real NOT NULL,
|
||||
"comment" text,
|
||||
CONSTRAINT "rating_akti_id_user_id_pk" PRIMARY KEY("akti_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "authenticator" ADD CONSTRAINT "authenticator_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "akti" ADD CONSTRAINT "akti_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "rating" ADD CONSTRAINT "rating_akti_id_akti_id_fk" FOREIGN KEY ("akti_id") REFERENCES "public"."akti"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "rating" ADD CONSTRAINT "rating_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
2
drizzle/0001_worthless_shiva.sql
Normal file
2
drizzle/0001_worthless_shiva.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "akti" ADD COLUMN "version" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "rating" ADD COLUMN "akti_version" integer NOT NULL;
|
||||
2
drizzle/0002_demonic_glorian.sql
Normal file
2
drizzle/0002_demonic_glorian.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "rating" DROP CONSTRAINT "rating_akti_id_user_id_pk";--> statement-breakpoint
|
||||
ALTER TABLE "rating" ADD COLUMN "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL;
|
||||
1
drizzle/0003_tranquil_iron_monger.sql
Normal file
1
drizzle/0003_tranquil_iron_monger.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "akti" ALTER COLUMN "user_id" SET NOT NULL;
|
||||
421
drizzle/meta/0000_snapshot.json
Normal file
421
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,421 @@
|
||||
{
|
||||
"id": "edd8f7a0-6cbd-41fc-8bf6-a381230a2085",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"token_type": {
|
||||
"name": "token_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_state": {
|
||||
"name": "session_state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.authenticator": {
|
||||
"name": "authenticator",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"credentialID": {
|
||||
"name": "credentialID",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialPublicKey": {
|
||||
"name": "credentialPublicKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"counter": {
|
||||
"name": "counter",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialDeviceType": {
|
||||
"name": "credentialDeviceType",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialBackedUp": {
|
||||
"name": "credentialBackedUp",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"transports": {
|
||||
"name": "transports",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"authenticator_userId_user_id_fk": {
|
||||
"name": "authenticator_userId_user_id_fk",
|
||||
"tableFrom": "authenticator",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"authenticator_credentialID_unique": {
|
||||
"name": "authenticator_credentialID_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["credentialID"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"sessionToken": {
|
||||
"name": "sessionToken",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verificationToken": {
|
||||
"name": "verificationToken",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"emailVerified": {
|
||||
"name": "emailVerified",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.akti": {
|
||||
"name": "akti",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary": {
|
||||
"name": "summary",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"body": {
|
||||
"name": "body",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"akti_user_id_user_id_fk": {
|
||||
"name": "akti_user_id_user_id_fk",
|
||||
"tableFrom": "akti",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.rating": {
|
||||
"name": "rating",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"akti_id": {
|
||||
"name": "akti_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"comment": {
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"rating_akti_id_akti_id_fk": {
|
||||
"name": "rating_akti_id_akti_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "akti",
|
||||
"columnsFrom": ["akti_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"rating_user_id_user_id_fk": {
|
||||
"name": "rating_user_id_user_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"rating_akti_id_user_id_pk": {
|
||||
"name": "rating_akti_id_user_id_pk",
|
||||
"columns": ["akti_id", "user_id"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
434
drizzle/meta/0001_snapshot.json
Normal file
434
drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,434 @@
|
||||
{
|
||||
"id": "c142665c-e8ff-4d27-8409-b23ca6b82039",
|
||||
"prevId": "edd8f7a0-6cbd-41fc-8bf6-a381230a2085",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"token_type": {
|
||||
"name": "token_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_state": {
|
||||
"name": "session_state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.authenticator": {
|
||||
"name": "authenticator",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"credentialID": {
|
||||
"name": "credentialID",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialPublicKey": {
|
||||
"name": "credentialPublicKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"counter": {
|
||||
"name": "counter",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialDeviceType": {
|
||||
"name": "credentialDeviceType",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialBackedUp": {
|
||||
"name": "credentialBackedUp",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"transports": {
|
||||
"name": "transports",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"authenticator_userId_user_id_fk": {
|
||||
"name": "authenticator_userId_user_id_fk",
|
||||
"tableFrom": "authenticator",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"authenticator_credentialID_unique": {
|
||||
"name": "authenticator_credentialID_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["credentialID"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"sessionToken": {
|
||||
"name": "sessionToken",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verificationToken": {
|
||||
"name": "verificationToken",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"emailVerified": {
|
||||
"name": "emailVerified",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.akti": {
|
||||
"name": "akti",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary": {
|
||||
"name": "summary",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"body": {
|
||||
"name": "body",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"akti_user_id_user_id_fk": {
|
||||
"name": "akti_user_id_user_id_fk",
|
||||
"tableFrom": "akti",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.rating": {
|
||||
"name": "rating",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"akti_id": {
|
||||
"name": "akti_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"comment": {
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"akti_version": {
|
||||
"name": "akti_version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"rating_akti_id_akti_id_fk": {
|
||||
"name": "rating_akti_id_akti_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "akti",
|
||||
"columnsFrom": ["akti_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"rating_user_id_user_id_fk": {
|
||||
"name": "rating_user_id_user_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"rating_akti_id_user_id_pk": {
|
||||
"name": "rating_akti_id_user_id_pk",
|
||||
"columns": ["akti_id", "user_id"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
436
drizzle/meta/0002_snapshot.json
Normal file
436
drizzle/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,436 @@
|
||||
{
|
||||
"id": "88b612dd-f1df-4eaf-a9d9-cd29db8996b9",
|
||||
"prevId": "c142665c-e8ff-4d27-8409-b23ca6b82039",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"token_type": {
|
||||
"name": "token_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_state": {
|
||||
"name": "session_state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.authenticator": {
|
||||
"name": "authenticator",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"credentialID": {
|
||||
"name": "credentialID",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialPublicKey": {
|
||||
"name": "credentialPublicKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"counter": {
|
||||
"name": "counter",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialDeviceType": {
|
||||
"name": "credentialDeviceType",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialBackedUp": {
|
||||
"name": "credentialBackedUp",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"transports": {
|
||||
"name": "transports",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"authenticator_userId_user_id_fk": {
|
||||
"name": "authenticator_userId_user_id_fk",
|
||||
"tableFrom": "authenticator",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"authenticator_credentialID_unique": {
|
||||
"name": "authenticator_credentialID_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["credentialID"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"sessionToken": {
|
||||
"name": "sessionToken",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verificationToken": {
|
||||
"name": "verificationToken",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"emailVerified": {
|
||||
"name": "emailVerified",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.akti": {
|
||||
"name": "akti",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary": {
|
||||
"name": "summary",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"body": {
|
||||
"name": "body",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"akti_user_id_user_id_fk": {
|
||||
"name": "akti_user_id_user_id_fk",
|
||||
"tableFrom": "akti",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.rating": {
|
||||
"name": "rating",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"akti_id": {
|
||||
"name": "akti_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"comment": {
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"akti_version": {
|
||||
"name": "akti_version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"rating_akti_id_akti_id_fk": {
|
||||
"name": "rating_akti_id_akti_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "akti",
|
||||
"columnsFrom": ["akti_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"rating_user_id_user_id_fk": {
|
||||
"name": "rating_user_id_user_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
436
drizzle/meta/0003_snapshot.json
Normal file
436
drizzle/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,436 @@
|
||||
{
|
||||
"id": "8f81af0f-3a4e-4fb8-b7f1-cf4e6eec7ecb",
|
||||
"prevId": "88b612dd-f1df-4eaf-a9d9-cd29db8996b9",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"token_type": {
|
||||
"name": "token_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_state": {
|
||||
"name": "session_state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.authenticator": {
|
||||
"name": "authenticator",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"credentialID": {
|
||||
"name": "credentialID",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialPublicKey": {
|
||||
"name": "credentialPublicKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"counter": {
|
||||
"name": "counter",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialDeviceType": {
|
||||
"name": "credentialDeviceType",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credentialBackedUp": {
|
||||
"name": "credentialBackedUp",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"transports": {
|
||||
"name": "transports",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"authenticator_userId_user_id_fk": {
|
||||
"name": "authenticator_userId_user_id_fk",
|
||||
"tableFrom": "authenticator",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"authenticator_credentialID_unique": {
|
||||
"name": "authenticator_credentialID_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["credentialID"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"sessionToken": {
|
||||
"name": "sessionToken",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["userId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verificationToken": {
|
||||
"name": "verificationToken",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires": {
|
||||
"name": "expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"emailVerified": {
|
||||
"name": "emailVerified",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.akti": {
|
||||
"name": "akti",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary": {
|
||||
"name": "summary",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"body": {
|
||||
"name": "body",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"akti_user_id_user_id_fk": {
|
||||
"name": "akti_user_id_user_id_fk",
|
||||
"tableFrom": "akti",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.rating": {
|
||||
"name": "rating",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"akti_id": {
|
||||
"name": "akti_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"comment": {
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"akti_version": {
|
||||
"name": "akti_version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"rating_akti_id_akti_id_fk": {
|
||||
"name": "rating_akti_id_akti_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "akti",
|
||||
"columnsFrom": ["akti_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"rating_user_id_user_id_fk": {
|
||||
"name": "rating_user_id_user_id_fk",
|
||||
"tableFrom": "rating",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
34
drizzle/meta/_journal.json
Normal file
34
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1763928082321,
|
||||
"tag": "0000_busy_zarda",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1763997401452,
|
||||
"tag": "0001_worthless_shiva",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1763999378258,
|
||||
"tag": "0002_demonic_glorian",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1764000151184,
|
||||
"tag": "0003_tranquil_iron_monger",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
38
eslint.config.js
Normal file
38
eslint.config.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node }
|
||||
},
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
60
package.json
Normal file
60
package.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "aktiteil",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"db:start": "docker compose up",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@auth/core": "0.41.1",
|
||||
"@auth/sveltekit": "1.11.1",
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@flowbite-svelte-plugins/texteditor": "^0.25.6",
|
||||
"@sveltejs/adapter-node": "^5.4.0",
|
||||
"@sveltejs/kit": "^2.49.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tiptap/core": "3.7.2",
|
||||
"@types/node": "^20.19.25",
|
||||
"drizzle-kit": "^0.31.7",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-svelte": "^3.13.0",
|
||||
"flowbite": "^4.0.1",
|
||||
"flowbite-svelte": "^1.28.1",
|
||||
"flowbite-svelte-icons": "^3.0.0",
|
||||
"globals": "^16.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"lowlight": "^3.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.43.14",
|
||||
"svelte-check": "^4.3.4",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.47.0",
|
||||
"vite": "^7.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/drizzle-adapter": "^1.11.1",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"postgres": "^3.4.7",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"valibot": "^1.1.0"
|
||||
}
|
||||
}
|
||||
5173
pnpm-lock.yaml
generated
Normal file
5173
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
74
src/app.css
Normal file
74
src/app.css
Normal file
@@ -0,0 +1,74 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin 'flowbite/plugin';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--color-primary-50: #fff5f2;
|
||||
--color-primary-100: #fff1ee;
|
||||
--color-primary-200: #ffe4de;
|
||||
--color-primary-300: #ffd5cc;
|
||||
--color-primary-400: #ffbcad;
|
||||
--color-primary-500: #fe795d;
|
||||
--color-primary-600: #ef562f;
|
||||
--color-primary-700: #eb4f27;
|
||||
--color-primary-800: #cc4522;
|
||||
--color-primary-900: #a5371b;
|
||||
|
||||
--color-secondary-50: #f0f9ff;
|
||||
--color-secondary-100: #e0f2fe;
|
||||
--color-secondary-200: #bae6fd;
|
||||
--color-secondary-300: #7dd3fc;
|
||||
--color-secondary-400: #38bdf8;
|
||||
--color-secondary-500: #0ea5e9;
|
||||
--color-secondary-600: #0284c7;
|
||||
--color-secondary-700: #0369a1;
|
||||
--color-secondary-800: #075985;
|
||||
--color-secondary-900: #0c4a6e;
|
||||
}
|
||||
|
||||
@source "../node_modules/flowbite-svelte/dist";
|
||||
@source "../node_modules/flowbite-svelte-icons/dist";
|
||||
|
||||
@layer base {
|
||||
/* disable chrome cancel button */
|
||||
input[type='search']::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-4xl md:text-5xl lg:text-6xl;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-3xl md:text-4xl lg:text-5xl;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-2xl md:text-3xl lg:text-4xl;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-xl md:text-2xl lg:text-3xl;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@apply text-lg md:text-xl lg:text-2xl;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@apply text-base md:text-lg lg:text-xl;
|
||||
}
|
||||
}
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
14
src/app.html
Normal file
14
src/app.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body
|
||||
data-sveltekit-preload-data="hover"
|
||||
class="dark:bg-gray-800 dark:text-gray-200 min-h-screen flex flex-col"
|
||||
>
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
||||
17
src/auth.ts
Normal file
17
src/auth.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SvelteKitAuth } from '@auth/sveltekit';
|
||||
import Nextcloud from '@auth/sveltekit/providers/nextcloud';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { DrizzleAdapter } from '@auth/drizzle-adapter';
|
||||
import { db } from '$lib/server/db';
|
||||
|
||||
export const { handle, signIn, signOut } = SvelteKitAuth({
|
||||
trustHost: env.AUTH_TRUST_HOST === 'true',
|
||||
adapter: DrizzleAdapter(db),
|
||||
providers: [
|
||||
Nextcloud({
|
||||
clientId: env.AUTH_NEXTCLOUD_ID,
|
||||
clientSecret: env.AUTH_NEXTCLOUD_SECRET,
|
||||
issuer: env.AUTH_NEXTCLOUD_ISSUER
|
||||
})
|
||||
]
|
||||
});
|
||||
1
src/hooks.server.ts
Normal file
1
src/hooks.server.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { handle } from './auth';
|
||||
1
src/lib/assets/favicon.svg
Normal file
1
src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
43
src/lib/auth.ts
Normal file
43
src/lib/auth.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Session, User } from '@auth/sveltekit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { db } from './server/db';
|
||||
import { users } from './server/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
interface Event {
|
||||
locals: {
|
||||
auth(): Promise<Session | null>;
|
||||
};
|
||||
}
|
||||
interface UserWithId extends User {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export async function ensureAuth(event: Event): Promise<UserWithId> {
|
||||
const session = await getSession(event);
|
||||
if (!session) error(401, { message: 'Du muesch di zersch iiloge' });
|
||||
|
||||
const user = session?.user;
|
||||
if (!user || !user.email || !user.id) {
|
||||
error(401, { message: 'Du muesch di zersch iiloge' });
|
||||
}
|
||||
|
||||
return { ...user, id: user.id, email: user.email }; // weird thingamajig so that ts compiler is happy
|
||||
}
|
||||
export async function getSession(event: Event) {
|
||||
const session = await event.locals.auth();
|
||||
if (!session) return null;
|
||||
if (!session.user) error(403, { message: 'Di gits garnid. Vilich nomau usloge u iiloge?' });
|
||||
|
||||
const res = await db
|
||||
.select({ id: users.id })
|
||||
.from(users)
|
||||
.limit(1)
|
||||
.where(eq(users.email, session.user.email ?? 'eaf9302d-9525-4f3e-8147-9620d2076f67')); //uuid as default to find nothing
|
||||
|
||||
if (!res[0]?.id) {
|
||||
error(403, { message: 'Di gits garnid. Vilich nomau usloge u iiloge?' });
|
||||
}
|
||||
|
||||
return { expires: session.expires, user: { ...session.user, id: res[0].id } };
|
||||
}
|
||||
78
src/lib/components/RichText.svelte
Normal file
78
src/lib/components/RichText.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { FormatButtonGroup, TextEditor } from '@flowbite-svelte-plugins/texteditor';
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import { Helper } from 'flowbite-svelte';
|
||||
|
||||
let {
|
||||
value = $bindable('Hello World'),
|
||||
name,
|
||||
required = false,
|
||||
minlength
|
||||
}: { value?: string; name?: string; required?: boolean; minlength?: number } = $props();
|
||||
|
||||
let editorInstance = $state<Editor | null>(null);
|
||||
let updating: boolean = false;
|
||||
|
||||
$effect(() => {
|
||||
if (updating) {
|
||||
updating = false;
|
||||
return;
|
||||
}
|
||||
// Only update content if it is actually different to prevent cursor jumping
|
||||
if (editorInstance && editorInstance.getHTML() !== value) {
|
||||
editorInstance.commands.setContent(value);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!editorInstance) return;
|
||||
|
||||
editorInstance.on('update', (editor) => {
|
||||
updating = true;
|
||||
const content = editor.editor.getHTML();
|
||||
errorMessage = '';
|
||||
|
||||
if (editor.editor.isEmpty) {
|
||||
value = '';
|
||||
} else {
|
||||
value = content;
|
||||
}
|
||||
});
|
||||
});
|
||||
let errorMessage = $state('');
|
||||
// Derived state to easily toggle classes
|
||||
</script>
|
||||
|
||||
<div class="relative group">
|
||||
<TextEditor
|
||||
bind:editor={editorInstance}
|
||||
content="<p></p> <p></p>"
|
||||
contentprops={{ id: 'formats-ex' }}
|
||||
>
|
||||
<FormatButtonGroup editor={editorInstance} />
|
||||
</TextEditor>
|
||||
|
||||
{#if name}
|
||||
<input
|
||||
type="text"
|
||||
{name}
|
||||
bind:value
|
||||
{required}
|
||||
minlength={minlength ? minlength + 6 : undefined}
|
||||
tabindex="-1"
|
||||
style="opacity: 0; position: absolute; pointer-events: none; z-index: -1; bottom: 0; left: 50%; height: 0; width: 0;"
|
||||
oninvalid={(e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
// 1. Read the message (e.g., "Please fill out this field.")
|
||||
errorMessage = target.validationMessage;
|
||||
editorInstance?.commands.focus();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{#if errorMessage}
|
||||
<Helper class="mt-2" color="red">
|
||||
{errorMessage}
|
||||
</Helper>
|
||||
{/if}
|
||||
28
src/lib/components/UserDisplay.svelte
Normal file
28
src/lib/components/UserDisplay.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import type { User } from '@auth/sveltekit';
|
||||
import { Avatar, Dropdown } from 'flowbite-svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { twMerge, type ClassNameValue } from 'tailwind-merge';
|
||||
let {
|
||||
user,
|
||||
children,
|
||||
class: className
|
||||
}: { user: User; children?: Snippet; class?: ClassNameValue } = $props();
|
||||
|
||||
let classList = $derived(twMerge('flex items-center gap-5', className));
|
||||
</script>
|
||||
|
||||
{#if children}
|
||||
<button class={classList}>
|
||||
<Avatar src={user.image ?? undefined} />
|
||||
<p>{user.name}</p>
|
||||
</button>
|
||||
<Dropdown simple>
|
||||
{@render children?.()}
|
||||
</Dropdown>
|
||||
{:else}
|
||||
<span class={classList}>
|
||||
<Avatar src={user.image ?? undefined} />
|
||||
<p>{user.name}</p>
|
||||
</span>
|
||||
{/if}
|
||||
23
src/lib/components/akti/AktiCard.svelte
Normal file
23
src/lib/components/akti/AktiCard.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Rating } from 'flowbite-svelte';
|
||||
interface Akti {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
rating?: number;
|
||||
}
|
||||
let { akti }: { akti: Akti } = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={resolve(`/akti/${akti.id}`)}
|
||||
class="w-full bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 shadow-md flex flex-col items-start p-5 gap-5 hover:shadow-lg transition-shadow duration-200"
|
||||
>
|
||||
<h4>{akti.title}</h4>
|
||||
<p class="text-left">{akti.summary}</p>
|
||||
|
||||
{#if akti.rating}
|
||||
<Rating id="example-1b" total={5} size={30} rating={akti.rating} class="self-end mt-auto" />
|
||||
{/if}
|
||||
</a>
|
||||
26
src/lib/components/akti/AktiEditor.svelte
Normal file
26
src/lib/components/akti/AktiEditor.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Button, Input, Label, Textarea } from 'flowbite-svelte';
|
||||
import RichText from '../RichText.svelte';
|
||||
interface Akti {
|
||||
title: string;
|
||||
summary: string;
|
||||
body: string;
|
||||
}
|
||||
let { akti }: { akti: Akti } = $props();
|
||||
</script>
|
||||
|
||||
<form method="POST" class="flex flex-col gap-5">
|
||||
<div>
|
||||
<Label>Titu</Label>
|
||||
<Input value={akti.title} type="text" name="title" required minlength={5} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Zämefassig</Label>
|
||||
<Textarea value={akti.summary} name="summary" required minlength={5} class="w-full min-h-40" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Inhaut</Label>
|
||||
<RichText value={akti.body} name="body" required />
|
||||
</div>
|
||||
<Button type="submit" class="grow-0 self-end">Spichere</Button>
|
||||
</form>
|
||||
62
src/lib/extractFormData.ts
Normal file
62
src/lib/extractFormData.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Code from: https://jovianmoon.io/posts/sveltekit-form-validation-with-valibot
|
||||
*/
|
||||
import { dev } from '$app/environment';
|
||||
import * as v from 'valibot';
|
||||
|
||||
export const extractFormData = async <TInput = unknown, TOutput = TInput>(
|
||||
request: Request,
|
||||
schema: v.BaseSchema<TInput, TOutput, v.BaseIssue<unknown>>
|
||||
): Promise<{
|
||||
data: TOutput | undefined;
|
||||
error: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
// Convert form data to an object with proper handling of multiple values
|
||||
formData.forEach((value, key) => {
|
||||
// Case 1: First time encountering this key
|
||||
if (result[key] === undefined) {
|
||||
result[key] = value;
|
||||
}
|
||||
// Case 2: Key exists and is already an array, add new value
|
||||
else if (Array.isArray(result[key])) {
|
||||
result[key].push(value);
|
||||
}
|
||||
// Case 3: Key exists but isn't an array yet, convert to array with both values
|
||||
else {
|
||||
result[key] = [result[key], value];
|
||||
}
|
||||
});
|
||||
|
||||
const validation = v.safeParse(schema, result);
|
||||
if (!validation.success) {
|
||||
if (dev) {
|
||||
console.error('Validation errors:');
|
||||
for (const error of validation.issues) {
|
||||
console.error(`- ${error.message}`);
|
||||
}
|
||||
}
|
||||
if (validation.issues && validation.issues.length > 0) {
|
||||
return {
|
||||
data: undefined,
|
||||
error: validation.issues.map((issue) => issue.message).join(', ')
|
||||
};
|
||||
}
|
||||
return {
|
||||
data: undefined,
|
||||
error: 'Error validating form submission, please check everything carefully.'
|
||||
};
|
||||
}
|
||||
|
||||
return { data: validation.output as TOutput, error: null };
|
||||
} catch (error) {
|
||||
if (dev) {
|
||||
console.error(`Error extracting form data: ${error}`);
|
||||
}
|
||||
return { data: undefined, error: `Error extracting form data: ${error}` };
|
||||
}
|
||||
};
|
||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
10
src/lib/server/db/index.ts
Normal file
10
src/lib/server/db/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { building } from '$app/environment';
|
||||
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
|
||||
const client = building ? postgres() : postgres(env.DATABASE_URL);
|
||||
|
||||
export const db = drizzle(client, { schema, casing: 'snake_case' });
|
||||
49
src/lib/server/db/schema/akti.ts
Normal file
49
src/lib/server/db/schema/akti.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { relations, sql } from 'drizzle-orm';
|
||||
import { integer, pgTable, real, text, uuid } from 'drizzle-orm/pg-core';
|
||||
import { users } from './user';
|
||||
|
||||
export const aktis = pgTable('akti', {
|
||||
id: uuid()
|
||||
.default(sql`gen_random_uuid()`)
|
||||
.primaryKey(),
|
||||
title: text().notNull(),
|
||||
summary: text().notNull(),
|
||||
body: text().notNull(),
|
||||
author: text('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
version: integer('version').notNull().default(1)
|
||||
});
|
||||
|
||||
export const ratings = pgTable('rating', {
|
||||
id: uuid()
|
||||
.default(sql`gen_random_uuid()`)
|
||||
.primaryKey(),
|
||||
aktiId: uuid()
|
||||
.notNull()
|
||||
.references(() => aktis.id, { onDelete: 'cascade' }),
|
||||
userId: text()
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
rating: real().notNull(),
|
||||
comment: text(),
|
||||
aktiVersion: integer('akti_version').notNull()
|
||||
});
|
||||
|
||||
export const aktisRelations = relations(aktis, ({ one }) => ({
|
||||
author: one(users, {
|
||||
fields: [aktis.author],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const ratingsRelations = relations(ratings, ({ one }) => ({
|
||||
akti: one(aktis, {
|
||||
fields: [ratings.aktiId],
|
||||
references: [aktis.id]
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [ratings.userId],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
76
src/lib/server/db/schema/auth.ts
Normal file
76
src/lib/server/db/schema/auth.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { boolean, integer, pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import type { AdapterAccountType } from '@auth/core/adapters';
|
||||
import { users } from './user';
|
||||
|
||||
export const accounts = pgTable(
|
||||
'account',
|
||||
{
|
||||
userId: text('userId')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
type: text('type').$type<AdapterAccountType>().notNull(),
|
||||
provider: text('provider').notNull(),
|
||||
providerAccountId: text('providerAccountId').notNull(),
|
||||
refresh_token: text('refresh_token'),
|
||||
access_token: text('access_token'),
|
||||
expires_at: integer('expires_at'),
|
||||
token_type: text('token_type'),
|
||||
scope: text('scope'),
|
||||
id_token: text('id_token'),
|
||||
session_state: text('session_state')
|
||||
},
|
||||
(account) => [
|
||||
{
|
||||
compoundKey: primaryKey({
|
||||
columns: [account.provider, account.providerAccountId]
|
||||
})
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
export const sessions = pgTable('session', {
|
||||
sessionToken: text('sessionToken').primaryKey(),
|
||||
userId: text('userId')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
expires: timestamp('expires', { mode: 'date' }).notNull()
|
||||
});
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
'verificationToken',
|
||||
{
|
||||
identifier: text('identifier').notNull(),
|
||||
token: text('token').notNull(),
|
||||
expires: timestamp('expires', { mode: 'date' }).notNull()
|
||||
},
|
||||
(verificationToken) => [
|
||||
{
|
||||
compositePk: primaryKey({
|
||||
columns: [verificationToken.identifier, verificationToken.token]
|
||||
})
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
export const authenticators = pgTable(
|
||||
'authenticator',
|
||||
{
|
||||
credentialID: text('credentialID').notNull().unique(),
|
||||
userId: text('userId')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
providerAccountId: text('providerAccountId').notNull(),
|
||||
credentialPublicKey: text('credentialPublicKey').notNull(),
|
||||
counter: integer('counter').notNull(),
|
||||
credentialDeviceType: text('credentialDeviceType').notNull(),
|
||||
credentialBackedUp: boolean('credentialBackedUp').notNull(),
|
||||
transports: text('transports')
|
||||
},
|
||||
(authenticator) => [
|
||||
{
|
||||
compositePK: primaryKey({
|
||||
columns: [authenticator.userId, authenticator.credentialID]
|
||||
})
|
||||
}
|
||||
]
|
||||
);
|
||||
3
src/lib/server/db/schema/index.ts
Normal file
3
src/lib/server/db/schema/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './auth.ts';
|
||||
export * from './user.ts';
|
||||
export * from './akti.ts';
|
||||
11
src/lib/server/db/schema/user.ts
Normal file
11
src/lib/server/db/schema/user.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('user', {
|
||||
id: text('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: text('name'),
|
||||
email: text('email').unique(),
|
||||
emailVerified: timestamp('emailVerified', { mode: 'date' }),
|
||||
image: text('image')
|
||||
});
|
||||
7
src/routes/+error.svelte
Normal file
7
src/routes/+error.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center grow">
|
||||
<h3>{page.status}: {page.error?.message ?? 'Upsi, da isch öppis kaputt gange...'}</h3>
|
||||
</div>
|
||||
10
src/routes/+layout.server.ts
Normal file
10
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getSession as getSession } from '$lib/auth';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
const session = await getSession(event);
|
||||
|
||||
return {
|
||||
session
|
||||
};
|
||||
};
|
||||
47
src/routes/+layout.svelte
Normal file
47
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import {
|
||||
Navbar,
|
||||
NavBrand,
|
||||
NavLi,
|
||||
NavUl,
|
||||
NavHamburger,
|
||||
DropdownItem,
|
||||
Button
|
||||
} from 'flowbite-svelte';
|
||||
import type { LayoutProps } from './$types';
|
||||
import { signIn, signOut } from '@auth/sveltekit/client';
|
||||
import UserDisplay from '$lib/components/UserDisplay.svelte';
|
||||
|
||||
let { data, children }: LayoutProps = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
<Navbar class="border-b border-gray-200 dark:border-gray-700">
|
||||
<NavBrand href="/">
|
||||
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">aktiteil</span
|
||||
>
|
||||
</NavBrand>
|
||||
|
||||
<NavHamburger />
|
||||
<NavUl>
|
||||
<NavLi href="/">Dehei</NavLi>
|
||||
<NavLi href="/akti">Neui Akti</NavLi>
|
||||
</NavUl>
|
||||
|
||||
{#if data.session?.user}
|
||||
<UserDisplay user={data.session.user}>
|
||||
<DropdownItem>
|
||||
<Button onclick={() => signOut()}>Sign out</Button>
|
||||
</DropdownItem>
|
||||
</UserDisplay>
|
||||
{:else}
|
||||
<Button onclick={() => signIn('nextcloud')}>Signin</Button>
|
||||
{/if}
|
||||
</Navbar>
|
||||
<main class="p-5 sm:px-20 2xl:px-60 flex flex-col h-full grow">
|
||||
{@render children()}
|
||||
</main>
|
||||
21
src/routes/+page.server.ts
Normal file
21
src/routes/+page.server.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis, ratings } from '$lib/server/db/schema';
|
||||
import { avg, eq } from 'drizzle-orm';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const a = await db
|
||||
.select({
|
||||
id: aktis.id,
|
||||
title: aktis.title,
|
||||
summary: aktis.summary,
|
||||
rating: avg(ratings.rating)
|
||||
})
|
||||
.from(aktis)
|
||||
.leftJoin(ratings, eq(aktis.id, ratings.aktiId))
|
||||
.groupBy(aktis.id, aktis.title, aktis.summary);
|
||||
|
||||
return {
|
||||
aktis: a.map((a) => ({ ...a, rating: a.rating ? parseFloat(a.rating) : undefined }))
|
||||
};
|
||||
};
|
||||
12
src/routes/+page.svelte
Normal file
12
src/routes/+page.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import AktiCard from '$lib/components/akti/AktiCard.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 3xl:grid-cols-5">
|
||||
{#each data.aktis as akti (akti.id)}
|
||||
<AktiCard {akti}></AktiCard>
|
||||
{/each}
|
||||
</div>
|
||||
38
src/routes/akti/+page.server.ts
Normal file
38
src/routes/akti/+page.server.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { redirect, type Actions } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { extractFormData } from '$lib/extractFormData';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
import * as v from 'valibot';
|
||||
import { ensureAuth } from '$lib/auth';
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis } from '$lib/server/db/schema';
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
await ensureAuth(event);
|
||||
return {};
|
||||
};
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
const user = await ensureAuth(event);
|
||||
|
||||
const akti = (
|
||||
await extractFormData(
|
||||
event.request,
|
||||
v.object({
|
||||
title: v.pipe(v.string(), v.minLength(5)),
|
||||
summary: v.pipe(v.string(), v.minLength(5)),
|
||||
body: v.pipe(v.string(), v.minLength(5))
|
||||
})
|
||||
)
|
||||
).data;
|
||||
|
||||
if (!akti) return {};
|
||||
|
||||
const res = await db
|
||||
.insert(aktis)
|
||||
.values({ ...akti, author: user.id! })
|
||||
.returning({ id: aktis.id });
|
||||
|
||||
return redirect(303, resolve(`/akti/[aktiId]`, { aktiId: res[0].id }));
|
||||
}
|
||||
} satisfies Actions;
|
||||
5
src/routes/akti/+page.svelte
Normal file
5
src/routes/akti/+page.svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import AktiEditor from '$lib/components/akti/AktiEditor.svelte';
|
||||
</script>
|
||||
|
||||
<AktiEditor akti={{ title: '', summary: '', body: '' }} />
|
||||
66
src/routes/akti/[aktiId]/+page.server.ts
Normal file
66
src/routes/akti/[aktiId]/+page.server.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis, ratings } from '$lib/server/db/schema';
|
||||
import { error, redirect, type Actions } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { ensureAuth } from '$lib/auth';
|
||||
import { extractFormData } from '$lib/extractFormData';
|
||||
import * as v from 'valibot';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const akti = await db.query.aktis.findFirst({
|
||||
where: eq(aktis.id, event.params.aktiId),
|
||||
with: { author: true }
|
||||
});
|
||||
|
||||
const r = await db.query.ratings.findMany({
|
||||
with: { user: true },
|
||||
where: eq(ratings.aktiId, event.params.aktiId)
|
||||
});
|
||||
|
||||
if (!akti) {
|
||||
error(404, { message: 'Die Akti gits garnid, sorry...' });
|
||||
}
|
||||
|
||||
return {
|
||||
akti,
|
||||
ratings: r
|
||||
};
|
||||
};
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
const user = await ensureAuth(event);
|
||||
|
||||
if (!event.params.aktiId) return error(404);
|
||||
|
||||
const akti = await db
|
||||
.select({ id: aktis.id, version: aktis.version, author: aktis.author })
|
||||
.from(aktis)
|
||||
.limit(1)
|
||||
.where(eq(aktis.id, event.params.aktiId));
|
||||
|
||||
if (!akti || akti.length == 0) return error(404);
|
||||
if (akti[0].author != user.id) return error(403);
|
||||
|
||||
const changeRequest = (
|
||||
await extractFormData(
|
||||
event.request,
|
||||
v.object({
|
||||
title: v.pipe(v.string(), v.minLength(5)),
|
||||
summary: v.pipe(v.string(), v.minLength(5)),
|
||||
body: v.pipe(v.string(), v.minLength(5))
|
||||
})
|
||||
)
|
||||
).data;
|
||||
|
||||
if (!changeRequest) return error(400);
|
||||
|
||||
const res = await db
|
||||
.insert(aktis)
|
||||
.values({ ...changeRequest, author: user.id, version: akti[0].version + 1 })
|
||||
.returning({ id: aktis.id });
|
||||
|
||||
return redirect(303, resolve(`/akti/[aktiId]`, { aktiId: res[0].id }));
|
||||
}
|
||||
} satisfies Actions;
|
||||
40
src/routes/akti/[aktiId]/+page.svelte
Normal file
40
src/routes/akti/[aktiId]/+page.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Button } from 'flowbite-svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import { EditOutline, CloseOutline } from 'flowbite-svelte-icons';
|
||||
import AktiEditor from '$lib/components/akti/AktiEditor.svelte';
|
||||
import UserDisplay from '$lib/components/UserDisplay.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
let edit = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<h2>{data.akti?.title} <span class="text-xs text-gray-400">v{data.akti.version}</span></h2>
|
||||
{#if data.session?.user?.id === data.akti.author?.id && data.akti.author?.id}
|
||||
<Button onclick={() => (edit = !edit)} color={edit ? 'gray' : 'primary'}>
|
||||
{#if edit}
|
||||
<CloseOutline class="shrink-0 h-6 w-6" />
|
||||
{:else}
|
||||
<EditOutline class="shrink-0 h-6 w-6 -mr-0.5 ml-0.5" />
|
||||
{/if}
|
||||
</Button>
|
||||
{:else}
|
||||
<div class="flex gap-5 items-center">
|
||||
<p>gschribe vo:</p>
|
||||
<UserDisplay user={data.akti.author} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if edit}
|
||||
<AktiEditor akti={data.akti} />
|
||||
{:else}
|
||||
<div class="p-5 my-5 bg-gray-200 rounded-md">
|
||||
<h3 class="mb-2">Zämefassig</h3>
|
||||
<p>{data.akti.summary}</p>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html data.akti.body}
|
||||
{/if}
|
||||
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
18
svelte.config.js
Normal file
18
svelte.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
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: {
|
||||
// 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;
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"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
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
7
vite.config.ts
Normal file
7
vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user