Inital commit

This commit is contained in:
Leon Grünewald 2025-02-05 05:20:49 +01:00
commit 5f12ccfa34
32 changed files with 2708 additions and 0 deletions

23
.gitignore vendored Normal file
View 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-*

1
.npmrc Normal file
View file

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

38
README.md Normal file
View file

@ -0,0 +1,38 @@
# sv
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!
```bash
# 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 `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
```
## 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.

18
docker-compose.yml Normal file
View file

@ -0,0 +1,18 @@
services:
caddy:
image: caddy:latest
volumes:
- "./docker/Caddyfile:/etc/caddy/Caddyfile"
ports:
- "8080:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
pocketbase:
image: ghcr.io/muchobien/pocketbase:latest
ports:
- "8090:8090"
healthcheck: # optional, recommended since v0.10.0
test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
interval: 5s
timeout: 5s
retries: 5

25
docker/Caddyfile Normal file
View file

@ -0,0 +1,25 @@
# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.
http://fedora.raccoon-nase.ts.net:8080 {
# Set this path to your site's directory.
root * /usr/share/caddy
# Another common task is to set up a reverse proxy:
reverse_proxy http://host.docker.internal:5173
reverse_proxy /api/* http://pocketbase:8090
reverse_proxy /_/* http://pocketbase:8090
# Or serve a PHP site through php-fpm:
# php_fastcgi localhost:9000
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

View file

@ -0,0 +1,988 @@
[
{
"id": "pbc_3142635823",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "_superusers",
"type": "auth",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cost": 0,
"hidden": true,
"id": "password901924565",
"max": 0,
"min": 8,
"name": "password",
"pattern": "",
"presentable": false,
"required": true,
"system": true,
"type": "password"
},
{
"autogeneratePattern": "[a-zA-Z0-9]{50}",
"hidden": true,
"id": "text2504183744",
"max": 60,
"min": 30,
"name": "tokenKey",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"exceptDomains": null,
"hidden": false,
"id": "email3885137012",
"name": "email",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": true,
"type": "email"
},
{
"hidden": false,
"id": "bool1547992806",
"name": "emailVisibility",
"presentable": false,
"required": false,
"system": true,
"type": "bool"
},
{
"hidden": false,
"id": "bool256245529",
"name": "verified",
"presentable": false,
"required": false,
"system": true,
"type": "bool"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey_pbc_3142635823` ON `_superusers` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email_pbc_3142635823` ON `_superusers` (`email`) WHERE `email` != ''"
],
"system": true,
"authRule": "",
"manageRule": null,
"authAlert": {
"enabled": true,
"emailTemplate": {
"subject": "Login from a new location",
"body": "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
"oauth2": {
"mappedFields": {
"id": "",
"name": "",
"username": "",
"avatarURL": ""
},
"enabled": false
},
"passwordAuth": {
"enabled": true,
"identityFields": [
"email"
]
},
"mfa": {
"enabled": false,
"duration": 1800,
"rule": ""
},
"otp": {
"enabled": false,
"duration": 180,
"length": 8,
"emailTemplate": {
"subject": "OTP for {APP_NAME}",
"body": "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
"authToken": {
"duration": 86400
},
"passwordResetToken": {
"duration": 1800
},
"emailChangeToken": {
"duration": 1800
},
"verificationToken": {
"duration": 259200
},
"fileToken": {
"duration": 180
},
"verificationTemplate": {
"subject": "Verify your {APP_NAME} email",
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
},
"resetPasswordTemplate": {
"subject": "Reset your {APP_NAME} password",
"body": "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
},
"confirmEmailChangeTemplate": {
"subject": "Confirm your {APP_NAME} new email address",
"body": "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
{
"id": "_pb_users_auth_",
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"name": "users",
"type": "auth",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cost": 0,
"hidden": true,
"id": "password901924565",
"max": 0,
"min": 8,
"name": "password",
"pattern": "",
"presentable": false,
"required": true,
"system": true,
"type": "password"
},
{
"autogeneratePattern": "[a-zA-Z0-9]{50}",
"hidden": true,
"id": "text2504183744",
"max": 60,
"min": 30,
"name": "tokenKey",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"exceptDomains": null,
"hidden": false,
"id": "email3885137012",
"name": "email",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": true,
"type": "email"
},
{
"hidden": false,
"id": "bool1547992806",
"name": "emailVisibility",
"presentable": false,
"required": false,
"system": true,
"type": "bool"
},
{
"hidden": false,
"id": "bool256245529",
"name": "verified",
"presentable": false,
"required": false,
"system": true,
"type": "bool"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 255,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "file376926767",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"name": "avatar",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": null,
"type": "file"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
],
"system": false,
"authRule": "",
"manageRule": null,
"authAlert": {
"enabled": true,
"emailTemplate": {
"subject": "Login from a new location",
"body": "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
"oauth2": {
"mappedFields": {
"id": "",
"name": "name",
"username": "",
"avatarURL": "avatar"
},
"enabled": false
},
"passwordAuth": {
"enabled": true,
"identityFields": [
"email"
]
},
"mfa": {
"enabled": false,
"duration": 1800,
"rule": ""
},
"otp": {
"enabled": false,
"duration": 180,
"length": 8,
"emailTemplate": {
"subject": "OTP for {APP_NAME}",
"body": "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
"authToken": {
"duration": 604800
},
"passwordResetToken": {
"duration": 1800
},
"emailChangeToken": {
"duration": 1800
},
"verificationToken": {
"duration": 259200
},
"fileToken": {
"duration": 180
},
"verificationTemplate": {
"subject": "Verify your {APP_NAME} email",
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
},
"resetPasswordTemplate": {
"subject": "Reset your {APP_NAME} password",
"body": "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
},
"confirmEmailChangeTemplate": {
"subject": "Confirm your {APP_NAME} new email address",
"body": "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>"
}
},
{
"id": "pbc_4275539003",
"listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"createRule": null,
"updateRule": null,
"deleteRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"name": "_authOrigins",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text455797646",
"max": 0,
"min": 0,
"name": "collectionRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text127846527",
"max": 0,
"min": 0,
"name": "recordRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text4228609354",
"max": 0,
"min": 0,
"name": "fingerprint",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_authOrigins_unique_pairs` ON `_authOrigins` (collectionRef, recordRef, fingerprint)"
],
"system": true
},
{
"id": "pbc_2281828961",
"listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"createRule": null,
"updateRule": null,
"deleteRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"name": "_externalAuths",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text455797646",
"max": 0,
"min": 0,
"name": "collectionRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text127846527",
"max": 0,
"min": 0,
"name": "recordRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2462348188",
"max": 0,
"min": 0,
"name": "provider",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1044722854",
"max": 0,
"min": 0,
"name": "providerId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_externalAuths_record_provider` ON `_externalAuths` (collectionRef, recordRef, provider)",
"CREATE UNIQUE INDEX `idx_externalAuths_collection_provider` ON `_externalAuths` (collectionRef, provider, providerId)"
],
"system": true
},
{
"id": "pbc_2279338944",
"listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "_mfas",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text455797646",
"max": 0,
"min": 0,
"name": "collectionRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text127846527",
"max": 0,
"min": 0,
"name": "recordRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1582905952",
"max": 0,
"min": 0,
"name": "method",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
}
],
"indexes": [
"CREATE INDEX `idx_mfas_collectionRef_recordRef` ON `_mfas` (collectionRef,recordRef)"
],
"system": true
},
{
"id": "pbc_1638494021",
"listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "_otps",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text455797646",
"max": 0,
"min": 0,
"name": "collectionRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text127846527",
"max": 0,
"min": 0,
"name": "recordRef",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": true,
"type": "text"
},
{
"cost": 8,
"hidden": true,
"id": "password901924565",
"max": 0,
"min": 0,
"name": "password",
"pattern": "",
"presentable": false,
"required": true,
"system": true,
"type": "password"
},
{
"autogeneratePattern": "",
"hidden": true,
"id": "text3866985172",
"max": 0,
"min": 0,
"name": "sentTo",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": true,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": true,
"type": "autodate"
}
],
"indexes": [
"CREATE INDEX `idx_otps_collectionRef_recordRef` ON `_otps` (collectionRef, recordRef)"
],
"system": true
},
{
"id": "pbc_1687431684",
"listRule": "",
"viewRule": "",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "events",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text724990059",
"max": 0,
"min": 0,
"name": "title",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"convertURLs": false,
"hidden": false,
"id": "editor1843675174",
"maxSize": 0,
"name": "description",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2560465762",
"max": 0,
"min": 0,
"name": "slug",
"pattern": "^[a-zA-Z0-9\\-]+$",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1942858786",
"hidden": false,
"id": "relation1587448267",
"maxSelect": 1,
"minSelect": 0,
"name": "location",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"indexes": [],
"system": false
},
{
"id": "pbc_1942858786",
"listRule": null,
"viewRule": "",
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "locations",
"type": "base",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text223244161",
"max": 0,
"min": 0,
"name": "address",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number2499937429",
"max": null,
"min": null,
"name": "lat",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number2518964612",
"max": null,
"min": null,
"name": "lng",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3073079668",
"max": 20,
"min": 1,
"name": "zoom",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"indexes": [],
"system": false
}
]

