Compare commits

...

9 Commits

Author SHA1 Message Date
Sandra Modzelewska
32281aa1ff
Just working with a DB and stuff 2025-01-29 18:59:31 +00:00
Sandra Modzelewska
b8c555d8e7
Add biome and postres 2025-01-25 21:07:06 +00:00
Sandra Modzelewska
7fed360ab2
Some dev container changes 2025-01-25 20:05:05 +00:00
Sandra Modzelewska
33f22652d6
Fix some configs 2025-01-25 08:37:56 +00:00
Sandra Modzelewska
1f6e2d2ee1
Use the docker compose thing after all 2025-01-25 08:17:56 +00:00
Sandra Modzelewska
a0890bb764
Add SvelteKit 2025-01-25 07:50:17 +00:00
Sandra Modzelewska
6c2136db0e
Remove python files 2025-01-25 07:29:28 +00:00
Sandra Modzelewska
c1a11ebf4c
Drop the venv mount 2025-01-25 07:29:02 +00:00
Sandra Modzelewska
6adea0efbb
Drop Python, more efficient dev container build, doutd 2025-01-25 07:25:12 +00:00
59 changed files with 3429 additions and 532 deletions

View 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
View 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"
}
}

View 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

View File

@ -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

View File

@ -1,4 +1,5 @@
.git
node_modules
.venv
__pycache__
.pnpm-store
.DS_Store
Thumbs.db

5
.env Normal file
View 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
View File

@ -0,0 +1,2 @@
DATABASE_USER=postgres
DATABASE_HOST=database

23
.gitignore vendored
View File

@ -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
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

View File

@ -1 +0,0 @@
3.13

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"sqltools.connections": [
{
"previewLimit": 50,
"server": "database",
"port": 5432,
"driver": "PostgreSQL",
"name": "Dev Container Backend",
"database": "kredens",
"username": "postgres"
}
]
}

View File

@ -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
View 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"
}
}
}

View File

@ -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:

View File

@ -10,7 +10,7 @@ localhost {
}
handle {
reverse_proxy backend:8000 {
reverse_proxy dev:5173 {
header_up Host {host}
}
}

View 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

View File

@ -4,5 +4,3 @@ sudo apt update -y
sudo apt upgrade -y
cd ${WORKSPACE_FOLDER}
uv sync --frozen

View File

@ -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"]

View File

@ -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
View 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",
});

View 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;

View 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": {}
}
}

View 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
View File

@ -0,0 +1,8 @@
import { loadEnv } from 'vite';
console.log(process.env);
const env = loadEnv("development", ".", "");
// console.log(env);
// console.log(process.env)

View File

@ -1,8 +0,0 @@
watch:
docker compose up --watch
migrate:
uv run ./manage.py migrate
bootstrap:
uv run ./manage.py bootstrap

View File

@ -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()

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class KredensAuthConfig(AppConfig):
name = "kredens.auth"
label = "kredens_auth"

View File

@ -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"]
)

View File

@ -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"

View File

@ -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),
]

View File

@ -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()

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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
View 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
View 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
View 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,
});

View 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);
}

View 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
View 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(),
});

View File

@ -0,0 +1,5 @@
export interface Session {
id: string;
expiresAt: Date;
userId: number;
}

5
src/lib/server/user.ts Normal file
View File

@ -0,0 +1,5 @@
export interface User {
id: number;
username: string;
displayName: string;
}

View 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
View 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>

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

18
svelte.config.js Normal file
View 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
View 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
View File

@ -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
View 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}"],
},
});