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
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.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: '' };
|
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 = [
|
const COLLECTIONS = [
|
||||||
{
|
{
|
||||||
name: 'topics',
|
name: 'topics',
|
||||||
@@ -25,6 +31,7 @@ const COLLECTIONS = [
|
|||||||
{ name: 'label', type: 'text', required: true },
|
{ name: 'label', type: 'text', required: true },
|
||||||
{ name: 'type', type: 'text', required: false },
|
{ name: 'type', type: 'text', required: false },
|
||||||
{ name: 'description', 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: 'source', type: 'text', required: true },
|
||||||
{ name: 'target', type: 'text', required: true },
|
{ name: 'target', type: 'text', required: true },
|
||||||
{ name: 'type', type: 'text', required: false },
|
{ name: 'type', type: 'text', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -44,6 +52,7 @@ const COLLECTIONS = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'topic_id', type: 'text', required: true },
|
{ name: 'topic_id', type: 'text', required: true },
|
||||||
{ name: 'data', type: 'json', required: false },
|
{ name: 'data', type: 'json', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,6 +62,7 @@ const COLLECTIONS = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'topic_id', type: 'text', required: true },
|
{ name: 'topic_id', type: 'text', required: true },
|
||||||
{ name: 'questions', type: 'json', required: false },
|
{ name: 'questions', type: 'json', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,6 +73,7 @@ const COLLECTIONS = [
|
|||||||
{ name: 'name', type: 'text', required: true },
|
{ name: 'name', type: 'text', required: true },
|
||||||
{ name: 'status', type: 'text', required: false },
|
{ name: 'status', type: 'text', required: false },
|
||||||
{ name: 'error', 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: 'name', type: 'text', required: true },
|
||||||
{ name: 'pin', type: 'text', required: false },
|
{ name: 'pin', type: 'text', required: false },
|
||||||
{ name: 'role', 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: 'completed_at', type: 'text', required: false },
|
||||||
{ name: 'breakdown', type: 'json', required: false },
|
{ name: 'breakdown', type: 'json', required: false },
|
||||||
{ name: 'points_earned',type: 'number', 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: 'week_number', type: 'number', required: true },
|
||||||
{ name: 'questions', type: 'json', required: false },
|
{ name: 'questions', type: 'json', required: false },
|
||||||
{ name: 'meta', 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: 'user_id', type: 'text', required: true },
|
||||||
{ name: 'week_number', type: 'number', required: true },
|
{ name: 'week_number', type: 'number', required: true },
|
||||||
{ name: 'done', type: 'bool', required: false },
|
{ name: 'done', type: 'bool', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,6 +137,7 @@ const COLLECTIONS = [
|
|||||||
{ name: 'points', type: 'number', required: false },
|
{ name: 'points', type: 'number', required: false },
|
||||||
{ name: 'tests_completed', type: 'number', required: false },
|
{ name: 'tests_completed', type: 'number', required: false },
|
||||||
{ name: 'learnings_completed', type: 'number', required: false },
|
{ name: 'learnings_completed', type: 'number', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -131,6 +147,7 @@ const COLLECTIONS = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'key', type: 'text', required: true },
|
{ name: 'key', type: 'text', required: true },
|
||||||
{ name: 'value', type: 'text', required: false },
|
{ name: 'value', type: 'text', required: false },
|
||||||
|
...AUTODATE_FIELDS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ export const anthropicApi = {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'anthropic-version': '2023-06-01',
|
'anthropic-version': '2023-06-01',
|
||||||
'anthropic-dangerous-direct-browser-access': 'true'
|
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: model,
|
model: model,
|
||||||
max_tokens: 4000,
|
max_tokens: 4000,
|
||||||
|
temperature: 0,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [{ role: 'user', content: userMessage }]
|
messages: [{ role: 'user', content: userMessage }]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ export async function setLearnDone(userId, weekNumber) {
|
|||||||
// ── Leaderboard ───────────────────────────────────────────────────────────────
|
// ── Leaderboard ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function getLeaderboard() {
|
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) {
|
export async function upsertLeaderboardEntry(userId, name, pointsDelta, testsCompletedDelta = 0) {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const Admin = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{source.name}</p>
|
<p className="font-medium">{source.name}</p>
|
||||||
<p className="text-xs text-fg-muted flex items-center gap-1 mt-1">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,14 @@ export default defineConfig({
|
|||||||
'/api/anthropic': {
|
'/api/anthropic': {
|
||||||
target: 'https://api.anthropic.com',
|
target: 'https://api.anthropic.com',
|
||||||
changeOrigin: true,
|
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