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:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user