docs: correct ARCHITECTURE layout/§7, note intentional settings + single-space
Project tree + section 7 module paths now reflect the per-domain lib/actions/* reality (no monolithic lib/actions.ts); explicit note that /settings admin secret loading is by-design same-space management; getAuthMember single-space invariant documented in code. HANDOFF pass-31.
This commit is contained in:
+23
-15
@@ -90,15 +90,18 @@ Hackerspace.sh is a comprehensive member management platform designed for hacker
|
||||
│ │ ├── client.ts # Browser client
|
||||
│ │ ├── proxy.ts # Middleware session refresh
|
||||
│ │ └── server.ts # Server Component client
|
||||
│ ├── actions.ts # All server actions (CRUD)
|
||||
│ ├── auth-actions.ts # Auth-specific actions
|
||||
│ ├── types.ts # TypeScript interfaces
|
||||
│ ├── actions/ # Server actions, one module per domain
|
||||
│ │ ├── index.ts # Barrel (re-exports every domain module)
|
||||
│ │ ├── tasks.ts projects.ts members.ts payments.ts …
|
||||
│ │ └── classes.ts equipment.ts door.ts presence.ts …
|
||||
│ ├── *-logic.ts # Pure, unit-tested decision logic
|
||||
│ ├── *-guard.ts # Per-feature server-component guards
|
||||
│ ├── auth-helpers.ts # requireMember / permission gates
|
||||
│ └── utils.ts # Utility functions (cn)
|
||||
├── scripts/ # SQL migrations
|
||||
│ ├── 000_reset_schema.sql # Schema reset (dev only)
|
||||
│ ├── 001_create_schema.sql # Initial schema
|
||||
│ ├── 002-011_*.sql # Incremental fixes
|
||||
│ └── patch-dashboard.mjs # Utility script
|
||||
├── scripts/ # SQL
|
||||
│ ├── schema.sql # Canonical, idempotent (source of truth)
|
||||
│ ├── 0NN_*.sql # Numbered incremental migrations (→ 038)
|
||||
│ └── seed.sql # Optional seed data
|
||||
├── middleware.ts # Auth middleware
|
||||
├── docs/ # This documentation
|
||||
└── [config files] # next.config, tsconfig, etc.
|
||||
@@ -244,7 +247,7 @@ Admin (full access)
|
||||
| `createSpace` | spaceName, spaceSlug, spaceCity, displayName | Authenticated | Create new space |
|
||||
| `joinSpace` | inviteCode, displayName | Authenticated | Join existing space |
|
||||
|
||||
### 7.2 Tasks (`lib/actions.ts`)
|
||||
### 7.2 Tasks (`lib/actions/tasks.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -253,7 +256,7 @@ Admin (full access)
|
||||
| `completeTask` | taskId | Claimant |
|
||||
| `deleteTask` | taskId | Member |
|
||||
|
||||
### 7.3 Projects (`lib/actions.ts`)
|
||||
### 7.3 Projects (`lib/actions/projects.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -261,7 +264,7 @@ Admin (full access)
|
||||
| `updateProjectStatus` | projectId, status | Member |
|
||||
| `deleteProject` | projectId | Member |
|
||||
|
||||
### 7.4 Members (`lib/actions.ts`)
|
||||
### 7.4 Members (`lib/actions/members.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -271,7 +274,7 @@ Admin (full access)
|
||||
| `removeMember` | memberId | Admin |
|
||||
| `importMembers` | rows[] | Admin/Board |
|
||||
|
||||
### 7.5 Payments (`lib/actions.ts`)
|
||||
### 7.5 Payments (`lib/actions/payments.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -279,7 +282,7 @@ Admin (full access)
|
||||
| `linkPaymentToMember` | paymentId, memberId | Treasurer/Admin |
|
||||
| `importPaymentsCsv` | rows[] | Treasurer/Admin |
|
||||
|
||||
### 7.6 Knowledge Base & Secrets (`lib/actions.ts`)
|
||||
### 7.6 Knowledge Base & Secrets (`lib/actions/knowledge-base.ts`, `lib/actions/secrets.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -290,7 +293,7 @@ Admin (full access)
|
||||
| `deleteSecret` | secretId | Admin |
|
||||
| `upsertAreaLead` | area_code, area_name, lead_id?, lead_handle?, status? | Admin/Board |
|
||||
|
||||
### 7.7 Contacts (`lib/actions.ts`)
|
||||
### 7.7 Contacts (`lib/actions/contacts.ts`)
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
@@ -298,7 +301,12 @@ Admin (full access)
|
||||
| `updateContact` | contactId, updates | Member |
|
||||
| `deleteContact` | contactId | Member |
|
||||
|
||||
### 7.8 Settings (`lib/actions.ts`)
|
||||
### 7.8 Settings (`lib/actions/settings.ts`)
|
||||
|
||||
> Note: `/settings` is admin-only and intentionally loads the space's own
|
||||
> `webhook_secret` and integration `config` so an admin can view/rotate
|
||||
> them. That is by-design same-space admin secret management, not a leak;
|
||||
> the page redirects non-admins before any of it is fetched.
|
||||
|
||||
| Action | Parameters | Role Required |
|
||||
|--------|------------|---------------|
|
||||
|
||||
@@ -4,6 +4,26 @@ Append-only. Newest entries on top. Keep each entry to one screen.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-17 (pass 31) — Audit follow-ups + attendance polish; LOCAL, awaiting deploy
|
||||
|
||||
Branch `main`. Continuation of the pass-30 audit. User: handle the deferred items where confident + properly test/doc; "stick to single space for now". 5 commits `2dc0af5..` (this entry).
|
||||
|
||||
### Done (each gated + own commit, suite 459, build clean)
|
||||
- **`2dc0af5` PayPal sync**: `payments` has NO unique index on `external_id`, so the `onConflict:'external_id'` upsert was unreliable and fell back to an insert that stripped `external_id` -> duplicate, non-idempotent rows, not tenant-scoped. Replaced with: look up existing `external_id`s in THIS space, insert only new rows (id preserved). No migration, no schema risk.
|
||||
- **`1e1b93e` door IPv6 pin**: SSRF pin parser did `pinnedHost.split(':')[0]`, mangling any IPv6 pin (failed closed -> IPv6 controllers unusable). New pure exhaustively-tested `normalizeHost` (scheme/userinfo/port/path strip; `[ipv6]`/`[ipv6]:port`/bare-ipv6 unwrap) applied to both request host and pin. ALL prior behavior preserved (verified: IPv4, host:port, userinfo-spoof reject, trailing-dot reject, metadata always blocked, IPv4-mapped-IPv6 vs IPv4 fails closed). 29 door tests.
|
||||
- **`d5a71c9` /attendance polish** (user ask): client view with a "Here now" section + inline self check-in / check-in-as-host / check-out + note; history with name search, day picker, grouped-by-day headers, per-visit duration/status/notes, live visits/members summary. New pure tested `dayKey` (tz-stable) + `visitDurationMinutes`.
|
||||
- **docs (this commit)**: ARCHITECTURE project tree + §7 module paths corrected to `lib/actions/*` reality; explicit note that `/settings` admin secret loading is by-design (not a leak); `getAuthMember` single-space invariant documented in code.
|
||||
|
||||
### Decisions / not done (intentional)
|
||||
- **Single space per user**: kept (user decision). `getAuthMember` `.single()` is intentional and fails closed; documented in code + ARCHITECTURE Known Limitations #7. Not changed.
|
||||
- **Settings `select('*')`**: NOT narrowed — verified the admin-only client legitimately uses `webhook_secret` + integration `config`; narrowing would be cosmetic and risk breaking the UI. Documented as intentional instead.
|
||||
- A full ARCHITECTURE §7 prose rewrite remains a nice-to-have (paths now correct; an authoritative top-of-doc note already added pass-30).
|
||||
|
||||
### Open
|
||||
- LOCAL & undeployed: pass-31 (5 commits) + the pass-30 deploy-state docs. **Awaiting deploy approval** (no migration this pass; app + docs only). Default reverted to ASK-before-deploy (the pass-30 deploy-without-asking was one-time). After push: smoke `/attendance` (search/day filter/check-in), PayPal re-sync idempotency, door IPv6 pin if a controller is ever IPv6.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-17 (pass 30) — Presence/attendance feature + full app audit pass
|
||||
|
||||
Branch `main`. Big session: landing wording fix, a new presence module, a 4-agent read-only audit, and audit fixes. User authorized deploying this work without the per-deploy ask (one-time, for this ready work; default reverts to ASK next session unless reaffirmed). **DEPLOYED**: pushed `4b29280..c12a103`, Actions run `25982428014` success, migration 038 applied. Smoke: `/` `/login` 200; `/dashboard` `/attendance` `/doors` `/classes` `/equipment/manage` 307->login (gated, expected). NOT browser-verified.
|
||||
|
||||
@@ -43,6 +43,11 @@ export async function getAuthMember(supabase: ServerSupabase): Promise<Member |
|
||||
} = await supabase.auth.getUser()
|
||||
if (!user) return null
|
||||
|
||||
// INVARIANT: one active membership per user (single-space product model;
|
||||
// see ARCHITECTURE "Known Limitations"). `.single()` is intentional -- if a
|
||||
// user somehow has 2+ active memberships it fails closed (no action runs)
|
||||
// rather than silently picking a space. Multi-space would require an
|
||||
// explicit active-space selector before this can change.
|
||||
const { data } = await supabase
|
||||
.from('space_members')
|
||||
.select('id, space_id, user_id, role, display_name, handle')
|
||||
|
||||
Reference in New Issue
Block a user