19
jsconfig.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
}

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "cologne-furmeet-reg",
"private": true,
"version": "0.0.1",
"type": "module",
"packageManager": "pnpm@10.0.0",
"scripts": {
"dev": "vite dev --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.0.0"
},
"dependencies": {
"jdenticon": "^3.3.0",
"ol": "^10.4.0",
"pocketbase": "^0.25.1"
}
}

1113
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

60
src/app.css Normal file
View file

@ -0,0 +1,60 @@
/* inter-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url('/fonts/inter-v18-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
* {
--color-primary-a0: #ffffff;
--color-primary-a10: #e1e6fe;
--color-primary-a20: #c2cdfd;
--color-primary-a30: #a1b5fb;
--color-primary-a40: #7d9df9;
--color-primary-a50: #5087f6;
--color-primary-a60: #436bc0;
--color-primary-a70: #364f8c;
--color-primary-a80: #28365c;
--color-primary-a90: #191e2f;
--color-primary-a100: #000000;
--text-color: black;
box-sizing: border-box;
font-size: inherit;
}
html, body {
position: relative;
margin: 0;
padding: 0;
background-color: white;
font-size: 1rem;
}
p, a, h1, h2, h3, h4, h5, h6 {
font-family: Inter, Arial, sans-serif;
color: var(--text-color);
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
}
p {
line-height: 1.35rem;
}
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.2rem;
}
svg {
max-width: 100%;
height: auto;
}

13
src/app.d.ts vendored Normal file
View 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 {};

13
src/app.html Normal file
View file

@ -0,0 +1,13 @@
<!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" />
<script src="https://telegram.org/js/telegram-web-app.js?56"></script>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

5
src/hooks.client.js Normal file
View file

@ -0,0 +1,5 @@
import {initializePocketBase} from "$lib/pocketbase.js";
export const init = async () => {
initializePocketBase()
}

5
src/hooks.server.js Normal file
View file

@ -0,0 +1,5 @@
import {initializePocketBase} from "$lib/pocketbase.js";
export const init = async () => {
initializePocketBase()
}

View file

@ -0,0 +1,36 @@
<script>
const {children, footer} = $props();
</script>
<div class="container-grid">
<main>
{@render children()}
</main>
<footer>
{@render footer()}
</footer>
</div>
<style>
.container-grid {
display: grid;
height: 100vh;
grid-template-columns: [content-full-start] 2rem [content-start] 1fr [content-end] 2rem [content-full-end];
grid-template-rows: [content-header] 1fr [content-footer-start] min-content [content-footer-end];
}
main {
padding-top: 1rem;
grid-column: content;
}
footer {
bottom: 0;
width: 100%;
position: sticky;
grid-column: content;
grid-row: content-footer;
background-color: white;
}
</style>

View file

@ -0,0 +1,13 @@
<script>
const {children} = $props();
</script>
<div class="content-grid">
{@render children()}
</div>
<style>
.content-grid {
grid-column: content;
}
</style>

View file

@ -0,0 +1,27 @@
<script>
const {events} = $props();
</script>
<div class="event-list">
{#if events}
{#each events as event}
<div class="event">
<h2>{event?.title ?? "No title"}</h2>
<p class="event-description">{@html event?.description}</p>
<p><a href={"/events/" + event.slug}>Details</a></p>
</div>
{/each}
{:else}
<p>No events</p>
{/if}
</div>
<style>
.event {
padding: 0.5rem 1rem;
border: 2px solid #00000055;
border-radius: 3%;
}
</style>

View file

@ -0,0 +1,43 @@
<script>
import { page } from '$app/state';
</script>
<nav>
<ul>
<li class:selected={page.url.pathname === "/"}>
<a href="/" >Start</a>
</li>
<li class:selected={page.url.pathname === "/events"}>
<a href="/events">Events</a>
</li>
<li class:selected={page.url.pathname === "/rules"}>
<a href="/rules">Regeln</a>
</li>
</ul>
</nav>
<style>
ul {
display: flex;
justify-content: center;
padding: 0;
margin: 0;
list-style: none;
}
li {
transition: border-bottom-color linear 0.2s;
padding: 0.6rem 0.45rem;
border-bottom: 3px var(--color-primary-a80) solid;
&.selected {
border-bottom: 3px var(--color-primary-a40) solid;
}
}
a {
font-size: 1.5rem;
text-decoration: none;
}
</style>

View file

@ -0,0 +1,32 @@
<script>
const {src, alt = "Profile image"} = $props();
import jdenticon from "jdenticon/standalone";
</script>
<div class="rounded-avatar-wrapper">
{#if src}
<img class="rounded-avatar" {src} {alt} />
{:else}
{@const randomAvatar = jdenticon.toSvg(Navigator.userAgent, 320, { padding: 0 })}
<div class="rounded-avatar">
{@html randomAvatar}
</div>
{/if}
</div>
<style>
.rounded-avatar-wrapper {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
}
.rounded-avatar {
overflow: hidden;
border-radius: 100%;
aspect-ratio: 1 / 1;
}
</style>

1
src/lib/index.js Normal file
View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

13
src/lib/pocketbase.js Normal file
View file

@ -0,0 +1,13 @@
import PocketBase from 'pocketbase';
import {PUBLIC_POCKETBASE_URL} from "$env/static/public";
let pocketbase;
export function initializePocketBase() {
pocketbase = new PocketBase(PUBLIC_POCKETBASE_URL);
return pocketbase;
}
export function usePocketBase() {
return pocketbase;
}

27
src/routes/+layout.svelte Normal file
View file

@ -0,0 +1,27 @@
<script>
import '../app.css';
import FooterNavigation from "$lib/components/FooterNavigation.svelte";
import ContainerGridSingle from "$lib/components/ContainerGridSingle.svelte";
const { children } = $props();
$effect(() => {
Telegram?.WebApp?.ready();
Telegram?.WebApp?.setBackgroundColor('FFFFFF');
});
</script>
<ContainerGridSingle>
{@render children()}
{#snippet footer()}
<div class="footer-inner">
<FooterNavigation></FooterNavigation>
</div>
{/snippet}
</ContainerGridSingle>
<style>
.footer-inner {
margin-top: 1rem;
}
</style>

25
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,25 @@
<script>
import RoundedAvatar from "$lib/components/RoundedAvatar.svelte";
let user = $state(null);
$effect(() => {
const searchParams = new URLSearchParams(Telegram?.WebApp?.initData);
const tempUser = searchParams.get('user');
if (!tempUser) {
console.warn("User could not be loaded");
return;
}
user = JSON.parse(tempUser);
})
</script>
<section id="intro">
{#if user}
<RoundedAvatar src={user?.photo_url}></RoundedAvatar>
<h2>Hallo {user.first_name}!</h2>
{:else}
<RoundedAvatar src={null}></RoundedAvatar>
<h2>Hallo Besucher!</h2>
{/if}
<p>Du befinest dich auf der Anmeldeseite des Kölner Stammtischs. Schau dir die unteren Menüpunkte an, falls du mehr erfahren möchtest.</p>
</section>

View file

@ -0,0 +1,10 @@
import PocketBase from "pocketbase";
import {PUBLIC_POCKETBASE_URL} from "$env/static/public";
export const load = async ({ fetch }) => {
const pb = new PocketBase(PUBLIC_POCKETBASE_URL);
return {
eventCollection: await pb.collection("events").getFullList({fetch})
};
}

View file

@ -0,0 +1,8 @@
<script>
import EventList from "$lib/components/EventList.svelte";
const {data} = $props();
const {eventCollection} = data;
</script>
<h1>Events</h1>
<EventList events={eventCollection}></EventList>

View file

@ -0,0 +1,19 @@
import PocketBase from "pocketbase";
import { PUBLIC_POCKETBASE_URL } from "$env/static/public";
import { error } from '@sveltejs/kit'
export const load = async ({ params, fetch }) => {
const pb = new PocketBase(PUBLIC_POCKETBASE_URL);
let event = null;
try {
event = await pb.collection("events").getFirstListItem("slug = '" + params.slug + "'", {fetch, expand: "location"});
} catch (e) {
throw error(404, {
message: "Not found"
});
}
return {
event
};
}

View file

@ -0,0 +1,71 @@
<script>
import '../../../../node_modules/ol/ol.css';
import {Map, View} from 'ol';
import {OSM} from 'ol/source';
import {Tile} from 'ol/layer';
import {useGeographic} from "ol/proj";
import {onMount} from "svelte";
const { data } = $props();
const { event } = data;
let mapRef;
onMount(() => {
let lat = event?.expand?.location?.lat ?? null,
lng = event?.expand?.location?.lng ?? null;
if (lat && lng) {
const zoom = event?.expand?.location?.zoom ?? 18;
console.log(lat, lng)
useGeographic();
new Map({
target: mapRef,
layers: [
new Tile({
source: new OSM()
})
],
view: new View({
center: [lng, lat],
zoom,
}),
})
}
})
</script>
<div class="event-detail">
<h1>{event?.title ?? "No title"}</h1>
{#if event?.description}
{@html event.description}
{:else}
<p>{event?.description ?? "No description"}</p>
{/if}
<div class="event-location-details">
<h2>Location</h2>
<p class="location-address">
{event?.expand?.location?.name}<br />
{event?.expand?.location?.address}
</p>
<div class="event-map" bind:this={mapRef}></div>
</div>
</div>
<style>
.event-map {
width: 100%;
height: 50vh;
}
.event-location-details {
padding: 0 1rem 1rem 1rem;
border: 2px solid #00000055;
border-radius: 3%;
}
.location-address {
margin-top: 0;
}
</style>

View file

@ -0,0 +1,11 @@
<script>
</script>
<iframe src="https://www.furries.cologne/stammi-regeln/"></iframe>
<style>
iframe {
height: 100%;
width: 100%;
}
</style>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

13
svelte.config.js Normal file
View file

@ -0,0 +1,13 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
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;

9
vite.config.js Normal file
View file

@ -0,0 +1,9 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
server: {
allowedHosts: ['fedora.raccoon-nase.ts.net']
}
});