added qr, cleaned up profile views, and added the critical alerts
This commit is contained in:
@@ -44,6 +44,7 @@ import {
|
||||
deleteMedicationForBird,
|
||||
deleteVetVisitForBird,
|
||||
getBirdById,
|
||||
getBirdByPublicProfileCode,
|
||||
listBirds,
|
||||
listDueBirdMilestoneReminders,
|
||||
listMemorializedBirds,
|
||||
@@ -231,6 +232,8 @@ const lostBirdReportSchema = z.object({
|
||||
message: z.string().trim().max(1000).optional().or(z.literal('')),
|
||||
});
|
||||
|
||||
const publicProfileCodeSchema = z.string().trim().regex(/^[A-Za-z0-9_-]{8,32}$/);
|
||||
|
||||
const birdSchema = z.object({
|
||||
name: z.string().trim().min(1).max(120),
|
||||
tagId: z.string().trim().max(80).optional().or(z.literal('')),
|
||||
@@ -245,6 +248,7 @@ const birdSchema = z.object({
|
||||
photoDataUrl: z.union([photoDataUrlSchema, photoUrlSchema, z.literal('')]).optional(),
|
||||
notifyOnDob: z.boolean().optional(),
|
||||
notifyOnGotchaDay: z.boolean().optional(),
|
||||
publicProfileEnabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const memorializeBirdSchema = z.object({
|
||||
@@ -325,6 +329,7 @@ const hashToken = (token: string) => crypto.createHash('sha256').update(token).d
|
||||
const createSessionToken = () => crypto.randomBytes(32).toString('hex');
|
||||
const photoAccessSecret = process.env.PHOTO_ACCESS_SECRET?.trim() || process.env.S3_SECRET_ACCESS_KEY?.trim() || createSessionToken();
|
||||
const createIntegrationToken = () => `flpt_${crypto.randomBytes(24).toString('hex')}`;
|
||||
const createPublicProfileCode = () => crypto.randomBytes(9).toString('base64url');
|
||||
const createRandomId = () => crypto.randomUUID();
|
||||
const createCodeVerifier = () => crypto.randomBytes(32).toString('base64url');
|
||||
const createCodeChallenge = (verifier: string) => crypto.createHash('sha256').update(verifier).digest('base64url');
|
||||
@@ -579,6 +584,8 @@ const normalizeBird = (row: BirdRow) => ({
|
||||
photoUpdatedAt: row.photo_updated_at,
|
||||
notifyOnDob: row.notify_on_dob,
|
||||
notifyOnGotchaDay: row.notify_on_gotcha_day,
|
||||
publicProfileCode: row.public_profile_code ?? null,
|
||||
publicProfileEnabled: row.public_profile_enabled ?? false,
|
||||
memorializedAt: row.memorialized_at,
|
||||
memorializedOn: row.memorialized_on,
|
||||
memorialNote: row.memorial_note,
|
||||
@@ -588,6 +595,15 @@ const normalizeBird = (row: BirdRow) => ({
|
||||
latestRecordedOn: row.latest_recorded_on,
|
||||
});
|
||||
|
||||
const normalizePublicBirdProfile = (row: BirdRow) => ({
|
||||
id: row.id,
|
||||
workspaceId: row.workspace_id,
|
||||
name: row.name,
|
||||
gender: row.gender,
|
||||
dateOfBirth: row.date_of_birth,
|
||||
photoDataUrl: getBirdPhotoUrl(row),
|
||||
});
|
||||
|
||||
const normalizeWeight = (row: WeightRow) => ({
|
||||
id: row.id,
|
||||
birdId: row.bird_id,
|
||||
@@ -1949,6 +1965,28 @@ app.post('/api/lost-bird/report', lostBirdReportLimiter, async (req: Request, re
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/public/birds/:publicProfileCode', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const parsed = publicProfileCodeSchema.safeParse(req.params.publicProfileCode);
|
||||
|
||||
if (!parsed.success) {
|
||||
res.status(404).json({ error: 'Public bird profile not found.' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const bird = await getBirdByPublicProfileCode(parsed.data);
|
||||
|
||||
if (!bird) {
|
||||
res.status(404).json({ error: 'Public bird profile not found.' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ bird: normalizePublicBirdProfile(bird) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/auth/providers', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
providers: Object.values(oauthProviders).map((provider) => ({
|
||||
@@ -2858,6 +2896,8 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o
|
||||
photoUpdatedAt: photoStorage.photoUpdatedAt,
|
||||
notifyOnDob: parsed.data.notifyOnDob ?? false,
|
||||
notifyOnGotchaDay: parsed.data.notifyOnGotchaDay ?? false,
|
||||
publicProfileCode: createPublicProfileCode(),
|
||||
publicProfileEnabled: parsed.data.publicProfileEnabled ?? false,
|
||||
});
|
||||
|
||||
uploadedObjectKeyToCleanup = null;
|
||||
@@ -2998,6 +3038,8 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR
|
||||
photoUpdatedAt: photoStorage.photoUpdatedAt,
|
||||
notifyOnDob: parsed.data.notifyOnDob ?? false,
|
||||
notifyOnGotchaDay: parsed.data.notifyOnGotchaDay ?? false,
|
||||
publicProfileCode: existingBird.public_profile_code ?? createPublicProfileCode(),
|
||||
publicProfileEnabled: parsed.data.publicProfileEnabled ?? false,
|
||||
});
|
||||
|
||||
if (!bird) {
|
||||
|
||||
Reference in New Issue
Block a user