feat(02-01): add CRUD form actions

- Add create action with content validation (title optional, type defaults to thought)
- Add update action with field-by-field updates and validation
- Add delete action with existence check
- Add toggleComplete action to toggle between open/done
- Use getOrdered() in load function for tasks-first ordering
- Update page component to use new data structure
This commit is contained in:
Thomas Richter
2026-01-29 11:02:43 +01:00
parent 87bf928c12
commit 9a449228b7
2 changed files with 133 additions and 28 deletions

View File

@@ -1,24 +1,129 @@
import type { PageServerLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { fail } from '@sveltejs/kit';
import { entryRepository } from '$lib/server/db/repository';
export const load: PageServerLoad = async () => {
const count = entryRepository.count();
// Create a test entry if none exist (for verification)
let testEntry = null;
if (count === 0) {
testEntry = entryRepository.create({
title: 'Foundation Test',
content: 'This entry was created to verify the foundation is working.',
type: 'thought'
});
}
const entries = entryRepository.getAll({ limit: 5 });
const entries = entryRepository.getOrdered({ showCompleted: false });
return {
dbStatus: 'connected',
entryCount: testEntry ? count + 1 : count,
recentEntries: entries
entries
};
};
export const actions: Actions = {
create: async ({ request }) => {
const formData = await request.formData();
const title = formData.get('title')?.toString().trim() || undefined;
const content = formData.get('content')?.toString().trim() || '';
const type = formData.get('type')?.toString() as 'task' | 'thought' | undefined;
// Validate content is not empty
if (!content) {
return fail(400, {
error: 'Content is required',
title: title || '',
content: '',
type: type || 'thought'
});
}
const entry = entryRepository.create({
title,
content,
type: type || 'thought'
});
return { success: true, entryId: entry.id };
},
update: async ({ request }) => {
const formData = await request.formData();
const id = formData.get('id')?.toString();
if (!id) {
return fail(400, { error: 'Entry ID is required' });
}
const existing = entryRepository.getById(id);
if (!existing) {
return fail(404, { error: 'Entry not found' });
}
// Collect fields to update
const updates: Record<string, string | boolean | null> = {};
const title = formData.get('title');
if (title !== null) {
updates.title = title.toString().trim() || null;
}
const content = formData.get('content');
if (content !== null) {
const contentStr = content.toString().trim();
if (contentStr === '') {
return fail(400, { error: 'Content cannot be empty' });
}
updates.content = contentStr;
}
const type = formData.get('type');
if (type !== null) {
const typeStr = type.toString();
if (typeStr !== 'task' && typeStr !== 'thought') {
return fail(400, { error: 'Invalid type' });
}
updates.type = typeStr;
}
const status = formData.get('status');
if (status !== null) {
const statusStr = status.toString();
if (statusStr !== 'open' && statusStr !== 'done' && statusStr !== 'archived') {
return fail(400, { error: 'Invalid status' });
}
updates.status = statusStr;
}
entryRepository.update(id, updates);
return { success: true };
},
delete: async ({ request }) => {
const formData = await request.formData();
const id = formData.get('id')?.toString();
if (!id) {
return fail(400, { error: 'Entry ID is required' });
}
const existing = entryRepository.getById(id);
if (!existing) {
return fail(404, { error: 'Entry not found' });
}
entryRepository.delete(id);
return { success: true };
},
toggleComplete: async ({ request }) => {
const formData = await request.formData();
const id = formData.get('id')?.toString();
if (!id) {
return fail(400, { error: 'Entry ID is required' });
}
const existing = entryRepository.getById(id);
if (!existing) {
return fail(404, { error: 'Entry not found' });
}
// Toggle status between 'open' and 'done'
const newStatus = existing.status === 'done' ? 'open' : 'done';
entryRepository.update(id, { status: newStatus });
return { success: true };
}
};