Implement Workstream F1: me.get and me.setupProfile procedures

- Add me.get procedure returning user profile with needsSetup flag
- Add me.setupProfile procedure for initial profile setup after signup
- Add nonEmptyString/optionalString schema helpers with tests
- Use Web Crypto API (SubtleCrypto) for Cloudflare Workers compatibility
- Use @formatjs/intl-durationformat for duration formatting
- Remove node:crypto dependency from crypto utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-09 16:29:41 +08:00
parent 93851afe38
commit 860d791125
16 changed files with 205 additions and 61 deletions

View File

@@ -4,6 +4,7 @@
*/
import type { OrgRole } from "@reviq/db-schema";
import { DurationFormat } from "@formatjs/intl-durationformat";
import { ServerClient } from "postmark";
import {
BASE_URL,
@@ -113,37 +114,24 @@ const sendEmail = async (params: SendEmailParams): Promise<EmailResult> => {
// ===== Template Helpers =====
const formatExpiryHours = (hours: number): string => {
if (hours === 1) {
return "1 hour";
}
return `${hours} hours`;
const durationFormatter = new DurationFormat("en", { style: "long" });
const formatExpiryHours = (hours: number): string =>
durationFormatter.format({ hours });
const formatExpiryMinutes = (minutes: number): string =>
durationFormatter.format({ minutes });
const formatExpiryDays = (days: number): string =>
durationFormatter.format({ days });
const roleLabels: Record<OrgRole, string> = {
owner: "Owner",
admin: "Admin",
member: "Member",
};
const formatExpiryMinutes = (minutes: number): string => {
if (minutes === 1) {
return "1 minute";
}
return `${minutes} minutes`;
};
const formatExpiryDays = (days: number): string => {
if (days === 1) {
return "1 day";
}
return `${days} days`;
};
const formatRoleDisplay = (role: OrgRole): string => {
switch (role) {
case "owner":
return "Owner";
case "admin":
return "Admin";
case "member":
return "Member";
}
};
const formatRoleDisplay = (role: OrgRole): string => roleLabels[role];
/**
* Get the correct article (a/an) for a role