feat(attendance): add notification delivery and NFC security foundation
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
CREATE TABLE IF NOT EXISTS device_push_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
staff_id UUID REFERENCES staffs(id) ON DELETE SET NULL,
|
||||
business_membership_id UUID REFERENCES business_memberships(id) ON DELETE SET NULL,
|
||||
vendor_membership_id UUID REFERENCES vendor_memberships(id) ON DELETE SET NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'FCM'
|
||||
CHECK (provider IN ('FCM', 'APNS', 'WEB_PUSH')),
|
||||
platform TEXT NOT NULL
|
||||
CHECK (platform IN ('IOS', 'ANDROID', 'WEB')),
|
||||
push_token TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
device_id TEXT,
|
||||
app_version TEXT,
|
||||
app_build TEXT,
|
||||
locale TEXT,
|
||||
timezone TEXT,
|
||||
notifications_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
invalidated_at TIMESTAMPTZ,
|
||||
invalidation_reason TEXT,
|
||||
last_registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_delivery_at TIMESTAMPTZ,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT chk_device_push_tokens_membership_scope
|
||||
CHECK (
|
||||
business_membership_id IS NOT NULL
|
||||
OR vendor_membership_id IS NOT NULL
|
||||
OR staff_id IS NOT NULL
|
||||
OR user_id IS NOT NULL
|
||||
)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_device_push_tokens_provider_hash
|
||||
ON device_push_tokens (provider, token_hash);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_push_tokens_user_active
|
||||
ON device_push_tokens (user_id, last_seen_at DESC)
|
||||
WHERE invalidated_at IS NULL AND notifications_enabled = TRUE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_push_tokens_staff_active
|
||||
ON device_push_tokens (staff_id, last_seen_at DESC)
|
||||
WHERE staff_id IS NOT NULL AND invalidated_at IS NULL AND notifications_enabled = TRUE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notification_deliveries (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
notification_outbox_id UUID NOT NULL REFERENCES notification_outbox(id) ON DELETE CASCADE,
|
||||
device_push_token_id UUID REFERENCES device_push_tokens(id) ON DELETE SET NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'FCM'
|
||||
CHECK (provider IN ('FCM', 'APNS', 'WEB_PUSH')),
|
||||
delivery_status TEXT NOT NULL
|
||||
CHECK (delivery_status IN ('SIMULATED', 'SENT', 'FAILED', 'INVALID_TOKEN', 'SKIPPED')),
|
||||
provider_message_id TEXT,
|
||||
attempt_number INTEGER NOT NULL DEFAULT 1 CHECK (attempt_number >= 1),
|
||||
error_code TEXT,
|
||||
error_message TEXT,
|
||||
response_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
sent_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_deliveries_outbox_created
|
||||
ON notification_deliveries (notification_outbox_id, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_deliveries_token_created
|
||||
ON notification_deliveries (device_push_token_id, created_at DESC)
|
||||
WHERE device_push_token_id IS NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS attendance_security_proofs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
assignment_id UUID NOT NULL REFERENCES assignments(id) ON DELETE CASCADE,
|
||||
shift_id UUID NOT NULL REFERENCES shifts(id) ON DELETE CASCADE,
|
||||
staff_id UUID NOT NULL REFERENCES staffs(id) ON DELETE RESTRICT,
|
||||
actor_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
event_type TEXT NOT NULL
|
||||
CHECK (event_type IN ('CLOCK_IN', 'CLOCK_OUT')),
|
||||
source_type TEXT NOT NULL
|
||||
CHECK (source_type IN ('NFC', 'GEO', 'QR', 'MANUAL', 'SYSTEM')),
|
||||
device_id TEXT,
|
||||
nfc_tag_uid TEXT,
|
||||
proof_nonce TEXT,
|
||||
proof_timestamp TIMESTAMPTZ,
|
||||
request_fingerprint TEXT,
|
||||
attestation_provider TEXT
|
||||
CHECK (attestation_provider IS NULL OR attestation_provider IN ('PLAY_INTEGRITY', 'APP_ATTEST', 'DEVICE_CHECK')),
|
||||
attestation_token_hash TEXT,
|
||||
attestation_status TEXT NOT NULL DEFAULT 'NOT_PROVIDED'
|
||||
CHECK (attestation_status IN ('NOT_PROVIDED', 'RECORDED_UNVERIFIED', 'VERIFIED', 'REJECTED', 'BYPASSED')),
|
||||
attestation_reason TEXT,
|
||||
object_uri TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_attendance_security_proofs_nonce
|
||||
ON attendance_security_proofs (tenant_id, proof_nonce)
|
||||
WHERE proof_nonce IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_attendance_security_proofs_assignment_created
|
||||
ON attendance_security_proofs (assignment_id, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_attendance_security_proofs_staff_created
|
||||
ON attendance_security_proofs (staff_id, created_at DESC);
|
||||
Reference in New Issue
Block a user