feat(attendance): add notification delivery and NFC security foundation

This commit is contained in:
zouantchaw
2026-03-16 17:06:17 +01:00
parent 5d8240ed51
commit 73287f42bd
21 changed files with 1734 additions and 36 deletions

View File

@@ -17,6 +17,8 @@ const preferredLocationSchema = z.object({
const hhmmSchema = z.string().regex(/^\d{2}:\d{2}$/, 'Time must use HH:MM format');
const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must use YYYY-MM-DD format');
const clockInModeSchema = z.enum(['NFC_REQUIRED', 'GEO_REQUIRED', 'EITHER']);
const pushProviderSchema = z.enum(['FCM', 'APNS', 'WEB_PUSH']);
const pushPlatformSchema = z.enum(['IOS', 'ANDROID', 'WEB']);
const shiftPositionSchema = z.object({
roleId: z.string().uuid().optional(),
@@ -205,6 +207,11 @@ export const staffClockInSchema = z.object({
longitude: z.number().min(-180).max(180).optional(),
accuracyMeters: z.number().int().nonnegative().optional(),
capturedAt: z.string().datetime().optional(),
proofNonce: z.string().min(8).max(255).optional(),
proofTimestamp: z.string().datetime().optional(),
attestationProvider: z.enum(['PLAY_INTEGRITY', 'APP_ATTEST', 'DEVICE_CHECK']).optional(),
attestationToken: z.string().min(16).max(20000).optional(),
isMockLocation: z.boolean().optional(),
notes: z.string().max(2000).optional(),
overrideReason: z.string().max(2000).optional(),
rawPayload: z.record(z.any()).optional(),
@@ -224,6 +231,11 @@ export const staffClockOutSchema = z.object({
longitude: z.number().min(-180).max(180).optional(),
accuracyMeters: z.number().int().nonnegative().optional(),
capturedAt: z.string().datetime().optional(),
proofNonce: z.string().min(8).max(255).optional(),
proofTimestamp: z.string().datetime().optional(),
attestationProvider: z.enum(['PLAY_INTEGRITY', 'APP_ATTEST', 'DEVICE_CHECK']).optional(),
attestationToken: z.string().min(16).max(20000).optional(),
isMockLocation: z.boolean().optional(),
notes: z.string().max(2000).optional(),
overrideReason: z.string().max(2000).optional(),
breakMinutes: z.number().int().nonnegative().optional(),
@@ -253,6 +265,27 @@ export const staffLocationBatchSchema = z.object({
message: 'assignmentId or shiftId is required',
});
export const pushTokenRegisterSchema = z.object({
provider: pushProviderSchema.default('FCM'),
platform: pushPlatformSchema,
pushToken: z.string().min(16).max(4096),
deviceId: z.string().max(255).optional(),
appVersion: z.string().max(80).optional(),
appBuild: z.string().max(80).optional(),
locale: z.string().max(32).optional(),
timezone: z.string().max(64).optional(),
notificationsEnabled: z.boolean().optional(),
metadata: z.record(z.any()).optional(),
});
export const pushTokenDeleteSchema = z.object({
tokenId: z.string().uuid().optional(),
pushToken: z.string().min(16).max(4096).optional(),
reason: z.string().max(255).optional(),
}).refine((value) => value.tokenId || value.pushToken, {
message: 'tokenId or pushToken is required',
});
export const staffProfileSetupSchema = z.object({
fullName: z.string().min(2).max(160),
bio: z.string().max(5000).optional(),