chore: clear audit backlog (types sync, email norm, docs, members shortcut)

Finishes all remaining tracked work from the full-app audit.

- types/database.ts synced: added the 8 missing tables (forum_threads,
  comments, space_tiers, space_invites, space_onboarding_steps,
  space_role_labels, space_custom_roles, space_member_custom_roles),
  the comment_entity_type enum (type + Constants), and the missing
  columns (space_members.tier_id/onboarding_completed_at/
  onboarding_progress, secrets.encrypted_value/encryption_version,
  knowledge_base.render_markdown).
- Email canonicalization: new emailField() helper (trim+lowercase)
  applied to signIn/signUp/addMember/updateMember/createContact and
  the import path; prevents case-variant duplicates and sign-in
  mismatches.
- Members directory: per-row "make area lead" picker (admin/board)
  assigns a member to any area-lead role; mirrors Customize.
- Docs refreshed and re-dated: DATABASE_SCHEMA.md (migrations 014-024
  summary), DB_SCHEMA_MAP.md (new-table quick map), API_REFERENCE.md
  (all server actions from migrations 016-024). schema.sql named as
  source of truth.

Backlog is now empty. 271/271 tests pass, build clean. No migration.
This commit is contained in:
Moheeb Zara
2026-05-15 19:01:52 -07:00
parent 86d44f6efc
commit 4c9b55147b
9 changed files with 232 additions and 20 deletions
+11 -6
View File
@@ -33,13 +33,18 @@ export const flexibleDateTime = () =>
// ─── Auth ────────────────────────────────────────────────────────────────────
// Canonicalize every email to trimmed lowercase so case/whitespace variants
// cannot create duplicate members/contacts or fail sign-in.
export const emailField = (msg = 'Invalid email address') =>
z.string().max(200).email(msg).transform(s => s.trim().toLowerCase())
export const signInSchema = z.object({
email: z.string().email('Invalid email address'),
email: emailField(),
password: z.string().min(1, 'Password is required'),
})
export const signUpSchema = z.object({
email: z.string().email('Invalid email address'),
email: emailField(),
password: z.string().min(8, 'Password must be at least 8 characters'),
fullName: z.string().min(1, 'Full name is required').max(100, 'Name is too long'),
action: z.enum(['create', 'join']),
@@ -92,7 +97,7 @@ export const updateProjectStatusSchema = z.object({
// ─── Members ─────────────────────────────────────────────────────────────────
export const addMemberSchema = z.object({
email: z.string().email('Invalid email address'),
email: emailField(),
display_name: z.string().min(1, 'Display name is required').max(100),
phone: z.string().max(20).optional().nullable(),
handle: z.string().max(50).optional().nullable(),
@@ -105,7 +110,7 @@ export const addMemberSchema = z.object({
export const updateMemberSchema = z.object({
memberId: z.string().uuid('Invalid member ID'),
display_name: z.string().min(1).max(100).optional(),
email: z.string().email().optional(),
email: emailField().optional(),
phone: z.string().max(20).optional().nullable(),
handle: z.string().max(50).optional().nullable(),
role: z.enum(['admin', 'board', 'treasurer', 'member', 'associate']).optional(),
@@ -121,7 +126,7 @@ export const updateMemberSchema = z.object({
export const createContactSchema = z.object({
name: z.string().min(1, 'Name is required').max(200),
contact_type: z.enum(['vendor', 'supplier', 'partner', 'landlord', 'city']),
email: z.string().email().optional().nullable(),
email: emailField().optional().nullable(),
phone: z.string().max(20).optional().nullable(),
details: z.string().max(500).optional().nullable(),
note: z.string().max(2000).optional().nullable(),
@@ -524,7 +529,7 @@ export const assignAreaLeadSchema = z.object({
// ─── Bulk import ─────────────────────────────────────────────────────────────
const importEmail = z.string().email('Invalid email').max(200).transform(s => s.trim().toLowerCase())
const importEmail = emailField('Invalid email')
export const importMembersSchema = z.array(
z.object({