Skip to content

Commit cb7ebeb

Browse files
committed
feat: Implement fluency score upload and storage functionality
- Added fluency_score_json column to users table for storing AI fluency scores. - Introduced API endpoint to receive and store fluency scores from the extension. - Updated backend services to handle fluency score uploads and ensure synchronization with the server dashboard. - Enhanced dashboard rendering to display the latest fluency scores directly from the database.
1 parent b357308 commit cb7ebeb

8 files changed

Lines changed: 200 additions & 346 deletions

File tree

sharing-server/src/db.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface UserRow {
1111
created_at: string;
1212
last_seen_at: string | null;
1313
is_admin: number;
14+
fluency_score_json: string | null;
1415
}
1516

1617
export interface UploadRow {
@@ -158,6 +159,14 @@ function initSchema(db: DatabaseSync): void {
158159
if (!cols.some(c => c.name === 'fluency_json')) {
159160
db.exec('ALTER TABLE usage_uploads ADD COLUMN fluency_json TEXT');
160161
}
162+
163+
// Add fluency_score_json column to users if it doesn't exist (migration for existing DBs)
164+
const userCols = db
165+
.prepare("PRAGMA table_info(users)")
166+
.all() as unknown as Array<{ name: string }>;
167+
if (!userCols.some(c => c.name === 'fluency_score_json')) {
168+
db.exec('ALTER TABLE users ADD COLUMN fluency_score_json TEXT');
169+
}
161170
}
162171

163172
export function upsertUser(
@@ -247,3 +256,14 @@ export function getUploadsForUser(userId: number, days = 30): UploadRow[] {
247256
export function getAllUsers(): UserRow[] {
248257
return getDb().prepare('SELECT * FROM users ORDER BY created_at DESC').all() as unknown as UserRow[];
249258
}
259+
260+
export function upsertUserFluencyScore(userId: number, scoreJson: string): void {
261+
getDb().prepare(`
262+
UPDATE users SET fluency_score_json = ? WHERE id = ?
263+
`).run(scoreJson, userId);
264+
}
265+
266+
export function getUserFluencyScore(userId: number): string | null {
267+
const row = getDb().prepare('SELECT fluency_score_json FROM users WHERE id = ?').get(userId) as unknown as { fluency_score_json: string | null } | undefined;
268+
return row?.fluency_score_json ?? null;
269+
}

sharing-server/src/routes/api.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Hono } from 'hono';
22
import { requireBearerAuth, checkUploadRateLimit, type AuthVariables } from '../auth.js';
3-
import { upsertUpload, deleteUploadsForDays, getUploadsForUser, getDb, type UploadEntry } from '../db.js';
3+
import { upsertUpload, deleteUploadsForDays, getUploadsForUser, getDb, upsertUserFluencyScore, type UploadEntry } from '../db.js';
44

55
const MAX_STRING_LENGTHS = {
66
model: 128,
@@ -111,6 +111,35 @@ api.get('/data', requireBearerAuth, (c) => {
111111
return c.json(data);
112112
});
113113

114+
/**
115+
* POST /api/fluency-score — Store the extension's locally-computed fluency score.
116+
* Body: { overallStage, overallLabel, categories, computedAt }
117+
* This is the authoritative score: the server dashboard uses it directly instead of
118+
* re-computing from aggregated upload blobs.
119+
*/
120+
api.post('/fluency-score', requireBearerAuth, async (c) => {
121+
const user = c.get('user');
122+
123+
let body: unknown;
124+
try {
125+
body = await c.req.json();
126+
} catch {
127+
return c.json({ error: 'Invalid JSON body.' }, 400);
128+
}
129+
130+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
131+
return c.json({ error: 'Body must be a JSON object.' }, 400);
132+
}
133+
134+
const b = body as Record<string, unknown>;
135+
if (typeof b.overallStage !== 'number' || !Array.isArray(b.categories)) {
136+
return c.json({ error: '"overallStage" (number) and "categories" (array) are required.' }, 400);
137+
}
138+
139+
upsertUserFluencyScore(user.id, JSON.stringify(body));
140+
return c.json({ ok: true });
141+
});
142+
114143
// ── Helpers ──────────────────────────────────────────────────────────────────
115144

116145
function clampDays(raw: string | undefined): number {

0 commit comments

Comments
 (0)