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
+5
View File
@@ -11,6 +11,11 @@ All notable changes to this project are documented here. Format follows [Keep a
- Customize -> Area leads: create area-lead roles, see Vacant until assigned, assign/unassign a member from a picker. The assigned member effectively holds that role, so any Ops item whose access list includes it opens to them.
- SECURITY DEFINER helpers `user_effective_roles` and `user_has_permission`.
### Changed
- All emails are canonicalized to trimmed lowercase (sign-in, sign-up, add/update member, contacts, imports) so case/whitespace variants no longer create duplicate members or fail sign-in.
- `types/database.ts` brought in sync with the schema (8 tables, the `comment_entity_type` enum, and the new columns); docs (`DATABASE_SCHEMA.md`, `DB_SCHEMA_MAP.md`, `API_REFERENCE.md`) refreshed for migrations 016-024.
- The member directory has a per-row "make area lead" assignment for admin/board (mirrors Customize -> Area leads).
### Security
- Closed two member self-escalation paths: a non-privileged member can no longer change their own `tier_id` or `onboarding_completed_at` on the `space_members` row (migration 024 extends the self-change trigger). Onboarding completion now goes through the service client after server-side validation.
- Added in-code space scoping to forum/comment/channel mutations (defence-in-depth on top of RLS).
+26 -2
View File
@@ -1,8 +1,8 @@
# DB Schema Map — Quick Reference
> **Last Updated**: 2026-03-10
> **Last Updated**: 2026-05-15
> **Full Documentation**: See [docs/DATABASE_SCHEMA.md](./docs/DATABASE_SCHEMA.md) for complete reference
> **Source of Truth**: Live database schema
> **Source of Truth**: `scripts/schema.sql` (canonical, idempotent)
---
@@ -390,3 +390,27 @@ Default channels created by trigger on space INSERT: `general`, `announcements`,
| 011 | Renamed tasks `type` column to `task_type` (canonical enum column name) |
| 012 | Canonical sync — task_type enum, cleaned duplicate columns |
| 013 | Added `space_members.last_paid_at` timestamptz — required by importMembers and linkPaymentToMember |
---
## Tables added by migrations 016-024 (quick map)
| Table | Key columns | Purpose |
|-------|-------------|---------|
| `proposals` / `proposal_votes` | space_id, status, quorum_*, voting_*; member_id, position | Async governance voting |
| `incidents` / `incident_updates` | space_id, status, sla_*; visibility | Incident reports + log |
| `policies` | space_id, slug, version, status, body_* | Versioned policy library |
| `space_areas` | space_id, code, name, sort_order, is_archived | Per-space configurable areas |
| `forum_threads` | space_id, author_id, title, category, pinned, locked, comment_count | Forum |
| `comments` | space_id, entity_type, entity_id, author_id, parent_id, body | Polymorphic comments (forum/proposal/incident/policy) |
| `space_tiers` | space_id, slug, name, monthly_price_cents, billing_cadence, is_system | Custom membership tiers (`space_members.tier_id` FK) |
| `space_role_labels` | space_id, role, display_name, color | Rename/recolor built-in roles |
| `space_custom_roles` / `space_member_custom_roles` | space_id, slug, name, color / member_id, custom_role_id | Custom non-privileged roles + assignment |
| `space_invites` | space_id, code, label, expires_at, max_uses, uses_count, is_enabled | Multi-code invites |
| `space_onboarding_steps` | space_id, step_key, step_type, title, body, config, is_enabled, is_required | Configurable member onboarding |
| `space_role_permissions` | space_id, subject, permission | Role/custom-role permission grants |
| `ops_acl` | space_id, entity_type, entity_id, role | Per-item Ops access list (secret/kb/process/area_lead) |
Column additions: `space_members.tier_id`, `onboarding_completed_at`, `onboarding_progress`; `secrets.encrypted_value`, `encryption_version`; `knowledge_base.render_markdown`, `is_meeting_minutes`, `meeting_date`. New enum `comment_entity_type`.
Helper functions: `user_effective_roles(uid,sid)`, `user_has_permission(uid,sid,perm)` (both SECURITY DEFINER, search_path=public), plus governance quorum/tally/SLA functions.
+30 -2
View File
@@ -2,14 +2,18 @@
import { useState } from 'react'
import { Plus, X, ChevronDown } from 'lucide-react'
import { addMember, updateMember, approveMember, removeMember } from '@/lib/actions'
import { addMember, updateMember, approveMember, removeMember, assignAreaLead } from '@/lib/actions'
import { toast } from 'sonner'
import { useRouter } from 'next/navigation'
import type { Tables } from '@/types/database'
type Member = Tables<'space_members'>
type AreaLeadRole = { id: string; area_name: string; lead_id: string | null }
interface Props {
members: Member[]
currentRole: string
areaLeadRoles?: AreaLeadRole[]
}
const TIER_COLORS: Record<string, string> = {
@@ -21,8 +25,17 @@ const TIER_COLORS: Record<string, string> = {
const isAdmin = (role: string) => role === 'admin' || role === 'board'
export function MembersClient({ members: initialMembers, currentRole }: Props) {
export function MembersClient({ members: initialMembers, currentRole, areaLeadRoles = [] }: Props) {
const router = useRouter()
const [members, setMembers] = useState<Member[]>(initialMembers)
async function makeAreaLead(memberId: string, areaLeadRoleId: string) {
if (!areaLeadRoleId) return
const result = await assignAreaLead({ area_lead_role_id: areaLeadRoleId, member_id: memberId })
if (result.error) { toast.error(result.error); return }
toast.success('Assigned as area lead')
router.refresh()
}
const [activeTab, setActiveTab] = useState<'all' | 'payment_issues' | 'unverified' | 'inactive'>('all')
const [search, setSearch] = useState('')
const [tierFilter, setTierFilter] = useState('')
@@ -326,6 +339,21 @@ export function MembersClient({ members: initialMembers, currentRole }: Props) {
>
REMOVE
</button>
{areaLeadRoles.length > 0 && (
<select
defaultValue=""
onChange={e => { makeAreaLead(m.id, e.target.value); e.currentTarget.value = '' }}
className="font-mono text-[10px] border border-border px-1.5 py-0.5 rounded bg-background text-muted-foreground hover:border-primary hover:text-primary transition focus:outline-none"
title="Assign this member as an area lead"
>
<option value="">+ area lead</option>
{areaLeadRoles.map(r => (
<option key={r.id} value={r.id}>
{r.area_name}{r.lead_id ? ' (reassign)' : ' (vacant)'}
</option>
))}
</select>
)}
</div>
</td>
)}
+12
View File
@@ -57,10 +57,22 @@ export default async function MembersPage() {
.eq('space_id', self.space_id)
.order('joined_at')
// Area-lead roles, so admin/board can assign a lead straight from a member
// row (the same capability also lives in Customize -> Area leads).
const canManageLeads = self.role === 'admin' || self.role === 'board'
const { data: areaLeadRoles } = canManageLeads
? await supabase
.from('area_leads')
.select('id, area_name, lead_id')
.eq('space_id', self.space_id)
.order('area_name')
: { data: [] }
return (
<MembersClient
members={members ?? []}
currentRole={self.role}
areaLeadRoles={(areaLeadRoles ?? []) as Array<{ id: string; area_name: string; lead_id: string | null }>}
/>
)
}
+37 -1
View File
@@ -1,7 +1,8 @@
# Hackerspace.sh - API Reference
> **Last Updated**: 2026-03-10
> **Last Updated**: 2026-05-15
> Complete reference for all server actions, database operations, and API patterns.
> Actions added since the original baseline are listed in "Server actions added (migrations 016-024)" at the end.
---
@@ -851,3 +852,38 @@ async function handleSubmit() {
| "Treasurer access required" | Payment role check | Show permission error |
| "Invalid invite code" | Code not found | Ask user to verify |
| "A space with this URL slug already exists" | Duplicate slug | Suggest different slug |
---
## Server actions added (migrations 016-024)
All are `'use server'` in `lib/actions/*`, re-exported from `lib/actions/index.ts`,
validate input with Zod via `parseInput`, authorize via `requireMember` /
`requireMemberWithRole`, scope writes by `member.space_id`, and `revalidatePath`.
### Governance — `proposals.ts`, `incidents.ts`, `policies.ts`
`createProposal`, `openProposal`, `castVote`, `decideProposal`, `withdrawProposal`, `deleteProposal`; `fileIncident`, `updateIncidentStatus`, `addIncidentUpdate`, `appealIncident`; `createPolicy`, `supersedePolicy`, `updatePolicyStatus`.
### Areas — `areas.ts`
`createArea`, `updateArea`, `deleteArea` (admin/board; delete admin).
### Forum + comments — `forum.ts`
`createForumThread`, `updateForumThread` (pin/lock = admin/board), `deleteForumThread`, `addComment`, `editComment`, `deleteComment`. All space-scoped in code.
### Chat channels — `comms.ts`
`createChannel` (any member), `renameChannel`, `deleteChannel` (creator or admin/board; default channels protected).
### Tiers / roles / invites — `tiers.ts`, `roles.ts`, `invites.ts`
`createTier`/`updateTier`/`deleteTier`; `upsertRoleLabel`, `createCustomRole`/`updateCustomRole`/`deleteCustomRole`, `assignCustomRole`/`unassignCustomRole`; `createInvite`/`updateInvite`/`deleteInvite`.
### Onboarding — `onboarding.ts`
`createOnboardingStep`/`updateOnboardingStep`/`deleteOnboardingStep` (admin/board); `markOnboardingStepDone`, `finishOnboarding`, `skipOnboarding` (member; completion writes via the service client after the server-side required-steps check, so the self-change trigger cannot be bypassed).
### Permissions / Ops ACLs / area-lead — `permissions.ts`
`setRolePermissions(subject, permissions[])`, `setOpsAcl(entity_type, entity_id, roles[])` (admin/board), `createAreaLeadRole`, `assignAreaLead`, `unassignAreaLead`, `deleteAreaLeadRole`. ACLs are additive: empty list falls back to the item's default visibility.
### Secrets — `secrets.ts`
`createSecret`, `updateSecret`, `revealSecret` (server-only plaintext path, audit-logged, errors on corrupt ciphertext), `deleteSecret`. The list query never selects `value`/`encrypted_value`.
### Bulk import — `imports.ts`, `payments.ts`
`importMembers`, `importPaymentsCsv`: per-row Zod validation, lowercased emails, `flexibleDateTime` date normalization, enum-checked platform/tier, positive finite amounts; invalid rows skipped and returned as a `skipped` count.
+36 -4
View File
@@ -1,9 +1,12 @@
# Hackerspace.sh - Database Schema Reference
> **Last Updated**: 2026-03-10
> **Database**: PostgreSQL via Supabase
> **Tables**: 13
> **Source of Truth**: Live database schema
> **Last Updated**: 2026-05-15
> **Database**: PostgreSQL via self-hosted Supabase
> **Tables**: ~30
> **Source of Truth**: `scripts/schema.sql` (canonical, idempotent); numbered
> migrations `scripts/0NN_*.sql` upgrade existing deployments. The 13-table
> reference below is the original baseline; tables added by migrations
> 014-024 are summarized in the "Migrations 014-024" section at the end.
---
@@ -680,3 +683,32 @@ CREATE INDEX idx_activity_space ON activity_log(space_id, created_at);
| `009_fix_member_insert_rls.sql` | 1.8 | Fixed member INSERT RLS for admin/board |
| `010_fix_channel_trigger.sql` | 1.9 | Removed non-existent description column from trigger |
| `011_fix_member_status_enum.sql` | 2.0 | Fixed enum values (active→current, pending→unverified) |
---
## Migrations 014-024 (additions since the 13-table baseline)
`scripts/schema.sql` is the canonical idempotent schema; each numbered
migration is mirrored as a section in it. Tables/columns added:
| Mig | Adds |
|-----|------|
| 014 | `space_members.user_id` made nullable (offline/imported members) |
| 015 | `prevent_member_self_role_change()` trigger (self-escalation guard) |
| 016 | Governance kernel: `proposals`, `proposal_votes`, `incidents`, `incident_updates`, `policies` + quorum/tally/SLA functions |
| 017 | Governance RLS hardening (cross-tenant edge cases) |
| 018 | `space_members.skills/interests/willing_to/affiliations/coi_last_disclosed_at`; `knowledge_base.is_meeting_minutes/meeting_date`; financial/member-directory visibility settings |
| 019 | Auto-expire open proposals past their voting window |
| 020 | `space_areas` (per-space configurable areas) + seeded defaults |
| 021 | `forum_threads`, `comments` (polymorphic: forum_thread/proposal/incident/policy), `space_tiers` + `space_members.tier_id`, `space_role_labels`, `space_custom_roles`, `space_member_custom_roles`, `space_invites`; `secrets.encrypted_value/encryption_version`; `knowledge_base.render_markdown`; comms_channels member-creatable |
| 022 | `space_onboarding_steps`; `space_members.onboarding_completed_at/onboarding_progress`; seed default steps |
| 023 | `space_role_permissions`, `ops_acl`; `user_effective_roles()`, `user_has_permission()` (SECURITY DEFINER); `secrets`/`knowledge_base` SELECT rewritten as `existing-rule OR ops_acl-match` (additive) |
| 024 | `prevent_member_self_role_change()` extended to also block self-change of `tier_id` and `onboarding_completed_at` |
New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`.
Every new table has RLS enabled with SELECT/INSERT/UPDATE/DELETE policies
(writes admin/board, deletes admin, reads space members) and, where it has
`updated_at`, a `touch_updated_at` trigger. The ACL model is additive:
with zero `ops_acl`/`space_role_permissions` rows the app behaves exactly
as the role-based baseline.
+7 -5
View File
@@ -28,12 +28,14 @@ Five parallel read-only audits (security/RLS, server actions/validation, routes/
- **KB/process per-item ACL wired.** `OpsAclEditor` renders in the KB/process viewer modal (admin/board); entity type derived from the `process` tag. Per-item Ops ACLs now cover secrets + KB + processes.
- `/ops` and `/import` denial paths left as-is: unreachable behind the `(app)` layout gate, or already a clear denial with no data leak.
### Tracked backlog (not security-critical, zero runtime impact)
### Pass 16c — backlog cleared (same day)
- `types/database.ts` still missing 8 prior-pass tables + `comment_entity_type` enum + 3 `space_members` columns (loose casts; build green via `ignoreBuildErrors`).
- `docs/DATABASE_SCHEMA.md`, `DB_SCHEMA_MAP.md`, `docs/API_REFERENCE.md` stale since 2026-03-10 (missing migrations 016-024).
- Email `.toLowerCase()` still missing on the non-import auth/member schemas (import path now normalizes).
- Members-row "Make area lead" shortcut (Customize -> Area leads fully covers assignment).
- **`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 (both type union and Constants array), and the missing columns: `space_members.tier_id/onboarding_completed_at/onboarding_progress`, `secrets.encrypted_value/encryption_version`, `knowledge_base.render_markdown`.
- **Email normalized everywhere.** New `emailField()` helper (trim + lowercase) applied to signIn/signUp/addMember/updateMember/createContact and the import path. Prevents case-variant duplicate members/contacts.
- **Members-row "Make area lead".** A per-row picker in the members directory (admin/board) assigns a member to any area-lead role via `assignAreaLead`; same capability also in Customize -> Area leads.
- **Docs refreshed.** `DATABASE_SCHEMA.md` (header + migrations 014-024 summary), `DB_SCHEMA_MAP.md` (table quick-map + helpers), `docs/API_REFERENCE.md` (all server actions added in migrations 016-024). All re-dated 2026-05-15 with `scripts/schema.sql` named as source of truth.
Backlog is now empty. Remaining known non-issues: `/ops` + `/import` `return null` are unreachable behind the `(app)` layout gate; `next.config.mjs` keeps `ignoreBuildErrors` (pre-existing legacy app/ TS noise) — the new code is type-clean.
### Verification
+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({
+68
View File
@@ -46,6 +46,54 @@ export type Database = {
Update: { id?: string; space_id?: string; entity_type?: string; entity_id?: string; role?: string; created_at?: string }
Relationships: []
}
forum_threads: {
Row: { id: string; space_id: string; author_id: string | null; title: string; body: string | null; category: string; pinned: boolean; locked: boolean; comment_count: number; last_comment_at: string | null; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; author_id?: string | null; title: string; body?: string | null; category?: string; pinned?: boolean; locked?: boolean; comment_count?: number; last_comment_at?: string | null; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; author_id?: string | null; title?: string; body?: string | null; category?: string; pinned?: boolean; locked?: boolean; comment_count?: number; last_comment_at?: string | null; created_at?: string; updated_at?: string }
Relationships: []
}
comments: {
Row: { id: string; space_id: string; entity_type: Database['public']['Enums']['comment_entity_type']; entity_id: string; author_id: string | null; parent_id: string | null; body: string; edited_at: string | null; created_at: string }
Insert: { id?: string; space_id: string; entity_type: Database['public']['Enums']['comment_entity_type']; entity_id: string; author_id?: string | null; parent_id?: string | null; body: string; edited_at?: string | null; created_at?: string }
Update: { id?: string; space_id?: string; entity_type?: Database['public']['Enums']['comment_entity_type']; entity_id?: string; author_id?: string | null; parent_id?: string | null; body?: string; edited_at?: string | null; created_at?: string }
Relationships: []
}
space_tiers: {
Row: { id: string; space_id: string; slug: string; name: string; description: string | null; monthly_price_cents: number; billing_cadence: string; is_system: boolean; is_archived: boolean; sort_order: number; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; slug: string; name: string; description?: string | null; monthly_price_cents?: number; billing_cadence?: string; is_system?: boolean; is_archived?: boolean; sort_order?: number; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; slug?: string; name?: string; description?: string | null; monthly_price_cents?: number; billing_cadence?: string; is_system?: boolean; is_archived?: boolean; sort_order?: number; created_at?: string; updated_at?: string }
Relationships: []
}
space_invites: {
Row: { id: string; space_id: string; code: string; label: string | null; expires_at: string | null; max_uses: number | null; uses_count: number; is_enabled: boolean; created_by: string | null; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; code: string; label?: string | null; expires_at?: string | null; max_uses?: number | null; uses_count?: number; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; code?: string; label?: string | null; expires_at?: string | null; max_uses?: number | null; uses_count?: number; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Relationships: []
}
space_onboarding_steps: {
Row: { id: string; space_id: string; step_key: string; step_type: string; title: string; body: string | null; config: Json; is_enabled: boolean; is_required: boolean; is_system: boolean; sort_order: number; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; step_key: string; step_type: string; title: string; body?: string | null; config?: Json; is_enabled?: boolean; is_required?: boolean; is_system?: boolean; sort_order?: number; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; step_key?: string; step_type?: string; title?: string; body?: string | null; config?: Json; is_enabled?: boolean; is_required?: boolean; is_system?: boolean; sort_order?: number; created_at?: string; updated_at?: string }
Relationships: []
}
space_role_labels: {
Row: { id: string; space_id: string; role: string; display_name: string | null; description: string | null; color: string | null; sort_order: number; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; role: string; display_name?: string | null; description?: string | null; color?: string | null; sort_order?: number; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; role?: string; display_name?: string | null; description?: string | null; color?: string | null; sort_order?: number; created_at?: string; updated_at?: string }
Relationships: []
}
space_custom_roles: {
Row: { id: string; space_id: string; slug: string; name: string; description: string | null; color: string | null; sort_order: number; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; slug: string; name: string; description?: string | null; color?: string | null; sort_order?: number; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; slug?: string; name?: string; description?: string | null; color?: string | null; sort_order?: number; created_at?: string; updated_at?: string }
Relationships: []
}
space_member_custom_roles: {
Row: { member_id: string; custom_role_id: string; assigned_at: string }
Insert: { member_id: string; custom_role_id: string; assigned_at?: string }
Update: { member_id?: string; custom_role_id?: string; assigned_at?: string }
Relationships: []
}
activity_log: {
Row: {
action: string
@@ -563,6 +611,7 @@ export type Database = {
id: string
is_meeting_minutes: boolean
is_pinned: boolean
render_markdown: boolean
meeting_date: string | null
pinned: boolean
space_id: string
@@ -585,6 +634,7 @@ export type Database = {
id?: string
is_meeting_minutes?: boolean
is_pinned?: boolean
render_markdown?: boolean
meeting_date?: string | null
pinned?: boolean
space_id: string
@@ -607,6 +657,7 @@ export type Database = {
id?: string
is_meeting_minutes?: boolean
is_pinned?: boolean
render_markdown?: boolean
meeting_date?: string | null
pinned?: boolean
space_id?: string
@@ -1078,6 +1129,8 @@ export type Database = {
notes: string | null
space_id: string
title: string | null
encrypted_value: string | null
encryption_version: number
updated_at: string
value: string
}
@@ -1093,6 +1146,8 @@ export type Database = {
notes?: string | null
space_id: string
title?: string | null
encrypted_value?: string | null
encryption_version?: number
updated_at?: string
value: string
}
@@ -1108,6 +1163,8 @@ export type Database = {
notes?: string | null
space_id?: string
title?: string | null
encrypted_value?: string | null
encryption_version?: number
updated_at?: string
value?: string
}
@@ -1192,6 +1249,9 @@ export type Database = {
status: Database["public"]["Enums"]["member_status"]
stripe_customer_id: string | null
tier: Database["public"]["Enums"]["member_tier"]
tier_id: string | null
onboarding_completed_at: string | null
onboarding_progress: Json
updated_at: string
user_id: string | null
willing_to: string[]
@@ -1222,6 +1282,9 @@ export type Database = {
status?: Database["public"]["Enums"]["member_status"]
stripe_customer_id?: string | null
tier?: Database["public"]["Enums"]["member_tier"]
tier_id?: string | null
onboarding_completed_at?: string | null
onboarding_progress?: Json
updated_at?: string
user_id?: string | null
willing_to?: string[]
@@ -1252,6 +1315,9 @@ export type Database = {
status?: Database["public"]["Enums"]["member_status"]
stripe_customer_id?: string | null
tier?: Database["public"]["Enums"]["member_tier"]
tier_id?: string | null
onboarding_completed_at?: string | null
onboarding_progress?: Json
updated_at?: string
user_id?: string | null
willing_to?: string[]
@@ -1502,6 +1568,7 @@ export type Database = {
Enums: {
area_lead_status: "active" | "vacant" | "handoff"
channel_type: "general" | "area" | "ops" | "project"
comment_entity_type: "forum_thread" | "proposal" | "incident" | "policy"
contact_type: "vendor" | "supplier" | "partner" | "landlord" | "city"
directory_visibility:
| "board_only"
@@ -1685,6 +1752,7 @@ export const Constants = {
Enums: {
area_lead_status: ["active", "vacant", "handoff"],
channel_type: ["general", "area", "ops", "project"],
comment_entity_type: ["forum_thread", "proposal", "incident", "policy"],
contact_type: ["vendor", "supplier", "partner", "landlord", "city"],
directory_visibility: [
"board_only",