diff --git a/.gitignore b/.gitignore index d89e037..aa74e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules # Output .output .vercel +.wrangler /.svelte-kit /dist /build diff --git a/package.json b/package.json index 55afb23..38ec759 100644 --- a/package.json +++ b/package.json @@ -45,5 +45,8 @@ "vite": "^5.0.3", "vitest": "^2.0.0" }, - "type": "module" + "type": "module", + "dependencies": { + "@auth/sveltekit": "^1.4.2" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcd6cc4..8b16f67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + '@auth/sveltekit': + specifier: ^1.4.2 + version: 1.4.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)))(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)))(svelte@4.2.19) devDependencies: '@fontsource/fira-mono': specifier: ^5.0.0 @@ -105,6 +109,36 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@auth/core@0.34.2': + resolution: {integrity: sha512-KywHKRgLiF3l7PLyL73fjLSIBe1YNcA6sMeew4yMP6cfCWGXZrkkXd32AjRi1hlJ9nvovUBGZHvbn+LijO6ZeQ==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@auth/sveltekit@1.4.2': + resolution: {integrity: sha512-47xHm+e0i26HwXNeG+DRYj3Dv7+qbATPvXh5thYMxEal5oWItf9DT9ohhkEwVQvrmH0QsDYvlGztegZiB1MHMA==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.3 + '@sveltejs/kit': ^1.0.0 || ^2.0.0 + nodemailer: ^6.6.5 + svelte: ^3.54.0 || ^4.0.0 || ^5 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + '@cloudflare/kv-asset-handler@0.3.4': resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} engines: {node: '>=16.13'} @@ -522,6 +556,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@playwright/test@1.46.1': resolution: {integrity: sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==} engines: {node: '>=18'} @@ -1226,6 +1263,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jose@5.8.0: + resolution: {integrity: sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1357,6 +1397,9 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + oauth4webapi@2.13.0: + resolution: {integrity: sha512-/dpLFP/JOETpd8Rg2yhSgs+D4iKAPlntXTEWIEt5sn21jXA/OEVUUnMp2MTAXk34X0GJFU6MaJHEZbrRFq/H3w==} + ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} @@ -1464,6 +1507,14 @@ packages: resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1479,6 +1530,9 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + printable-characters@1.0.42: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} @@ -1907,6 +1961,23 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@auth/core@0.34.2': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.8.0 + oauth4webapi: 2.13.0 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + + '@auth/sveltekit@1.4.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)))(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)))(svelte@4.2.19)': + dependencies: + '@auth/core': 0.34.2 + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)))(svelte@4.2.19)(vite@5.4.3(@types/node@22.5.3)) + set-cookie-parser: 2.7.0 + svelte: 4.2.19 + '@cloudflare/kv-asset-handler@0.3.4': dependencies: mime: 3.0.0 @@ -2170,6 +2241,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@panva/hkdf@1.2.1': {} + '@playwright/test@1.46.1': dependencies: playwright: 1.46.1 @@ -2931,6 +3004,8 @@ snapshots: isexe@2.0.0: {} + jose@5.8.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -3046,6 +3121,8 @@ snapshots: dependencies: path-key: 4.0.0 + oauth4webapi@2.13.0: {} + ohash@1.1.3: {} once@1.4.0: @@ -3137,6 +3214,13 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.0 + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + prelude-ls@1.2.1: {} prettier-plugin-svelte@3.2.6(prettier@3.3.3)(svelte@4.2.19): @@ -3146,6 +3230,8 @@ snapshots: prettier@3.3.3: {} + pretty-format@3.8.0: {} + printable-characters@1.0.42: {} prism-svelte@0.5.0: {} diff --git a/src/app.css b/src/app.css index 52394f6..336b1f5 100644 --- a/src/app.css +++ b/src/app.css @@ -20,6 +20,7 @@ body { width: 100vw; min-height: 100vh; margin: 0; + overflow-x: hidden; } #SVELTEUI_PROVIDER { diff --git a/src/app.d.ts b/src/app.d.ts index 743f07b..f3e9419 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -6,7 +6,15 @@ declare global { // interface Locals {} // interface PageData {} // interface PageState {} - // interface Platform {} + interface Platform { + env: { + FORMS_DB: D1Database; + }; + context: { + waitUntil(promise: Promise): void; + }; + caches: CacheStorage & { default: Cache }; + } } } diff --git a/src/app.html b/src/app.html index 77a5ff5..e36fe24 100644 --- a/src/app.html +++ b/src/app.html @@ -4,6 +4,7 @@ + RustLangES Dashboard %sveltekit.head% diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..530b12b --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,6 @@ +import { SvelteKitAuth } from "@auth/sveltekit" +import GitHub from "@auth/sveltekit/providers/github" + +export const { handle, signIn, signOut } = SvelteKitAuth({ + providers: [GitHub], +}) diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..6a02258 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,7 @@ +import { sequence } from "@sveltejs/kit/hooks"; + +import { prepareStylesSSR } from "@svelteuidev/core"; + +import { handle as authHandle } from "./auth"; + +export const handle = sequence(authHandle, prepareStylesSSR); diff --git a/src/lib/forms/models.d.ts b/src/lib/forms/models.d.ts new file mode 100644 index 0000000..fffe63e --- /dev/null +++ b/src/lib/forms/models.d.ts @@ -0,0 +1,41 @@ +export interface Form { + id: number; + title: string; + description: string; + edition: string; + + /** 0 = false, 1 = true. D1 Things */ + multiple_times: number; + + /** 0 = false, 1 = true. D1 Things */ + require_login: number; + + /** 0 = false, 1 = true. D1 Things */ + deleted: number; + + /** Created datetime in seconds */ + created_at: number; +} + +type Step = { + id: number; + title: string; + description: string; + type: Type; + data: Data; +}; + +export type Question = QuestionText | QuestionQuestionText | QuestionOptions; + +export type QuestionText = Step<'text', {}>; + +export type QuestionQuestionText = Step<'questionText', { required?: boolean }>; + +export type QuestionOptions = Step< + 'options', + { + canMultiple: boolean; + required: boolean; + options: Array; + } +>; diff --git a/src/lib/forms/service/stores/form.ts b/src/lib/forms/service/stores/form.ts new file mode 100644 index 0000000..2d9a8a2 --- /dev/null +++ b/src/lib/forms/service/stores/form.ts @@ -0,0 +1,39 @@ +import { writable } from 'svelte/store'; +import type { Form, Question } from '../../models'; + +export const form = writable
(); +export const questions = writable([]); + +export async function loadDetails( + platform: Readonly | undefined, + type_form: string +): Promise<{ form: Form; questions: Question }> { + const form_res = await platform!.env.FORMS_DB.prepare('SELECT * FROM Form WHERE id = ?') + .bind(type_form) + .run(); + + const questions_res = await platform!.env.FORMS_DB.prepare( + 'SELECT * FROM Question WHERE form_id = ?' + ) + .bind(type_form) + .run(); + + const formData = form_res.results[0]; + const questionsData = questions_res.results; + + form.set(formData); + questions.set(questionsData); + + return { form: formData, questions: questionsData }; +} + +export function receiveDetails(data: unknown) { + if (typeof data === 'object' && data !== null) { + if ('form' in data) { + form.set(data.form as Form); + } + if ('questions' in data) { + questions.set(data.questions as Question[]); + } + } +} diff --git a/src/lib/forms/service/stores/forms.ts b/src/lib/forms/service/stores/forms.ts new file mode 100644 index 0000000..cd0c9b6 --- /dev/null +++ b/src/lib/forms/service/stores/forms.ts @@ -0,0 +1,20 @@ +import { writable } from 'svelte/store'; +import type { Form } from '../../models'; + +export const forms = writable([]); + +export async function loadFormsFromDB( + platform: Readonly | undefined +): Promise { + const forms_res = await platform!.env.FORMS_DB.prepare('SELECT * FROM Form').run(); + + forms.set(forms_res.results); + + return forms_res.results; +} + +export function receiveForms(data: unknown) { + if (typeof data === 'object' && data !== null && 'forms' in data) { + forms.set(data.forms as Form[]); + } +} diff --git a/src/lib/forms/service/stores/question.ts b/src/lib/forms/service/stores/question.ts new file mode 100644 index 0000000..d497eef --- /dev/null +++ b/src/lib/forms/service/stores/question.ts @@ -0,0 +1,25 @@ +import { writable } from 'svelte/store'; +import type { Question } from '../../models'; + +export const question = writable(); + +export async function loadQuestionFromDB( + platform: Readonly | undefined, + params: number +): Promise { + const question_res = await platform!.env.FORMS_DB.prepare('SELECT * FROM Question WHERE id = ?') + .bind(params) + .run(); + + question.set(question_res.results[0]); + + return question_res.results[0]; +} + +export function receiveQuestion(data: unknown) { + if (typeof data === 'object' && data !== null && 'question' in data) { + question.set(data.question as Question); + } else { + console.error('Invalid data format in receiveQuestion:', data); + } +} diff --git a/src/routes/(dashboard)/Nav.svelte b/src/lib/presentation/NavBar.svelte similarity index 73% rename from src/routes/(dashboard)/Nav.svelte rename to src/lib/presentation/NavBar.svelte index 41f696e..300637d 100644 --- a/src/routes/(dashboard)/Nav.svelte +++ b/src/lib/presentation/NavBar.svelte @@ -1,35 +1,45 @@ - -