feat: implement PocketBase database schema setup script and core API service for content management
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -22,3 +22,12 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Secrets
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# PocketBase
|
||||
pb_data/
|
||||
pocketbase.exe
|
||||
pocketbase.zip
|
||||
|
||||
@@ -15,6 +15,12 @@ if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
||||
|
||||
const OPEN_RULES = { listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' };
|
||||
|
||||
// PocketBase v0.23: created/updated must be explicitly defined as autodate fields
|
||||
const AUTODATE_FIELDS = [
|
||||
{ name: 'created', type: 'autodate', onCreate: true, onUpdate: false },
|
||||
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
||||
];
|
||||
|
||||
const COLLECTIONS = [
|
||||
{
|
||||
name: 'topics',
|
||||
@@ -25,6 +31,7 @@ const COLLECTIONS = [
|
||||
{ name: 'label', type: 'text', required: true },
|
||||
{ name: 'type', type: 'text', required: false },
|
||||
{ name: 'description', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -35,6 +42,7 @@ const COLLECTIONS = [
|
||||
{ name: 'source', type: 'text', required: true },
|
||||
{ name: 'target', type: 'text', required: true },
|
||||
{ name: 'type', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -44,6 +52,7 @@ const COLLECTIONS = [
|
||||
fields: [
|
||||
{ name: 'topic_id', type: 'text', required: true },
|
||||
{ name: 'data', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -53,6 +62,7 @@ const COLLECTIONS = [
|
||||
fields: [
|
||||
{ name: 'topic_id', type: 'text', required: true },
|
||||
{ name: 'questions', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -63,6 +73,7 @@ const COLLECTIONS = [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'status', type: 'text', required: false },
|
||||
{ name: 'error', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -73,6 +84,7 @@ const COLLECTIONS = [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'pin', type: 'text', required: false },
|
||||
{ name: 'role', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -89,6 +101,7 @@ const COLLECTIONS = [
|
||||
{ name: 'completed_at', type: 'text', required: false },
|
||||
{ name: 'breakdown', type: 'json', required: false },
|
||||
{ name: 'points_earned',type: 'number', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -100,6 +113,7 @@ const COLLECTIONS = [
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'questions', type: 'json', required: false },
|
||||
{ name: 'meta', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -110,6 +124,7 @@ const COLLECTIONS = [
|
||||
{ name: 'user_id', type: 'text', required: true },
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'done', type: 'bool', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -122,6 +137,7 @@ const COLLECTIONS = [
|
||||
{ name: 'points', type: 'number', required: false },
|
||||
{ name: 'tests_completed', type: 'number', required: false },
|
||||
{ name: 'learnings_completed', type: 'number', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -131,6 +147,7 @@ const COLLECTIONS = [
|
||||
fields: [
|
||||
{ name: 'key', type: 'text', required: true },
|
||||
{ name: 'value', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -30,11 +30,11 @@ export const anthropicApi = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'anthropic-version': '2023-06-01',
|
||||
'anthropic-dangerous-direct-browser-access': 'true'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model,
|
||||
max_tokens: 4000,
|
||||
temperature: 0,
|
||||
system: systemPrompt,
|
||||
messages: [{ role: 'user', content: userMessage }]
|
||||
})
|
||||
|
||||
@@ -205,7 +205,8 @@ export async function setLearnDone(userId, weekNumber) {
|
||||
// ── Leaderboard ───────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getLeaderboard() {
|
||||
return pb.collection('leaderboard').getFullList({ sort: '-points' });
|
||||
const entries = await pb.collection('leaderboard').getFullList();
|
||||
return entries.sort((a, b) => (b.points || 0) - (a.points || 0));
|
||||
}
|
||||
|
||||
export async function upsertLeaderboardEntry(userId, name, pointsDelta, testsCompletedDelta = 0) {
|
||||
|
||||
@@ -104,7 +104,7 @@ const Admin = () => {
|
||||
<div>
|
||||
<p className="font-medium">{source.name}</p>
|
||||
<p className="text-xs text-fg-muted flex items-center gap-1 mt-1">
|
||||
<Clock size={12} /> {new Date(source.created).toLocaleString()}
|
||||
<Clock size={12} /> {source.created ? new Date(source.created).toLocaleString() : '—'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,14 @@ export default defineConfig({
|
||||
'/api/anthropic': {
|
||||
target: 'https://api.anthropic.com',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/anthropic/, '')
|
||||
rewrite: (path) => path.replace(/^\/api\/anthropic/, ''),
|
||||
configure: (proxy) => {
|
||||
proxy.on('proxyReq', (proxyReq) => {
|
||||
if (process.env.ANTHROPIC_API_KEY) {
|
||||
proxyReq.setHeader('x-api-key', process.env.ANTHROPIC_API_KEY);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user