Compare commits
2 Commits
v0.1.0
...
b3087aa9d4
| Author | SHA1 | Date | |
|---|---|---|---|
| b3087aa9d4 | |||
| 2e16cf9d51 |
@@ -0,0 +1,65 @@
|
|||||||
|
# Project Review & Refactoring TODOs
|
||||||
|
|
||||||
|
This document contains the prioritized list of refactoring tasks, architectural improvements, and testing strategies for the Aktiteil project.
|
||||||
|
|
||||||
|
## 🚨 Must do (Security & Critical Best Practices)
|
||||||
|
|
||||||
|
- [ ] **Fix Critical XSS Vulnerability (`{@html}` without sanitization)**
|
||||||
|
- **Where:** `src/routes/akti/[aktiId]/+page.svelte`
|
||||||
|
- **Why:** Rendering user input via `{@html data.akti.body}` without sanitization allows malicious scripts to be injected.
|
||||||
|
- **Fix:** Use the already installed `sanitize-html` library on the server to sanitize `changeRequest.body` before updating/inserting into the database.
|
||||||
|
|
||||||
|
- [ ] **Move Server-Only Code to `$lib/server`**
|
||||||
|
- **Where:** `src/lib/auth.ts`
|
||||||
|
- **Why:** It imports from `./server/db`. Keeping server-side dependencies in the general `$lib` folder risks accidental imports by client components, breaking the Vite build and potentially leaking server logic.
|
||||||
|
- **Fix:** Move and rename it to `src/lib/server/session.ts` (or `authUtils.ts`) and update imports in `.server.ts` files.
|
||||||
|
|
||||||
|
- [ ] **Fix Action Validation Error Handling**
|
||||||
|
- **Where:** `src/routes/akti/[aktiId]/+page.server.ts` and `src/routes/akti/[aktiId]/comment/+page.server.ts`
|
||||||
|
- **Why:** Currently returning `error(400)` on validation failure, which wipes form data and shows a generic error page.
|
||||||
|
- **Fix:** Use SvelteKit's `fail(400, { message: 'Invalid data' })` to keep the user on the page and preserve their input.
|
||||||
|
|
||||||
|
- [ ] **Fix Hacky Fallback in Auth Query**
|
||||||
|
- **Where:** `src/lib/auth.ts` -> `getSession()`
|
||||||
|
- **Why:** Querying the DB with a fallback UUID (`eaf930...`) when email is missing is an anti-pattern.
|
||||||
|
- **Fix:** Implement an early return (`if (!session?.user?.email) return null;`) before hitting the database.
|
||||||
|
|
||||||
|
## 🛠️ Should do (Performance & Architecture)
|
||||||
|
|
||||||
|
- [ ] **Parallelize Database Queries**
|
||||||
|
- **Where:** `src/routes/akti/[aktiId]/+page.server.ts` (load function)
|
||||||
|
- **Why:** Queries are running sequentially.
|
||||||
|
- **Fix:** Use `Promise.all([ db.query.aktis.findFirst(...), db.query.ratings.findMany(...) ])` to run concurrently.
|
||||||
|
|
||||||
|
- [ ] **Implement Pagination / Limit for the Dashboard**
|
||||||
|
- **Where:** `src/routes/+page.server.ts`
|
||||||
|
- **Why:** Querying all records joined with ratings will scale poorly.
|
||||||
|
- **Fix:** Add a `.limit()` clause and consider basic pagination or infinite scrolling.
|
||||||
|
|
||||||
|
- [ ] **Extend Auth.js Types Globally**
|
||||||
|
- **Where:** `src/app.d.ts`
|
||||||
|
- **Why:** TypeScript doesn't inherently know `session.user.id` exists, leading to hacky workarounds.
|
||||||
|
- **Fix:** Override `@auth/sveltekit` Session types in `app.d.ts` to include `id` and `email` strictly.
|
||||||
|
|
||||||
|
- [ ] **Consider Adopting a Form Library**
|
||||||
|
- **Where:** `src/lib/extractFormData.ts`
|
||||||
|
- **Why:** Custom form extractors lack instant client-side validation and seamless server-side error mapping.
|
||||||
|
- **Fix:** Consider switching to `sveltekit-superforms` which integrates well with Valibot.
|
||||||
|
|
||||||
|
## ✨ Nice to have (UX & Polish)
|
||||||
|
|
||||||
|
- [ ] **Clarify File Naming (`auth.ts` vs `auth.ts`)**
|
||||||
|
- Rename `src/lib/auth.ts` to `session.ts` or similar to distinguish from `src/auth.ts` (Auth.js setup).
|
||||||
|
|
||||||
|
- [ ] **Abstract Heavy Database Queries**
|
||||||
|
- Move complex aggregations (like computing averages in `src/routes/+page.server.ts`) into a dedicated `src/lib/server/db/queries.ts` file to keep routes clean.
|
||||||
|
|
||||||
|
- [ ] **Clean up Redundant Imports**
|
||||||
|
- In `src/routes/+layout.server.ts`, change `import { getSession as getSession }` to `import { getSession }`.
|
||||||
|
|
||||||
|
## 🧪 Testing Plan
|
||||||
|
|
||||||
|
- [ ] **Add Playwright (End-to-End Testing)**
|
||||||
|
- Install Playwright to test SvelteKit server actions, DB integration, and Flowbite forms holistically.
|
||||||
|
- [ ] **Add Vitest + Svelte Testing Library (Unit/Component Testing)**
|
||||||
|
- Set up Vitest to test UI components (`AktiCard`, `AktiEditor`) and utility functions (`extractFormData`) in isolation.
|
||||||
+7
-1
@@ -10,6 +10,8 @@
|
|||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "prettier --check . && eslint .",
|
"lint": "prettier --check . && eslint .",
|
||||||
"db:start": "docker compose up",
|
"db:start": "docker compose up",
|
||||||
@@ -28,6 +30,8 @@
|
|||||||
"@sveltejs/kit": "^2.49.0",
|
"@sveltejs/kit": "^2.49.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/svelte": "^5.3.1",
|
||||||
"@tiptap/core": "3.7.2",
|
"@tiptap/core": "3.7.2",
|
||||||
"@types/node": "^20.19.25",
|
"@types/node": "^20.19.25",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
@@ -39,6 +43,7 @@
|
|||||||
"flowbite-svelte-icons": "^3.0.0",
|
"flowbite-svelte-icons": "^3.0.0",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
|
"jsdom": "^29.0.1",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-svelte": "^3.4.0",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
@@ -47,7 +52,8 @@
|
|||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.47.0",
|
"typescript-eslint": "^8.47.0",
|
||||||
"vite": "^7.2.4"
|
"vite": "^7.2.4",
|
||||||
|
"vitest": "^4.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.11.1",
|
"@auth/drizzle-adapter": "^1.11.1",
|
||||||
|
|||||||
Generated
+761
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { extractFormData } from './extractFormData';
|
||||||
|
import * as v from 'valibot';
|
||||||
|
|
||||||
|
describe('extractFormData', () => {
|
||||||
|
it('should successfully extract and validate correct form data', async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', 'John Doe');
|
||||||
|
formData.append('age', '30');
|
||||||
|
|
||||||
|
const request = new Request('http://localhost', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const schema = v.object({
|
||||||
|
name: v.string(),
|
||||||
|
age: v.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await extractFormData(request, schema);
|
||||||
|
|
||||||
|
expect(result.error).toBeNull();
|
||||||
|
expect(result.data).toEqual({ name: 'John Doe', age: '30' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail validation with missing required fields', async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('age', '30');
|
||||||
|
|
||||||
|
const request = new Request('http://localhost', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const schema = v.object({
|
||||||
|
name: v.string(),
|
||||||
|
age: v.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await extractFormData(request, schema);
|
||||||
|
|
||||||
|
expect(result.data).toBeUndefined();
|
||||||
|
expect(result.error).toBeTypeOf('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import '@testing-library/jest-dom/vitest';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { svelteTesting } from '@testing-library/svelte/vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit(), svelteTesting()],
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./vitest-setup.ts']
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user