Compare commits
9 Commits
2f17b84449
...
32281aa1ff
Author | SHA1 | Date | |
---|---|---|---|
|
32281aa1ff | ||
|
b8c555d8e7 | ||
|
7fed360ab2 | ||
|
33f22652d6 | ||
|
1f6e2d2ee1 | ||
|
a0890bb764 | ||
|
6c2136db0e | ||
|
c1a11ebf4c | ||
|
6adea0efbb |
43
.devcontainer/build/devcontainer.json
Executable file
43
.devcontainer/build/devcontainer.json
Executable file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "Kredens Prebuilt Dev Container",
|
||||
"build": {
|
||||
"dockerfile": "../../dockerfiles/devcontainer.dockerfile",
|
||||
"context": "../.."
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker": {
|
||||
"installDockerComposeSwitch": false
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"nodeGypDependencies": true,
|
||||
"installYarnUsingApt": true,
|
||||
"version": "lts",
|
||||
"pnpmVersion": "latest",
|
||||
"nvmVersion": "latest"
|
||||
}
|
||||
},
|
||||
"onCreateCommand": "onCreate.sh",
|
||||
"postAttachCommand": "postAttach.sh",
|
||||
"remoteEnv": {
|
||||
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}",
|
||||
"WORKSPACE_FOLDER": "${containerWorkspaceFolder}",
|
||||
"UV_LINK_MODE": "copy"
|
||||
},
|
||||
"mounts": [
|
||||
{
|
||||
"type": "volume",
|
||||
"source": "${devcontainerId}-node-modules",
|
||||
"target": "${containerWorkspaceFolder}/node_modules"
|
||||
},
|
||||
{
|
||||
"type": "volume",
|
||||
"source": "${devcontainerId}-bashhistory",
|
||||
"target": "/commandhistory"
|
||||
}
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["ms-azuretools.vscode-docker"]
|
||||
}
|
||||
}
|
||||
}
|
78
.devcontainer/devcontainer.json
Executable file → Normal file
78
.devcontainer/devcontainer.json
Executable file → Normal file
@ -1,76 +1,10 @@
|
||||
{
|
||||
"name": "Kredens Dev Container",
|
||||
"build": {
|
||||
"dockerfile": "../dockerfiles/devcontainer.dockerfile",
|
||||
"context": ".."
|
||||
},
|
||||
"forwardPorts": [
|
||||
80, 443, 2019
|
||||
],
|
||||
"portsAttributes": {
|
||||
"80": {
|
||||
"label": "Plaintext",
|
||||
"protocol": "http",
|
||||
"elevateIfNeeded": true
|
||||
},
|
||||
"443": {
|
||||
"label": "Encrypted",
|
||||
"protocol": "https",
|
||||
"elevateIfNeeded": true
|
||||
},
|
||||
"2019": {
|
||||
"label": "Admin API",
|
||||
"protocol": "http",
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"installDockerComposeSwitch": false
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"nodeGypDependencies": true,
|
||||
"installYarnUsingApt": true,
|
||||
"version": "lts",
|
||||
"pnpmVersion": "latest",
|
||||
"nvmVersion": "latest"
|
||||
},
|
||||
"ghcr.io/va-h/devcontainers-features/uv:1": {
|
||||
"shellautocompletion": true,
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
"onCreateCommand": "${containerWorkspaceFolder}/.devcontainer/onCreate.sh",
|
||||
"postAttachCommand": "${containerWorkspaceFolder}/.devcontainer/postAttach.sh",
|
||||
"name": "Kredens Dev Environment",
|
||||
"dockerComposeFile": "./docker-compose.devcontainer.yml",
|
||||
"service": "dev",
|
||||
"workspaceFolder": "/workspaces/kredens",
|
||||
"remoteEnv": {
|
||||
"WORKSPACE_FOLDER": "${containerWorkspaceFolder}",
|
||||
"UV_LINK_MODE": "copy"
|
||||
},
|
||||
"mounts": [
|
||||
{
|
||||
"type": "volume",
|
||||
"source": "${devcontainerId}-venv",
|
||||
"target": "${containerWorkspaceFolder}/.venv"
|
||||
},
|
||||
{
|
||||
"type": "volume",
|
||||
"source": "${devcontainerId}-node-modules",
|
||||
"target": "${containerWorkspaceFolder}/node_modules"
|
||||
},
|
||||
{
|
||||
"type": "volume",
|
||||
"source": "${devcontainerId}-bashhistory",
|
||||
"target": "/commandhistory"
|
||||
}
|
||||
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.debugpy",
|
||||
"ms-azuretools.vscode-docker"
|
||||
]
|
||||
}
|
||||
"COMPOSE_PROJECT_NAME": "kredens_devcontainer",
|
||||
"COMPOSE_FILE": "${containerWorkspaceFolder}/.devcontainer/docker-compose.devcontainer.yml"
|
||||
}
|
||||
}
|
||||
|
12
.devcontainer/docker-compose.devcontainer.yml
Normal file
12
.devcontainer/docker-compose.devcontainer.yml
Normal file
@ -0,0 +1,12 @@
|
||||
include:
|
||||
- ../docker-compose.yml
|
||||
services:
|
||||
dev:
|
||||
image: fullspectrumconfidence.modzero.xyz/modzero/kredens-devcontainer
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
command: sleep infinity
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo apt update -y
|
||||
sudo apt upgrade -y
|
||||
|
||||
sudo apt install -y postgresql-common
|
||||
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||
sudo apt upgrade -y
|
||||
sudo apt install -y postgresql-client
|
||||
|
||||
|
||||
git config --global --add safe.directory ${WORKSPACE_FOLDER}
|
||||
sudo chown $(id -u):$(id -g) -fR \
|
||||
${WORKSPACE_FOLDER} \
|
||||
${WORKSPACE_FOLDER}/.venv \
|
||||
${WORKSPACE_FOLDER}/node_modules \
|
||||
/commandhistory
|
||||
|
||||
cd ${WORKSPACE_FOLDER}
|
||||
|
||||
uv python install
|
||||
uv venv --allow-existing
|
||||
|
||||
uv tool install rust-just
|
||||
uv tool install ruff
|
@ -1,4 +1,5 @@
|
||||
.git
|
||||
node_modules
|
||||
.venv
|
||||
__pycache__
|
||||
.pnpm-store
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
5
.env
Normal file
5
.env
Normal file
@ -0,0 +1,5 @@
|
||||
DATABASE_USER=kredens
|
||||
DATABASE_NAME=kredens
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_URL=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}
|
2
.env.development
Normal file
2
.env.development
Normal file
@ -0,0 +1,2 @@
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_HOST=database
|
23
.gitignore
vendored
23
.gitignore
vendored
@ -1,4 +1,21 @@
|
||||
.venv
|
||||
.env
|
||||
node_modules
|
||||
__pycache__
|
||||
.pnpm-store
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env*.local
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
@ -1 +0,0 @@
|
||||
3.13
|
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "database",
|
||||
"port": 5432,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "Dev Container Backend",
|
||||
"database": "kredens",
|
||||
"username": "postgres"
|
||||
}
|
||||
]
|
||||
}
|
41
README.md
41
README.md
@ -1,19 +1,38 @@
|
||||
# Kredens
|
||||
# sv
|
||||
|
||||
Where I keep my spoons.
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Development
|
||||
## Creating a project
|
||||
|
||||
Create a .env file:
|
||||
```sh
|
||||
DATABASE_PASSWORD=TOTALMESSREPLACE
|
||||
DATABASE_URL=postgres://postgres:${DATABASE_PASSWORD}@host.docker.internal/kredens
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
Then, to start the thing:
|
||||
## Developing
|
||||
|
||||
```sh
|
||||
% docker compose up -d
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
The entire thing comes with a dev container that should mostly work.
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
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/kit/adapters) for your target environment.
|
||||
|
29
biome.json
Normal file
29
biome.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": [".pnpm-store", ".svelte-kit"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,4 @@
|
||||
services:
|
||||
backend:
|
||||
image: modzero.xyz/kredens/backend
|
||||
build:
|
||||
dockerfile: dockerfiles/backend.dockerfile
|
||||
context: .
|
||||
pull_policy: build
|
||||
develop:
|
||||
watch:
|
||||
- path: ./kredens
|
||||
action: sync
|
||||
target: /app/kredens
|
||||
- path: ./pyproject.toml
|
||||
action: rebuild
|
||||
- path: ./manage.py
|
||||
action: rebuild
|
||||
- path: ./uv.lock
|
||||
action: rebuild
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DATABASE_PASSWORD:?Database password required}@database/kredens
|
||||
depends_on:
|
||||
- database
|
||||
database:
|
||||
image: postgres:17
|
||||
restart: unless-stopped
|
||||
@ -43,7 +22,7 @@ services:
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
volumes:
|
||||
- ./configs/caddy/:/etc/caddy/
|
||||
- ${LOCAL_WORKSPACE_FOLDER:-./}dockerfiles/assets/caddy/:/etc/caddy/
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
ports:
|
||||
|
@ -10,7 +10,7 @@ localhost {
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy backend:8000 {
|
||||
reverse_proxy dev:5173 {
|
||||
header_up Host {host}
|
||||
}
|
||||
}
|
21
dockerfiles/assets/scripts/onCreate.sh
Executable file
21
dockerfiles/assets/scripts/onCreate.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
git config --global --add safe.directory ${WORKSPACE_FOLDER}
|
||||
sudo chown $(id -u):$(id -g) -fR \
|
||||
${WORKSPACE_FOLDER} \
|
||||
${WORKSPACE_FOLDER}/.venv \
|
||||
${WORKSPACE_FOLDER}/node_modules \
|
||||
/commandhistory
|
||||
|
||||
cd ${WORKSPACE_FOLDER}
|
||||
|
||||
if [ ! -f .env.local ]; then
|
||||
DATABASE_PASSWORD=$(pwgen 16 1)
|
||||
SUPERUSER_PASSWORD=$(pwgen 8 1)
|
||||
cat << EOF > .env.local
|
||||
DATABASE_PASSWORD=${DATABASE_PASSWORD}
|
||||
|
||||
SUPERUSER_EMAIL=super@example.org
|
||||
SUPERUSER_PASSWORD=${SUPERUSER_PASSWORD}
|
||||
EOF
|
||||
fi
|
@ -4,5 +4,3 @@ sudo apt update -y
|
||||
sudo apt upgrade -y
|
||||
|
||||
cd ${WORKSPACE_FOLDER}
|
||||
|
||||
uv sync --frozen
|
@ -1,21 +0,0 @@
|
||||
FROM python:3.12-slim-bookworm
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --no-install-project
|
||||
|
||||
COPY ./kredens/ /app/kredens
|
||||
COPY \
|
||||
manage.py \
|
||||
pyproject.toml \
|
||||
uv.lock \
|
||||
/app/
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen
|
||||
|
||||
ENTRYPOINT [ "uv", "run", "python", "./manage.py" ]
|
||||
CMD [ "runserver", "0.0.0.0:8000"]
|
@ -12,28 +12,50 @@ RUN \
|
||||
/etc/apt/apt.conf.d/docker-no-languages \
|
||||
&& apt-get update -y && apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
debian-keyring \
|
||||
debian-archive-keyring \
|
||||
apt-transport-https \
|
||||
gnupg
|
||||
debian-archive-keyring \
|
||||
debian-keyring \
|
||||
gnupg \
|
||||
postgresql-common \
|
||||
pwgen
|
||||
|
||||
RUN --mount=type=bind,target=/tmp/caddy.key,source=./configs/keys/caddy.key \
|
||||
# Add Caddy repo
|
||||
|
||||
RUN --mount=type=bind,target=/tmp/caddy.key,source=./dockerfiles/assets/keys/caddy.key \
|
||||
gpg -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg --dearmor /tmp/caddy.key
|
||||
COPY ./configs/apt/sources.list.d/caddy-stable.list /etc/apt/sources.list
|
||||
COPY ./dockerfiles/assets/apt-sources/caddy-stable.list /etc/apt/sources.list
|
||||
|
||||
# Add Postgresql repo
|
||||
|
||||
RUN /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update -y \
|
||||
apt-get update -y && apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
caddy
|
||||
caddy \
|
||||
postgresql-client
|
||||
|
||||
RUN mkdir -p /workspaces/kredens && chown vscode:vscode /workspaces
|
||||
WORKDIR /workspaces/kredens
|
||||
|
||||
# Persist history
|
||||
|
||||
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
||||
RUN \
|
||||
SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
||||
&& mkdir /commandhistory \
|
||||
&& touch /commandhistory/.bash_history \
|
||||
&& chown -R $USERNAME /commandhistory \
|
||||
&& echo "$SNIPPET" >> "/home/$USERNAME/.bashrc"
|
||||
&& echo "$SNIPPET" >> "/home/$USERNAME/.bashrc"
|
||||
|
||||
RUN \
|
||||
mkdir -p /home/${USERNAME}/.local/bin/ \
|
||||
&& chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.local/
|
||||
|
||||
COPY --chown=${USERNAME}:${USERNAME} \
|
||||
dockerfiles/assets/scripts/onCreate.sh \
|
||||
dockerfiles/assets/scripts/postAttach.sh \
|
||||
/home/${USERNAME}/.local/bin/
|
||||
|
||||
USER ${USERNAME}
|
21
drizzle.config.ts
Normal file
21
drizzle.config.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import dotenv from "dotenv";
|
||||
import dotenvExpand from "dotenv-expand";
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
dotenvExpand.expand(
|
||||
dotenv.config({
|
||||
path: [".env" , ".env.local", ".env.development", ".env.development.local"],
|
||||
override: true
|
||||
})
|
||||
);
|
||||
|
||||
export default defineConfig({
|
||||
out: "./drizzle",
|
||||
schema: "./src/lib/server/schema.ts",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
// biome-ignore lint/style/noNonNullAssertion: We're inside a config, crashing is fine
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
casing: "snake_case",
|
||||
});
|
15
drizzle/0000_users-and-sessions.sql
Normal file
15
drizzle/0000_users-and-sessions.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE "sessions" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "sessions_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||
"user_id" integer,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||
"username" varchar(128) NOT NULL,
|
||||
"password_hash" text,
|
||||
"display_name" text,
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
131
drizzle/meta/0000_snapshot.json
Normal file
131
drizzle/meta/0000_snapshot.json
Normal file
@ -0,0 +1,131 @@
|
||||
{
|
||||
"id": "f22311a8-ce92-4bd5-9a7c-5e8085af13a9",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "sessions_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sessions_user_id_users_id_fk": {
|
||||
"name": "sessions_user_id_users_id_fk",
|
||||
"tableFrom": "sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "users_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1738177092891,
|
||||
"tag": "0000_users-and-sessions",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
8
index.js
Normal file
8
index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { loadEnv } from 'vite';
|
||||
|
||||
console.log(process.env);
|
||||
|
||||
const env = loadEnv("development", ".", "");
|
||||
|
||||
// console.log(env);
|
||||
// console.log(process.env)
|
8
justfile
8
justfile
@ -1,8 +0,0 @@
|
||||
watch:
|
||||
docker compose up --watch
|
||||
|
||||
migrate:
|
||||
uv run ./manage.py migrate
|
||||
|
||||
bootstrap:
|
||||
uv run ./manage.py bootstrap
|
@ -1,16 +0,0 @@
|
||||
"""
|
||||
ASGI config for kredens project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kredens.settings')
|
||||
|
||||
application = get_asgi_application()
|
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class KredensAuthConfig(AppConfig):
|
||||
name = "kredens.auth"
|
||||
label = "kredens_auth"
|
@ -1,14 +0,0 @@
|
||||
import os
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create a superuser with a preset password"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
get_user_model().objects.create_superuser(
|
||||
username=os.environ["SUPERUSER_NAME"],
|
||||
email=os.environ["SUPERUSER_EMAIL"],
|
||||
password=os.environ["SUPERUSER_PASSWORD"]
|
||||
)
|
@ -1,132 +0,0 @@
|
||||
"""
|
||||
Django settings for kredens project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.1.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import dj_database_url
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure-n+yg%(i+kmm6yf=ab5)f1qv)-kzn$*!v&f6(5rtczz52!rer$&"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"kredens.auth",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"https://localhost"
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "kredens.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "kredens.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": dj_database_url.config(
|
||||
default=f"postgres://postgres:{os.environ.get("DATABASE_PASSWORD")}@localhost/kredens",
|
||||
conn_max_age=600,
|
||||
conn_health_checks=True,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
@ -1,22 +0,0 @@
|
||||
"""
|
||||
URL configuration for kredens project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
"""
|
||||
WSGI config for kredens project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kredens.settings')
|
||||
|
||||
application = get_wsgi_application()
|
22
manage.py
22
manage.py
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kredens.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
43
package.json
Normal file
43
package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "kredens",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dcUp": "devcontainer up --workspace-folder .",
|
||||
"dcAttach": "devcontainer exec --workspace-folder . /bin/bash -",
|
||||
"dcBuild": "devcontainer build --config .devcontainer/build/devcontainer.json --workspace-folder . --image-name fullspectrumconfidence.modzero.xyz/modzero/kredens-devcontainer",
|
||||
"dcPush": "npm run dcBuild -- --push",
|
||||
"dev": "vite dev --host --port 5173",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --host --port 5173",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/kit": "^2.16.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/pg": "^8.11.11",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-expand": "^12.0.1",
|
||||
"drizzle-kit": "^0.30.3",
|
||||
"svelte": "^5.19.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.11",
|
||||
"vitest": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devcontainers/cli": "^0.73.0",
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"drizzle-orm": "^0.39.0",
|
||||
"pg": "^8.13.1"
|
||||
}
|
||||
}
|
2653
pnpm-lock.yaml
generated
Normal file
2653
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
[project]
|
||||
name = "kredens"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"dj-database-url>=2.3.0",
|
||||
"django>=5.1.5",
|
||||
"psycopg[binary]>=3.2.4",
|
||||
]
|
18
src/app.d.ts
vendored
Normal file
18
src/app.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
|
||||
import type { Session } from "$lib/server/session";
|
||||
import type { User } from "$lib/server/user";
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
user: User | null;
|
||||
session: Session | null;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
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, expect, it } from "vitest";
|
||||
|
||||
describe("sum test", () => {
|
||||
it("adds 1 + 2 to equal 3", () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
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.ts
Normal file
10
src/lib/server/db.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { DATABASE_PASSWORD } from "$env/static/private";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
|
||||
import * as schema from "./schema";
|
||||
|
||||
export const db = drizzle({
|
||||
connection: `postgres://postgres:${DATABASE_PASSWORD}@database/kredens`,
|
||||
casing: "snake_case",
|
||||
schema,
|
||||
});
|
12
src/lib/server/password.ts
Normal file
12
src/lib/server/password.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { hash, verify } from "@node-rs/argon2";
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return await hash(password);
|
||||
}
|
||||
|
||||
export async function verifyPasswordHash(
|
||||
hash: string,
|
||||
password: string,
|
||||
): Promise<boolean> {
|
||||
return await verify(hash, password);
|
||||
}
|
155
src/lib/server/rate-limit.ts
Normal file
155
src/lib/server/rate-limit.ts
Normal file
@ -0,0 +1,155 @@
|
||||
// Source: https://github.com/lucia-auth/example-sveltekit-email-password-2fa/blob/7f9f22d48fb058e4085a11c52528e781c4bc70df/src/lib/server/rate-limit.ts
|
||||
export class RefillingTokenBucket<_Key> {
|
||||
public max: number;
|
||||
public refillIntervalSeconds: number;
|
||||
|
||||
constructor(max: number, refillIntervalSeconds: number) {
|
||||
this.max = max;
|
||||
this.refillIntervalSeconds = refillIntervalSeconds;
|
||||
}
|
||||
|
||||
private storage = new Map<_Key, RefillBucket>();
|
||||
|
||||
public check(key: _Key, cost: number): boolean {
|
||||
const bucket = this.storage.get(key) ?? null;
|
||||
if (bucket === null) {
|
||||
return true;
|
||||
}
|
||||
const now = Date.now();
|
||||
const refill = Math.floor(
|
||||
(now - bucket.refilledAt) / (this.refillIntervalSeconds * 1000),
|
||||
);
|
||||
if (refill > 0) {
|
||||
return Math.min(bucket.count + refill, this.max) >= cost;
|
||||
}
|
||||
return bucket.count >= cost;
|
||||
}
|
||||
|
||||
public consume(key: _Key, cost: number): boolean {
|
||||
let bucket = this.storage.get(key) ?? null;
|
||||
const now = Date.now();
|
||||
if (bucket === null) {
|
||||
bucket = {
|
||||
count: this.max - cost,
|
||||
refilledAt: now,
|
||||
};
|
||||
this.storage.set(key, bucket);
|
||||
return true;
|
||||
}
|
||||
const refill = Math.floor(
|
||||
(now - bucket.refilledAt) / (this.refillIntervalSeconds * 1000),
|
||||
);
|
||||
bucket.count = Math.min(bucket.count + refill, this.max);
|
||||
bucket.refilledAt = now;
|
||||
if (bucket.count < cost) {
|
||||
return false;
|
||||
}
|
||||
bucket.count -= cost;
|
||||
this.storage.set(key, bucket);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Throttler<_Key> {
|
||||
public timeoutSeconds: number[];
|
||||
|
||||
private storage = new Map<_Key, ThrottlingCounter>();
|
||||
|
||||
constructor(timeoutSeconds: number[]) {
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
}
|
||||
|
||||
public consume(key: _Key): boolean {
|
||||
let counter = this.storage.get(key) ?? null;
|
||||
const now = Date.now();
|
||||
if (counter === null) {
|
||||
counter = {
|
||||
timeout: 0,
|
||||
updatedAt: now,
|
||||
};
|
||||
this.storage.set(key, counter);
|
||||
return true;
|
||||
}
|
||||
const allowed =
|
||||
now - counter.updatedAt >= this.timeoutSeconds[counter.timeout] * 1000;
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
counter.updatedAt = now;
|
||||
counter.timeout = Math.min(
|
||||
counter.timeout + 1,
|
||||
this.timeoutSeconds.length - 1,
|
||||
);
|
||||
this.storage.set(key, counter);
|
||||
return true;
|
||||
}
|
||||
|
||||
public reset(key: _Key): void {
|
||||
this.storage.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpiringTokenBucket<_Key> {
|
||||
public max: number;
|
||||
public expiresInSeconds: number;
|
||||
|
||||
private storage = new Map<_Key, ExpiringBucket>();
|
||||
|
||||
constructor(max: number, expiresInSeconds: number) {
|
||||
this.max = max;
|
||||
this.expiresInSeconds = expiresInSeconds;
|
||||
}
|
||||
|
||||
public check(key: _Key, cost: number): boolean {
|
||||
const bucket = this.storage.get(key) ?? null;
|
||||
const now = Date.now();
|
||||
if (bucket === null) {
|
||||
return true;
|
||||
}
|
||||
if (now - bucket.createdAt >= this.expiresInSeconds * 1000) {
|
||||
return true;
|
||||
}
|
||||
return bucket.count >= cost;
|
||||
}
|
||||
|
||||
public consume(key: _Key, cost: number): boolean {
|
||||
let bucket = this.storage.get(key) ?? null;
|
||||
const now = Date.now();
|
||||
if (bucket === null) {
|
||||
bucket = {
|
||||
count: this.max - cost,
|
||||
createdAt: now,
|
||||
};
|
||||
this.storage.set(key, bucket);
|
||||
return true;
|
||||
}
|
||||
if (now - bucket.createdAt >= this.expiresInSeconds * 1000) {
|
||||
bucket.count = this.max;
|
||||
}
|
||||
if (bucket.count < cost) {
|
||||
return false;
|
||||
}
|
||||
bucket.count -= cost;
|
||||
this.storage.set(key, bucket);
|
||||
return true;
|
||||
}
|
||||
|
||||
public reset(key: _Key): void {
|
||||
this.storage.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
interface RefillBucket {
|
||||
count: number;
|
||||
refilledAt: number;
|
||||
}
|
||||
|
||||
interface ExpiringBucket {
|
||||
count: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
interface ThrottlingCounter {
|
||||
timeout: number;
|
||||
updatedAt: number;
|
||||
}
|
15
src/lib/server/schema.ts
Normal file
15
src/lib/server/schema.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { integer, pgTable, varchar, text, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
username: varchar({length: 128, }).unique().notNull(),
|
||||
password_hash: text(),
|
||||
displayName: text(),
|
||||
});
|
||||
|
||||
export const sessions = pgTable("sessions", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
userId: integer().references(() => users.id),
|
||||
expiresAt: timestamp({withTimezone: true}).notNull(),
|
||||
});
|
||||
|
5
src/lib/server/session.ts
Normal file
5
src/lib/server/session.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Session {
|
||||
id: string;
|
||||
expiresAt: Date;
|
||||
userId: number;
|
||||
}
|
5
src/lib/server/user.ts
Normal file
5
src/lib/server/user.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
displayName: string;
|
||||
}
|
12
src/routes/+page.server.ts
Normal file
12
src/routes/+page.server.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { count } from "drizzle-orm";
|
||||
|
||||
import { db } from "$lib/server/db";
|
||||
import { users } from "$lib/server/schema";
|
||||
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
c: (await db.select({ value: count() }).from(users))[0]
|
||||
};
|
||||
};
|
10
src/routes/+page.svelte
Normal file
10
src/routes/+page.svelte
Normal file
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
const { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
|
||||
<p>There is { data.c.value } users!</p>
|
18
src/routes/login/+page.server.ts
Normal file
18
src/routes/login/+page.server.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { RefillingTokenBucket, Throttler } from "$lib/server/rate-limit";
|
||||
|
||||
import type { Actions, PageServerLoad, PageServerLoadEvent } from "./$types";
|
||||
|
||||
const throttler = new Throttler<number>([0, 1, 2, 4, 8, 16, 30, 60, 180, 300]);
|
||||
const ipBucket = new RefillingTokenBucket<string>(20, 1);
|
||||
|
||||
export const load: PageServerLoad = (event: PageServerLoadEvent) => {
|
||||
if (event.locals.session !== null && event.locals.user !== null) {
|
||||
return redirect(302, "/");
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
default: async (event) => {},
|
||||
} satisfies Actions;
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
18
svelte.config.js
Normal file
18
svelte.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
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: {
|
||||
// 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;
|
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
|
||||
}
|
117
uv.lock
generated
117
uv.lock
generated
@ -1,117 +0,0 @@
|
||||
version = 1
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dj-database-url"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/9f/fc9905758256af4f68a55da94ab78a13e7775074edfdcaddd757d4921686/dj_database_url-2.3.0.tar.gz", hash = "sha256:ae52e8e634186b57e5a45e445da5dc407a819c2ceed8a53d1fac004cc5288787", size = 10980 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/91/641a4e5c8903ed59f6cbcce571003bba9c5d2f731759c31db0ba83bb0bdb/dj_database_url-2.3.0-py3-none-any.whl", hash = "sha256:bb0d414ba0ac5cd62773ec7f86f8cc378a9dbb00a80884c2fc08cc570452521e", size = 7793 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/17/834e3e08d590dcc27d4cc3c5cd4e2fb757b7a92bab9de8ee402455732952/Django-5.1.5.tar.gz", hash = "sha256:19bbca786df50b9eca23cee79d495facf55c8f5c54c529d9bf1fe7b5ea086af3", size = 10700031 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/e6/e92c8c788b83d109f34d933c5e817095d85722719cb4483472abc135f44e/Django-5.1.5-py3-none-any.whl", hash = "sha256:c46eb936111fffe6ec4bc9930035524a8be98ec2f74d8a0ff351226a3e52f459", size = 8276957 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kredens"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "dj-database-url" },
|
||||
{ name = "django" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "dj-database-url", specifier = ">=2.3.0" },
|
||||
{ name = "django", specifier = ">=5.1.5" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.4" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/f2/954b1467b3e2ca5945b83b5e320268be1f4df486c3e8ffc90f4e4b707979/psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92", size = 156109 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/40/49/15114d5f7ee68983f4e1a24d47e75334568960352a07c6f0e796e912685d/psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381", size = 198716 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
binary = [
|
||||
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.2.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/e2/f56675aada063762f08559b6969e47e1313f269fc1682c16457c13da8186/psycopg_binary-3.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:81ab801c0d35830c876bf0d1edc8e7dd2f73aa2b04fe24eb812159c0b054d149", size = 3846854 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/8b/8c4a66b2b3db494367df0299535b7d2df78f303334228c517b8d00c411d5/psycopg_binary-3.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c09e02ce1124eb6638b3381df050a8cf88aedfad4522f939945cda49050a990c", size = 3932292 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/e8/618d45f77cebce73d75497c95685a0902aea3783386d9335ce486c69e13a/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a249cdc6a5c2b5088a8677acba66b291e5237524739ab3d27498e1ef189312f5", size = 4493785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/87/fc30318e6b97e723e017e7dc88d0f721bbfb749de1a6e414e52d4ac54c9a/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2960ba8a5c0ad75e184f6d8bf76bdf023708999efe75fe4e13445136c1cd206", size = 4304874 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/30/1d127e651c21cd77befaf361c7c3b9001bfff51ac38027e8fce598ba0701/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dae2e50b0d3425c167eebbedc3553f7c811dbc0dbfc737b6877f68a03be7daf", size = 4541296 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/5e/22c824cb38745c1c744cec85d227190727c564afb75960ce0057ca15fd84/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bf7ee7e0002c2cce43ecb923ec510358056eb2e44a96afaeb0424518f35206", size = 4255756 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/83/ae8783dec3f7e39df8a4056e4d383926ffec531970c0b415d48d9fd4a2c2/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f5c85eeb63b1a8a6b026eef57f5da36ff215ce9a6a3bb8e20a409670d6cfbda", size = 3845918 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/f7/fb7bffb0c4c45a5a82fe324e4f7b176075a4c5372e546a038858dd13c7ab/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8c7b95899d4d6d23c5cc46cb3419e8e6ca68d867509432ee1487042564a1ea55", size = 3315429 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/a3/29f4993a239d6a3fb18ef8681d9990c007f5f73bdd2e21f65f07ac55ad6f/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fa4acea9ca20a567c3872a5afab2084751530bb57b8fb6b52820d5c54e7c8c3b", size = 3399388 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/5b/925171cbfa2e3d1ccb7f4c005d0d5db609ba796c1d08a23c42825b09c554/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c487f35a1905bb15da927c1fc05f70f3d29f0e21fb4ba21d360a0da9c755f20", size = 3436702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/47/25b2b85b8fcabf99bfa92b4b0d587894c01576bf0b2bf137c243d1eb1070/psycopg_binary-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:80297c3a9f7b5a6afdb0d8f220661ccd796e5c9128c44b32c41267f7daefd37f", size = 2779196 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
|
||||
]
|
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
|
||||
test: {
|
||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||
},
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user