diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..b864612 --- /dev/null +++ b/.example.env @@ -0,0 +1,5 @@ +AWS_ACCESS_KEY_ID=************************** +AWS_SECRET_ACCESS_KEY=**************************************************************** +AWS_DEFAULT_REGION=****** +AWS_ENDPOINT_URL=localhost:3000 +S3_DEFAULT_BUCKET=comicverse-test-bucket diff --git a/.gitignore b/.gitignore index 79518f7..68c23fa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +data.db diff --git a/bun.lockb b/bun.lockb index d1e9d95..21ce4e2 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/flake.nix b/flake.nix index 5be7a89..38e9896 100644 --- a/flake.nix +++ b/flake.nix @@ -22,10 +22,11 @@ devShells = forAllSystems (system: pkgs: { default = pkgs.mkShell { buildInputs = with pkgs; [ + awscli2 + bun eslint nodejs_22 nodePackages_latest.prettier - bun ]; }; }); diff --git a/package.json b/package.json index f8ef5f0..027ffd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,28 @@ { "name": "comicverse", "version": "0.0.1", + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/eslint": "^9.6.0", + "@types/node": "^22.6.0", + "eslint": "^9.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.36.0", + "globals": "^15.0.0", + "minio": "^8.0.1", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", + "svelte": "^4.2.7", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vite": "^5.0.3", + "xml-js": "^1.6.11" + }, "private": true, "scripts": { "dev": "vite dev", @@ -11,22 +33,5 @@ "lint": "prettier --check . && eslint .", "format": "prettier --write ." }, - "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "@types/eslint": "^9.6.0", - "eslint": "^9.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.36.0", - "globals": "^15.0.0", - "prettier": "^3.1.1", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.2.7", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "typescript-eslint": "^8.0.0", - "vite": "^5.0.3" - }, "type": "module" } diff --git a/src/app.html b/src/app.html index 77a5ff5..bc40f4a 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/lib/index.ts b/src/lib/index.ts index 856f2b6..e655f2b 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1 +1,3 @@ -// place files you want to import through the `$lib` alias in this folder. +export { default as s3 } from './s3'; +export { default as db } from './sqlite'; +export * from './sqlite' diff --git a/src/lib/s3.ts b/src/lib/s3.ts new file mode 100644 index 0000000..e8ded2b --- /dev/null +++ b/src/lib/s3.ts @@ -0,0 +1,19 @@ +import { + AWS_ENDPOINT_URL, + AWS_ACCESS_KEY_ID, + AWS_DEFAULT_REGION, + AWS_SECRET_ACCESS_KEY +} from '$env/static/private'; + +import * as Minio from 'minio'; + +const client = new Minio.Client({ + endPoint: AWS_ENDPOINT_URL.split(':')[0], + port: Number(AWS_ENDPOINT_URL.split(':')[1]), + useSSL: false, + region: AWS_DEFAULT_REGION, + accessKey: AWS_ACCESS_KEY_ID, + secretKey: AWS_SECRET_ACCESS_KEY +}); + +export default client; diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts new file mode 100644 index 0000000..f6affd3 --- /dev/null +++ b/src/lib/sqlite.ts @@ -0,0 +1,28 @@ +import sqlite3 from 'sqlite3'; +import { open } from 'sqlite'; + +const db = await open({ + filename: 'data.db', + driver: sqlite3.cached.Database +}); + +await db.exec(` +CREATE TABLE IF NOT EXISTS projects ( + ID text NOT NULL, + Name text NOT NULL, + PRIMARY KEY(ID) +) +`); + +type Project = { + id: string; + title: string; + pages: { + title: string; + src: string; + background: string; + }[]; +}; + +export type { Project }; +export default db; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..ad4b45a --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,21 @@ + + + + + + + + +
+ +
+ + diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..53a4103 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,40 @@ +import { fail, type Actions } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +import { db, s3, type Project } from '$lib'; +import { AWS_S3_DEFAULT_BUCKET } from '$env/static/private'; + +export const load = (async ({}) => { + const res = await db.all('SELECT ID, Name FROM projects'); + return { projects: res }; +}) as PageServerLoad; + +export const actions: Actions = { + default: async ({ request }) => { + const data = await request.formData(); + const name = data.get('project-name'); + + if (!name) return fail(400, { name, missing: true }); + + const uuid = crypto.randomUUID().split('-')[0]; + + const res = await db.run('INSERT OR IGNORE INTO projects (ID, Name) VALUES (:id, :name)', { + ':id': uuid, + ':name': name + }); + + const project: Project = { + id: uuid, + title: name.toString(), + pages: [] + }; + + await s3.putObject(AWS_S3_DEFAULT_BUCKET, `${uuid}/project.json`, JSON.stringify(project)); + + if (res.changes == undefined) { + return fail(500, { reason: 'Failed to insert project into database' }); + } + + return { success: true }; + } +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5982b0a..5ee34b4 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,46 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

+ + +
+ {#each data.projects as p} + + {/each} +
+
+
+ + +
+
+
+
+ + diff --git a/src/routes/files/[project]/[file]/+server.ts b/src/routes/files/[project]/[file]/+server.ts new file mode 100644 index 0000000..55b2dcc --- /dev/null +++ b/src/routes/files/[project]/[file]/+server.ts @@ -0,0 +1,41 @@ +import { type RequestHandler } from '@sveltejs/kit'; +import stream from 'node:stream/promises'; + +import { db, s3, type Project } from '$lib'; +import { AWS_S3_DEFAULT_BUCKET } from '$env/static/private'; +import { extname } from 'node:path'; + +export const GET = (async ({ params }) => { + const file = await s3.getObject(AWS_S3_DEFAULT_BUCKET, `${params.project}/${params.file}`); + file.on('error', (err: any) => { + console.log(err); + }); + + let chunks: Buffer[] = []; + let buf; + + file.on('data', (chunk) => { + chunks.push(Buffer.from(chunk)); + }); + file.on('end', () => { + buf = Buffer.concat(chunks); + }); + await stream.finished(file) + + let res = new Response(buf); + res.headers.set( + 'Content-Type', + (() => { + switch (extname(params.file!)) { + case '.png': + return 'image/png'; + case '.json': + return 'application/json'; + } + return 'text/plain'; + })() + ); + res.headers.set('Cache-Control', 'max-age=604800') + + return res; +}) as RequestHandler; diff --git a/src/routes/projects/[id]/+page.server.ts b/src/routes/projects/[id]/+page.server.ts new file mode 100644 index 0000000..f713ec4 --- /dev/null +++ b/src/routes/projects/[id]/+page.server.ts @@ -0,0 +1,76 @@ +import { error, fail, redirect, type Actions } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import stream from 'node:stream/promises'; +import { db, s3, type Project } from '$lib'; +import { AWS_S3_DEFAULT_BUCKET } from '$env/static/private'; +import { extname } from 'node:path'; + +export const load = (async ({ params }) => { + const res = await db.get<{ id: string; name: string }>( + 'SELECT ID, Name FROM projects WHERE ID = ?', + params.id + ); + + if (res === undefined) { + return fail(404, { reason: 'Failed to find project into database' }); + } + + const project = await s3.getObject(AWS_S3_DEFAULT_BUCKET, `${params.id}/project.json`); + project.on('error', (err: any) => { + console.log(err); + }); + let p: string = ''; + project.on('data', (chunk: any) => { + p += chunk; + }); + await stream.finished(project); + let proj = JSON.parse(p) as Project; + + return { project: proj }; +}) as PageServerLoad; + +export const actions = { + delete: async ({ params }) => { + const res = await db.run('DELETE FROM projects WHERE ID = ?', params.id); + + await s3.removeObject(AWS_S3_DEFAULT_BUCKET, `${params.id}/project.json`); + + if (res === undefined) { + return fail(500, { reason: 'Failed to delete project' }); + } + + redirect(303, '/'); + }, + addpage: async ({ request, params }) => { + const form = await request.formData(); + const file = form?.get('file') as File; + const title = form?.get('title') as string; + const color = form?.get('color') as string; + + console.log(file); + + const project = await s3.getObject(AWS_S3_DEFAULT_BUCKET, `${params.id}/project.json`); + project.on('error', (err: any) => { + console.log(err); + }); + let p: string = ''; + project.on('data', (chunk: any) => { + p += chunk; + }); + await stream.finished(project); + let proj = JSON.parse(p) as Project; + + const filename = `${crypto.randomUUID().split('-')[0]}${extname(file?.name)}`; + + proj.pages.push({ + title: title, + src: filename, + background: color + }); + + const buf = Buffer.from(await file.arrayBuffer()); + + await s3.putObject(AWS_S3_DEFAULT_BUCKET, `${params.id}/project.json`, JSON.stringify(proj)); + await s3.putObject(AWS_S3_DEFAULT_BUCKET, `${params.id}/${filename}`, buf); + } +} as Actions; diff --git a/src/routes/projects/[id]/+page.svelte b/src/routes/projects/[id]/+page.svelte new file mode 100644 index 0000000..79cc3c6 --- /dev/null +++ b/src/routes/projects/[id]/+page.svelte @@ -0,0 +1,145 @@ + + +
+{JSON.stringify(
+			{
+				color: color,
+				scroll: scroll
+			},
+			null,
+			2
+		)}
+
+
+ + +
+
+ +

+ Add new page +

+
+
+ + + + +
+
+
+
+ +
(scroll = reader.scrollTop)} + > +
+ {#each pages as page} +
+ +
+
+ + +
+
+
+ {/each} +
+
+
+ + diff --git a/temp.json b/temp.json new file mode 100644 index 0000000..b6fbdff --- /dev/null +++ b/temp.json @@ -0,0 +1 @@ +{"id":"88ba21ea","title":"test project","pages":[]} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index bbf8c7d..efed4ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,5 +2,9 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit()] + plugins: [sveltekit()], + server: { + port: 3000, + host: '192.168.1.7' + } });