refactor(ts): day 3 batch 1 — convert 4 small route files
Migrates the 4 smallest and best-understood routes to TypeScript:
src/routes/logs.ts
src/routes/userPreferences.ts
src/routes/sessions.ts
src/routes/extensions.ts (11 Playwright tests cover this one)
Pattern established for the remaining 25 routes:
import express = require('express'); // CJS-style, fully typed
import type { Request, Response } from 'express';
const db = require('../db/database'); // stays `any` until Day 4
const { authMiddleware } = require('../middleware/auth');
const router = express.Router();
router.get('/foo', async function (req: Request, res: Response) {
// req.user is typed via src/types/express.d.ts augmentation
});
export = router; // CJS-compatible export
Why `import = require()` instead of `import from`:
Express is imported via the namespace-import syntax so tsc preserves
`require("express")` in the emitted CJS. Using plain ES import would
emit `require("express").default` which doesn't exist on Express'
CommonJS default export. The pattern keeps the compiled dist/*.js
byte-identical to what vanilla Node expects.
Why `const { authMiddleware } = require(...)` for other imports:
The middleware + utils files are still .js and their module.exports
shape isn't fully typed yet (Day 4 work). Using `require` avoids
dragging Day 4 work forward; the destructuring still gives us the
variable name we want.
New file — src/types/express.d.ts:
Augments Express.Request with `user?: AuthUser` and `sessionId?: string`
(attached by authMiddleware). Route handlers now type-check against
`req.user!.id` instead of requiring a runtime cast.
`req.user!` uses non-null assertion because authMiddleware guarantees
user is set for every mounted route; strict mode on Day 5 will keep
the assertion but add a type-guard check inside authMiddleware itself
so it propagates.
Verification
- npm run typecheck → 0 errors
- scripts/lint-references.js → green
- Compiled dist/src/routes/logs.js diffs against the original .js by
whitespace + var→const only. Behavior preserved.
Nothing deployed. Prod + e2e containers still run the previous image.
Progress: 1/30 (server.ts) + 4/29 routes = 5 of 54 total files migrated.
Remaining batches go in subsequent Day 3 commits.
This commit is contained in:
parent
a2fe1b38d1
commit
c1d61f5a72
6 changed files with 206 additions and 167 deletions
|
|
@ -4,39 +4,42 @@
|
|||
// Soft-delete via trashed_at: deleting moves to trash; user can restore
|
||||
// or purge. List supports async search (q) and trash toggle (trash=1).
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var db = require('../db/database');
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
import express = require('express');
|
||||
import type { Request, Response } from 'express';
|
||||
const db = require('../db/database');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const router = express.Router();
|
||||
router.use(authMiddleware);
|
||||
|
||||
function sanitizeType(t) {
|
||||
return (t === 'pager') ? 'pager' : 'extension';
|
||||
type ExtensionType = 'extension' | 'pager';
|
||||
|
||||
function sanitizeType(t: unknown): ExtensionType {
|
||||
return t === 'pager' ? 'pager' : 'extension';
|
||||
}
|
||||
function clip(s, n) {
|
||||
function clip(s: unknown, n?: number): string {
|
||||
return String(s == null ? '' : s).trim().substring(0, n || 200);
|
||||
}
|
||||
function parseId(raw) {
|
||||
var n = parseInt(raw, 10);
|
||||
return (Number.isFinite(n) && n > 0) ? n : null;
|
||||
function parseId(raw: unknown): number | null {
|
||||
const n = parseInt(raw as string, 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : null;
|
||||
}
|
||||
|
||||
// ── GET /api/extensions ─────────────────────────────────────
|
||||
// Query params:
|
||||
// trash=1 → return trashed items only (default: active only)
|
||||
// q=text → case-insensitive substring search on location/name/number/notes
|
||||
router.get('/extensions', async function (req, res) {
|
||||
router.get('/extensions', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var trashMode = req.query.trash === '1' || req.query.trash === 'true';
|
||||
var q = (req.query.q || '').trim();
|
||||
const trashMode = req.query.trash === '1' || req.query.trash === 'true';
|
||||
const q = ((req.query.q as string) || '').trim();
|
||||
|
||||
var sql =
|
||||
let sql =
|
||||
'SELECT id, location, name, number, type, notes, trashed_at, created_at, updated_at ' +
|
||||
'FROM user_phone_extensions WHERE user_id = $1 AND trashed_at IS ' +
|
||||
(trashMode ? 'NOT NULL' : 'NULL');
|
||||
var params = [req.user.id];
|
||||
const params: (string | number)[] = [req.user!.id];
|
||||
|
||||
if (q) {
|
||||
sql += ' AND (LOWER(location) LIKE $2 OR LOWER(name) LIKE $2 OR LOWER(number) LIKE $2 OR LOWER(notes) LIKE $2)';
|
||||
|
|
@ -44,110 +47,110 @@ router.get('/extensions', async function (req, res) {
|
|||
}
|
||||
sql += ' ORDER BY location ASC, type ASC, name ASC';
|
||||
|
||||
var rows = await db.all(sql, params);
|
||||
const rows = await db.all(sql, params);
|
||||
res.json({ success: true, items: rows });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('GET /extensions', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── POST /api/extensions ────────────────────────────────────
|
||||
router.post('/extensions', async function (req, res) {
|
||||
router.post('/extensions', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var location = clip(req.body.location, 120);
|
||||
var name = clip(req.body.name, 120);
|
||||
var number = clip(req.body.number, 40);
|
||||
var type = sanitizeType(req.body.type);
|
||||
var notes = clip(req.body.notes, 500);
|
||||
const location = clip(req.body.location, 120);
|
||||
const name = clip(req.body.name, 120);
|
||||
const number = clip(req.body.number, 40);
|
||||
const type = sanitizeType(req.body.type);
|
||||
const notes = clip(req.body.notes, 500);
|
||||
if (!location) return res.status(400).json({ error: 'Location is required' });
|
||||
if (!name) return res.status(400).json({ error: 'Name / department is required' });
|
||||
if (!number) return res.status(400).json({ error: 'Number is required' });
|
||||
if (!name) return res.status(400).json({ error: 'Name / department is required' });
|
||||
if (!number) return res.status(400).json({ error: 'Number is required' });
|
||||
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'INSERT INTO user_phone_extensions (user_id, location, name, number, type, notes) VALUES ($1,$2,$3,$4,$5,$6)',
|
||||
[req.user.id, location, name, number, type, notes]
|
||||
[req.user!.id, location, name, number, type, notes]
|
||||
);
|
||||
res.json({ success: true, id: result.lastInsertRowid });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('POST /extensions', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── PUT /api/extensions/:id ─────────────────────────────────
|
||||
router.put('/extensions/:id', async function (req, res) {
|
||||
router.put('/extensions/:id', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var id = parseId(req.params.id);
|
||||
const id = parseId(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'Invalid id' });
|
||||
var location = clip(req.body.location, 120);
|
||||
var name = clip(req.body.name, 120);
|
||||
var number = clip(req.body.number, 40);
|
||||
var type = sanitizeType(req.body.type);
|
||||
var notes = clip(req.body.notes, 500);
|
||||
const location = clip(req.body.location, 120);
|
||||
const name = clip(req.body.name, 120);
|
||||
const number = clip(req.body.number, 40);
|
||||
const type = sanitizeType(req.body.type);
|
||||
const notes = clip(req.body.notes, 500);
|
||||
if (!location || !name || !number) return res.status(400).json({ error: 'Location, name, and number are required' });
|
||||
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'UPDATE user_phone_extensions SET location=$1, name=$2, number=$3, type=$4, notes=$5, updated_at=NOW() WHERE id=$6 AND user_id=$7',
|
||||
[location, name, number, type, notes, id, req.user.id]
|
||||
[location, name, number, type, notes, id, req.user!.id]
|
||||
);
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found' });
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('PUT /extensions/:id', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── DELETE /api/extensions/:id — soft-delete (move to trash) ──
|
||||
router.delete('/extensions/:id', async function (req, res) {
|
||||
router.delete('/extensions/:id', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var id = parseId(req.params.id);
|
||||
const id = parseId(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'Invalid id' });
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'UPDATE user_phone_extensions SET trashed_at = NOW(), updated_at = NOW() WHERE id=$1 AND user_id=$2 AND trashed_at IS NULL',
|
||||
[id, req.user.id]
|
||||
[id, req.user!.id]
|
||||
);
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found or already trashed' });
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('DELETE /extensions/:id', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── POST /api/extensions/:id/restore — bring back from trash ──
|
||||
router.post('/extensions/:id/restore', async function (req, res) {
|
||||
router.post('/extensions/:id/restore', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var id = parseId(req.params.id);
|
||||
const id = parseId(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'Invalid id' });
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'UPDATE user_phone_extensions SET trashed_at = NULL, updated_at = NOW() WHERE id=$1 AND user_id=$2 AND trashed_at IS NOT NULL',
|
||||
[id, req.user.id]
|
||||
[id, req.user!.id]
|
||||
);
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Not found in trash' });
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('POST /extensions/:id/restore', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── DELETE /api/extensions/:id/purge — hard delete (only from trash) ──
|
||||
router.delete('/extensions/:id/purge', async function (req, res) {
|
||||
router.delete('/extensions/:id/purge', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var id = parseId(req.params.id);
|
||||
const id = parseId(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'Invalid id' });
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'DELETE FROM user_phone_extensions WHERE id=$1 AND user_id=$2 AND trashed_at IS NOT NULL',
|
||||
[id, req.user.id]
|
||||
[id, req.user!.id]
|
||||
);
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Must trash before purge' });
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logger.error('DELETE /extensions/:id/purge', e.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export = router;
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
|
||||
router.get('/logs/usage', authMiddleware, async function(req, res) {
|
||||
try {
|
||||
var days = parseInt(req.query.days) || 30;
|
||||
var summary = await logger.getUsageSummary(req.user.id, days);
|
||||
res.json({ success: true, summary: summary, period: days + ' days' });
|
||||
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
||||
});
|
||||
|
||||
router.get('/logs/audit', authMiddleware, async function(req, res) {
|
||||
try {
|
||||
var logs = await logger.getAuditLogs(req.user.id, parseInt(req.query.limit) || 50);
|
||||
res.json({ success: true, logs: logs });
|
||||
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
||||
});
|
||||
|
||||
router.get('/logs/api', authMiddleware, async function(req, res) {
|
||||
try {
|
||||
var logs = await logger.getApiLogs(req.user.id, parseInt(req.query.limit) || 50);
|
||||
res.json({ success: true, logs: logs });
|
||||
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
||||
});
|
||||
|
||||
router.get('/logs/access', authMiddleware, async function(req, res) {
|
||||
try {
|
||||
var logs = await logger.getAccessLogs(req.user.id, parseInt(req.query.limit) || 50);
|
||||
res.json({ success: true, logs: logs });
|
||||
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
||||
});
|
||||
|
||||
// Client-side error receiver — no auth required (sendBeacon can't send auth headers)
|
||||
router.post('/logs/client-error', function(req, res) {
|
||||
try {
|
||||
var e = req.body || {};
|
||||
console.error('[CLIENT ERROR]', JSON.stringify({
|
||||
type: e.type,
|
||||
message: e.message,
|
||||
source: e.source,
|
||||
line: e.line,
|
||||
col: e.col,
|
||||
stack: e.stack,
|
||||
ip: req.ip,
|
||||
ua: req.headers['user-agent'],
|
||||
time: new Date().toISOString()
|
||||
}));
|
||||
res.status(204).end();
|
||||
} catch(err) { res.status(204).end(); }
|
||||
});
|
||||
|
||||
// Client-side event logging (copy, export — PHI access tracking)
|
||||
router.post('/logs/client-event', authMiddleware, function(req, res) {
|
||||
try {
|
||||
var e = req.body || {};
|
||||
logger.audit(req.user.id, e.action || 'client_event', e.target || '', req, { category: 'phi_access' });
|
||||
res.status(204).end();
|
||||
} catch(err) { res.status(204).end(); }
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
77
src/routes/logs.ts
Normal file
77
src/routes/logs.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import express = require('express');
|
||||
import type { Request, Response } from 'express';
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/logs/usage', authMiddleware, async function (req: Request, res: Response) {
|
||||
try {
|
||||
const days = parseInt(req.query.days as string) || 30;
|
||||
const summary = await logger.getUsageSummary(req.user!.id, days);
|
||||
res.json({ success: true, summary, period: days + ' days' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/logs/audit', authMiddleware, async function (req: Request, res: Response) {
|
||||
try {
|
||||
const logs = await logger.getAuditLogs(req.user!.id, parseInt(req.query.limit as string) || 50);
|
||||
res.json({ success: true, logs });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/logs/api', authMiddleware, async function (req: Request, res: Response) {
|
||||
try {
|
||||
const logs = await logger.getApiLogs(req.user!.id, parseInt(req.query.limit as string) || 50);
|
||||
res.json({ success: true, logs });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/logs/access', authMiddleware, async function (req: Request, res: Response) {
|
||||
try {
|
||||
const logs = await logger.getAccessLogs(req.user!.id, parseInt(req.query.limit as string) || 50);
|
||||
res.json({ success: true, logs });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Client-side error receiver — no auth required (sendBeacon can't send auth headers)
|
||||
router.post('/logs/client-error', function (req: Request, res: Response) {
|
||||
try {
|
||||
const e = req.body || {};
|
||||
console.error('[CLIENT ERROR]', JSON.stringify({
|
||||
type: e.type,
|
||||
message: e.message,
|
||||
source: e.source,
|
||||
line: e.line,
|
||||
col: e.col,
|
||||
stack: e.stack,
|
||||
ip: req.ip,
|
||||
ua: req.headers['user-agent'],
|
||||
time: new Date().toISOString(),
|
||||
}));
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
res.status(204).end();
|
||||
}
|
||||
});
|
||||
|
||||
// Client-side event logging (copy, export — PHI access tracking)
|
||||
router.post('/logs/client-event', authMiddleware, function (req: Request, res: Response) {
|
||||
try {
|
||||
const e = req.body || {};
|
||||
logger.audit(req.user!.id, e.action || 'client_event', e.target || '', req, { category: 'phi_access' });
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
res.status(204).end();
|
||||
}
|
||||
});
|
||||
|
||||
export = router;
|
||||
|
|
@ -1,32 +1,33 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var db = require('../db/database');
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
import express = require('express');
|
||||
import type { Request, Response } from 'express';
|
||||
const db = require('../db/database');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
router.use(authMiddleware);
|
||||
|
||||
// List all sessions for current user
|
||||
router.get('/', async function(req, res) {
|
||||
router.get('/', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var sessions = await db.all(
|
||||
const sessions = await db.all(
|
||||
'SELECT id, ip_address, device_label, created_at, last_activity FROM user_sessions WHERE user_id = ? ORDER BY last_activity DESC',
|
||||
[req.user.id]
|
||||
[req.user!.id]
|
||||
);
|
||||
res.json({ success: true, sessions: sessions, currentSessionId: req.sessionId || null });
|
||||
res.json({ success: true, sessions, currentSessionId: req.sessionId || null });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Revoke a specific session
|
||||
router.delete('/:id', async function(req, res) {
|
||||
router.delete('/:id', async function (req: Request, res: Response) {
|
||||
try {
|
||||
if (req.params.id === req.sessionId) {
|
||||
return res.status(400).json({ error: 'Cannot revoke your current session. Use logout instead.' });
|
||||
}
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'DELETE FROM user_sessions WHERE id = ? AND user_id = ?',
|
||||
[req.params.id, req.user.id]
|
||||
[req.params.id, req.user!.id]
|
||||
);
|
||||
if (result.changes === 0) return res.status(404).json({ error: 'Session not found' });
|
||||
res.json({ success: true });
|
||||
|
|
@ -36,12 +37,12 @@ router.delete('/:id', async function(req, res) {
|
|||
});
|
||||
|
||||
// Revoke all other sessions
|
||||
router.delete('/', async function(req, res) {
|
||||
router.delete('/', async function (req: Request, res: Response) {
|
||||
try {
|
||||
if (!req.sessionId) return res.status(400).json({ error: 'Current session not identified. Log out and log back in.' });
|
||||
var result = await db.run(
|
||||
const result = await db.run(
|
||||
'DELETE FROM user_sessions WHERE user_id = ? AND id != ?',
|
||||
[req.user.id, req.sessionId]
|
||||
[req.user!.id, req.sessionId]
|
||||
);
|
||||
res.json({ success: true, revoked: result.changes });
|
||||
} catch (e) {
|
||||
|
|
@ -49,4 +50,4 @@ router.delete('/', async function(req, res) {
|
|||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export = router;
|
||||
|
|
@ -2,50 +2,54 @@
|
|||
// USER PREFERENCES — STT model & TTS voice selection
|
||||
// ============================================================
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var db = require('../db/database');
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
import express = require('express');
|
||||
import type { Request, Response } from 'express';
|
||||
const db = require('../db/database');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
router.use(authMiddleware);
|
||||
|
||||
interface SttTtsOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
// Get current user's STT/TTS preferences
|
||||
router.get('/preferences', async function(req, res) {
|
||||
router.get('/preferences', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var user = await db.get('SELECT stt_model, tts_voice FROM users WHERE id = ?', [req.user.id]);
|
||||
const user = await db.get('SELECT stt_model, tts_voice FROM users WHERE id = ?', [req.user!.id]);
|
||||
res.json({
|
||||
success: true,
|
||||
stt_model: user?.stt_model || null,
|
||||
tts_voice: user?.tts_voice || null
|
||||
tts_voice: user?.tts_voice || null,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('[Preferences]', err.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update user's STT/TTS preferences
|
||||
router.post('/preferences', async function(req, res) {
|
||||
router.post('/preferences', async function (req: Request, res: Response) {
|
||||
try {
|
||||
var { stt_model, tts_voice } = req.body;
|
||||
|
||||
const { stt_model, tts_voice } = req.body;
|
||||
await db.run(
|
||||
'UPDATE users SET stt_model = ?, tts_voice = ? WHERE id = ?',
|
||||
[stt_model || null, tts_voice || null, req.user.id]
|
||||
[stt_model || null, tts_voice || null, req.user!.id]
|
||||
);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('[Preferences]', err.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get available STT models and TTS voices
|
||||
router.get('/preferences/options', async function(req, res) {
|
||||
router.get('/preferences/options', async function (_req: Request, res: Response) {
|
||||
try {
|
||||
var provider = process.env.TRANSCRIBE_PROVIDER;
|
||||
var ttsProvider = process.env.TTS_PROVIDER;
|
||||
let provider = process.env.TRANSCRIBE_PROVIDER;
|
||||
let ttsProvider = process.env.TTS_PROVIDER;
|
||||
|
||||
// Auto-detect providers if not explicitly set
|
||||
if (!provider) {
|
||||
|
|
@ -63,13 +67,13 @@ router.get('/preferences/options', async function(req, res) {
|
|||
}
|
||||
|
||||
// STT Models
|
||||
var sttModels = [];
|
||||
let sttModels: SttTtsOption[] = [];
|
||||
if (provider === 'google') {
|
||||
sttModels = [
|
||||
{ value: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash (Experimental, fastest)' },
|
||||
{ value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash (Fast, accurate)' },
|
||||
{ value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash (Stable)' },
|
||||
{ value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro (Best quality, slower)' }
|
||||
{ value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro (Best quality, slower)' },
|
||||
];
|
||||
} else if (provider === 'litellm') {
|
||||
sttModels = [
|
||||
|
|
@ -78,16 +82,14 @@ router.get('/preferences/options', async function(req, res) {
|
|||
{ value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash' },
|
||||
{ value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro' },
|
||||
{ value: 'whisper-1', label: 'OpenAI Whisper-1' },
|
||||
{ value: 'whisper-large-v3', label: 'Whisper Large v3' }
|
||||
{ value: 'whisper-large-v3', label: 'Whisper Large v3' },
|
||||
];
|
||||
} else if (provider === 'openai') {
|
||||
sttModels = [
|
||||
{ value: 'whisper-1', label: 'Whisper-1 (OpenAI standard)' }
|
||||
];
|
||||
sttModels = [{ value: 'whisper-1', label: 'Whisper-1 (OpenAI standard)' }];
|
||||
} else if (provider === 'aws') {
|
||||
sttModels = [
|
||||
{ value: 'default', label: 'AWS Transcribe (Standard)' },
|
||||
{ value: 'medical', label: 'AWS Transcribe Medical' }
|
||||
{ value: 'medical', label: 'AWS Transcribe Medical' },
|
||||
];
|
||||
} else if (provider === 'local') {
|
||||
sttModels = [
|
||||
|
|
@ -95,12 +97,12 @@ router.get('/preferences/options', async function(req, res) {
|
|||
{ value: 'base', label: 'Whisper Base' },
|
||||
{ value: 'small', label: 'Whisper Small' },
|
||||
{ value: 'medium', label: 'Whisper Medium' },
|
||||
{ value: 'large', label: 'Whisper Large (best quality)' }
|
||||
{ value: 'large', label: 'Whisper Large (best quality)' },
|
||||
];
|
||||
}
|
||||
|
||||
// TTS Voices
|
||||
var ttsVoices = [];
|
||||
let ttsVoices: SttTtsOption[] = [];
|
||||
if (ttsProvider === 'google') {
|
||||
ttsVoices = [
|
||||
{ value: 'en-US-Journey-F', label: 'Journey (Female, natural)' },
|
||||
|
|
@ -115,7 +117,7 @@ router.get('/preferences/options', async function(req, res) {
|
|||
{ value: 'en-US-Neural2-G', label: 'Neural2 G (Female, standard)' },
|
||||
{ value: 'en-US-Neural2-H', label: 'Neural2 H (Female, standard)' },
|
||||
{ value: 'en-US-Neural2-I', label: 'Neural2 I (Male, standard)' },
|
||||
{ value: 'en-US-Neural2-J', label: 'Neural2 J (Male, standard)' }
|
||||
{ value: 'en-US-Neural2-J', label: 'Neural2 J (Male, standard)' },
|
||||
];
|
||||
} else if (ttsProvider === 'litellm' || ttsProvider === 'openai') {
|
||||
ttsVoices = [
|
||||
|
|
@ -124,7 +126,7 @@ router.get('/preferences/options', async function(req, res) {
|
|||
{ value: 'fable', label: 'Fable (British Male)' },
|
||||
{ value: 'onyx', label: 'Onyx (Deep Male)' },
|
||||
{ value: 'nova', label: 'Nova (Female)' },
|
||||
{ value: 'shimmer', label: 'Shimmer (Soft Female)' }
|
||||
{ value: 'shimmer', label: 'Shimmer (Soft Female)' },
|
||||
];
|
||||
} else if (ttsProvider === 'elevenlabs') {
|
||||
ttsVoices = [
|
||||
|
|
@ -136,22 +138,21 @@ router.get('/preferences/options', async function(req, res) {
|
|||
{ value: 'elli', label: 'Elli (Female, young)' },
|
||||
{ value: 'josh', label: 'Josh (Male, narration)' },
|
||||
{ value: 'arnold', label: 'Arnold (Male, crisp)' },
|
||||
{ value: 'sam', label: 'Sam (Male, raspy)' }
|
||||
{ value: 'sam', label: 'Sam (Male, raspy)' },
|
||||
];
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
sttProvider: provider || 'none',
|
||||
sttModels: sttModels,
|
||||
sttModels,
|
||||
ttsProvider: ttsProvider || 'browser',
|
||||
ttsVoices: ttsVoices
|
||||
ttsVoices,
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('[Preferences]', err.message);
|
||||
res.status(500).json({ error: 'Request failed' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export = router;
|
||||
20
src/types/express.d.ts
vendored
Normal file
20
src/types/express.d.ts
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// ============================================================
|
||||
// EXPRESS REQUEST AUGMENTATION
|
||||
// ============================================================
|
||||
// authMiddleware attaches `user` and `sessionId` to every request
|
||||
// after token verification. Declaring them here so every route
|
||||
// handler sees them typed without casting.
|
||||
// ============================================================
|
||||
|
||||
import type { AuthUser } from '../../shared/types';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: AuthUser;
|
||||
sessionId?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Loading…
Reference in a new issue