|
|
|
|
@ -0,0 +1,331 @@
|
|
|
|
|
# Dealspace — Full Feature Build
|
|
|
|
|
|
|
|
|
|
## Architecture Overview
|
|
|
|
|
|
|
|
|
|
Go + templ (compiled to `_templ.go`) + HTMX + SQLite. No external CSS framework CDN — uses Tailwind via the existing class approach already in templates. Server runs on port 8080 (mapped to 9300 externally).
|
|
|
|
|
|
|
|
|
|
**Key files:**
|
|
|
|
|
- `internal/model/models.go` — data types
|
|
|
|
|
- `internal/db/migrate.go` — schema + seed
|
|
|
|
|
- `internal/handler/*.go` — HTTP handlers
|
|
|
|
|
- `internal/store/store.go` — DB queries
|
|
|
|
|
- `internal/rbac/rbac.go` — permissions
|
|
|
|
|
- `templates/*.templ` — UI templates (must run `templ generate` after editing)
|
|
|
|
|
- `cmd/dealroom/main.go` — entrypoint
|
|
|
|
|
|
|
|
|
|
**Build & run:**
|
|
|
|
|
```bash
|
|
|
|
|
cd /home/johan/dev/dealroom
|
|
|
|
|
templ generate && go build -o bin/dealroom ./cmd/dealroom/main.go
|
|
|
|
|
systemctl --user restart dealroom 2>/dev/null || (pkill dealroom; sleep 1; ./bin/dealroom &)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Test user:** misha@muskepo.com / Dealspace2026! (owner role, org-1 "Apex Capital Partners")
|
|
|
|
|
|
|
|
|
|
**Current routes (from handler.go):** Check internal/handler/handler.go for the router.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## WHAT'S ALREADY BUILT (do NOT redo these)
|
|
|
|
|
|
|
|
|
|
- Auth (login, logout, signup with org creation, view toggle seller↔buyer)
|
|
|
|
|
- Dashboard with stage stat cards, last accessed per deal, recent activity linked to deal
|
|
|
|
|
- New Room modal (functional, creates deal with name/company/stage/dealsize/IOI/LOI/description)
|
|
|
|
|
- Deal rooms list page
|
|
|
|
|
- Deal detail with folder tree, file list, request list tabs, Atlas notes, file status change
|
|
|
|
|
- "All Documents" virtual folder already in folder tree
|
|
|
|
|
- Analytics page with deal filter dropdown
|
|
|
|
|
- Audit log with deal filter dropdown
|
|
|
|
|
- Contacts page with deal filter dropdown
|
|
|
|
|
- Admin panel (CRUD for deals, users, orgs, contacts)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## FEATURES TO BUILD
|
|
|
|
|
|
|
|
|
|
Work through these in order. Commit after each major section with `git add -A && git commit -m "feat: ..."`.
|
|
|
|
|
|
|
|
|
|
### 1. Organization Type on Signup
|
|
|
|
|
|
|
|
|
|
Add `org_type` field to `organizations` table: `TEXT DEFAULT 'company' CHECK (org_type IN ('bank','pe_vc','company'))`.
|
|
|
|
|
|
|
|
|
|
Add migration (additive ALTER TABLE or check-and-add pattern). Add org type selector to `signup.templ` with options: "Investment Bank", "PE / VC Firm", "Company". Store it.
|
|
|
|
|
|
|
|
|
|
Show org type in admin panel org edit form.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 2. Invite System
|
|
|
|
|
|
|
|
|
|
**Goal:** Org owners/admins can invite users by email. Invitee clicks a link, sets password, joins the org.
|
|
|
|
|
|
|
|
|
|
**Schema — add to migrate.go (additive):**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE IF NOT EXISTS invites (
|
|
|
|
|
token TEXT PRIMARY KEY,
|
|
|
|
|
org_id TEXT NOT NULL,
|
|
|
|
|
email TEXT NOT NULL,
|
|
|
|
|
role TEXT NOT NULL DEFAULT 'member',
|
|
|
|
|
invited_by TEXT NOT NULL,
|
|
|
|
|
expires_at DATETIME NOT NULL,
|
|
|
|
|
used_at DATETIME,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Routes:**
|
|
|
|
|
- `POST /invites/create` — owner/admin only; takes email + role; generates 32-byte hex token, 7-day expiry; stores in DB; returns invite link (just show it in a modal/toast — no email sending needed, Misha will copy/paste)
|
|
|
|
|
- `GET /invites/accept?token=XXX` — show accept page (name + password fields)
|
|
|
|
|
- `POST /invites/accept` — validate token not expired/used; create profile in same org; mark invite used
|
|
|
|
|
|
|
|
|
|
**UI:**
|
|
|
|
|
- Add "Team" page at `/team` — lists all profiles in org, shows role badges, invite button
|
|
|
|
|
- Invite button opens modal: email input + role dropdown (admin/member/viewer) → POST → show generated link
|
|
|
|
|
- Nav item "Team" in sidebar (between Contacts and Admin)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3. Remove Close Probability
|
|
|
|
|
|
|
|
|
|
In `admin.templ`: remove the `close_probability` form field (around line 263).
|
|
|
|
|
In `model/models.go`: keep the field (backward compat with existing DB) but don't expose it in any UI.
|
|
|
|
|
Search all templates for `close_probability` or `CloseProbability` and remove any display.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4. New Room Modal Enhancements
|
|
|
|
|
|
|
|
|
|
Add to the existing new room modal (in `dashboard.templ` AND `dealrooms.templ`):
|
|
|
|
|
|
|
|
|
|
- **Industry** field: text input (e.g. "Healthcare", "Fintech", "SaaS")
|
|
|
|
|
- **Exclusivity End** date field (already in Deal model as `ExclusivityEnd`, just add to modal form)
|
|
|
|
|
|
|
|
|
|
Add `industry TEXT DEFAULT ''` to deals table (additive migration).
|
|
|
|
|
Add `Industry string` to Deal model.
|
|
|
|
|
Persist both in `handleCreateDeal`.
|
|
|
|
|
|
|
|
|
|
For the "New Room" button on the deal rooms page (`dealrooms.templ`) — add the same modal that's currently on the dashboard. It should be identical functionality.
|
|
|
|
|
|
|
|
|
|
**Folder auto-create from paths:** Add an optional textarea in the modal: "Folder Structure (one path per line, e.g. Financial/Q4 Reports)". Parse on submit in `handleCreateDeal`: split by newline, for each path split by `/`, create nested folders. If field is empty, skip.
|
|
|
|
|
|
|
|
|
|
**Invite on create:** Add a textarea "Initial Team (one email per line, role: member)". After deal creation, create invite tokens for each email and show all generated links in a success modal. Keep it simple.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 5. Permission Controls on Request Lists
|
|
|
|
|
|
|
|
|
|
Add to deals table (additive):
|
|
|
|
|
```sql
|
|
|
|
|
ALTER TABLE deals ADD COLUMN buyer_can_comment INTEGER DEFAULT 1;
|
|
|
|
|
ALTER TABLE deals ADD COLUMN seller_can_comment INTEGER DEFAULT 1;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In deal detail request list view: if `buyer_can_comment=0` and user is buyer, hide buyer_comment input. If `seller_can_comment=0` and user is seller, hide seller_comment input.
|
|
|
|
|
|
|
|
|
|
In admin deal edit form: add checkboxes "Allow buyer comments" and "Allow seller comments".
|
|
|
|
|
|
|
|
|
|
**Folder/file visibility per buyer group:**
|
|
|
|
|
Add tables:
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE IF NOT EXISTS buyer_groups (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
deal_id TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS folder_access (
|
|
|
|
|
folder_id TEXT NOT NULL,
|
|
|
|
|
buyer_group TEXT NOT NULL,
|
|
|
|
|
PRIMARY KEY (folder_id, buyer_group)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In deal detail documents tab (seller/admin view): add "Restrict Access" button per folder that opens a modal to select which buyer groups can see it. If `folder_access` has rows for a folder, only those groups see it; if empty = visible to all. Filter files shown to buyer based on their `buyer_group` (from diligence_requests or a new field on profiles `buyer_group TEXT DEFAULT ''`).
|
|
|
|
|
|
|
|
|
|
Add `buyer_group TEXT DEFAULT ''` to profiles table (additive migration). Admin can set it per user.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 6. Deal Room: Folder Management
|
|
|
|
|
|
|
|
|
|
In deal detail documents tab, make the "New Folder" button functional:
|
|
|
|
|
- POST `/deals/{id}/folders/create` with `name` and optional `parent_id`
|
|
|
|
|
- Handler creates folder, redirects back to deal
|
|
|
|
|
|
|
|
|
|
Add "Rename" and "Delete" (soft delete or actual) per folder in the sidebar — small kebab menu or inline edit on hover.
|
|
|
|
|
|
|
|
|
|
Add drag-to-reorder: add `sort_order INTEGER DEFAULT 0` to folders (additive). Simple up/down buttons per folder to change order (HTMX swap).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 7. File Upload & Management
|
|
|
|
|
|
|
|
|
|
**Real file upload:**
|
|
|
|
|
|
|
|
|
|
Add `uploads_dir` config (default: `data/uploads/`).
|
|
|
|
|
|
|
|
|
|
`POST /deals/{id}/files/upload` — multipart form: `folder_id` (optional), `request_item_id` (optional). Save to `data/uploads/{deal_id}/{uuid}_{filename}`. Insert file record. Create DealActivity. Redirect back.
|
|
|
|
|
|
|
|
|
|
Add `storage_path TEXT DEFAULT ''` to files table.
|
|
|
|
|
|
|
|
|
|
In document list: add "Upload" button that opens a modal with file picker + folder selector + optional "Link to request item" dropdown.
|
|
|
|
|
|
|
|
|
|
Add "Delete" per file (owner/admin only): removes from DB + disk. POST `/deals/{id}/files/{fileID}/delete`.
|
|
|
|
|
|
|
|
|
|
Add `uploaded_at` display: `files.created_at` already exists — show it in the file list as "Uploaded Jan 15" next to file size.
|
|
|
|
|
|
|
|
|
|
**File download:** `GET /deals/{id}/files/{fileID}/download` — serve the file from disk with original filename. Log download activity.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 8. Doc ↔ Request List Linking
|
|
|
|
|
|
|
|
|
|
When uploading a file (see #7), the upload modal has an optional "Link to request item" dropdown showing open request items for this deal. On save, append the new file ID to `diligence_requests.linked_file_ids` (comma-separated).
|
|
|
|
|
|
|
|
|
|
In request list view: for each request item with `linked_file_ids != ""`, show linked file names as small pills with download links.
|
|
|
|
|
|
|
|
|
|
In the upload modal: when a buyer uploads (buyer view), they see only their own request items in the dropdown.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 9. Buyer-Specific Request Lists
|
|
|
|
|
|
|
|
|
|
Add to diligence_requests:
|
|
|
|
|
```sql
|
|
|
|
|
ALTER TABLE diligence_requests ADD COLUMN is_buyer_specific INTEGER DEFAULT 0;
|
|
|
|
|
ALTER TABLE diligence_requests ADD COLUMN visible_to_buyer_group TEXT DEFAULT '';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In seller view of request list: show all requests. Add "Add Buyer-Specific Request" button that creates a request visible only to a specific buyer_group.
|
|
|
|
|
|
|
|
|
|
In buyer view: show general requests (is_buyer_specific=0) AND their own specific ones (visible_to_buyer_group = their buyer_group).
|
|
|
|
|
|
|
|
|
|
In `getRequests()` in deals.go: apply this filter for buyer profiles.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 10. Document Comments
|
|
|
|
|
|
|
|
|
|
**Schema:**
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE IF NOT EXISTS file_comments (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
file_id TEXT NOT NULL,
|
|
|
|
|
deal_id TEXT NOT NULL,
|
|
|
|
|
user_id TEXT NOT NULL,
|
|
|
|
|
content TEXT NOT NULL,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In file list view: add a chat bubble icon per file. If comments exist, show count badge. Clicking opens a slide-over panel (HTMX `hx-get="/deals/{id}/files/{fileID}/comments"` → returns comment list + add form).
|
|
|
|
|
|
|
|
|
|
Routes:
|
|
|
|
|
- `GET /deals/{id}/files/{fileID}/comments` — returns HTMX partial with comments + form
|
|
|
|
|
- `POST /deals/{id}/files/{fileID}/comments` — add comment, return updated partial
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 11. Search Within Deal
|
|
|
|
|
|
|
|
|
|
In deal detail header area, add a search input. On input (debounce 300ms with HTMX), `hx-get="/deals/{id}/search?q=..."` → searches file names + folder names + request descriptions → returns HTMX partial replacing the active tab content with results.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 12. Request Lists Page: Organized by Deal then Buyer
|
|
|
|
|
|
|
|
|
|
In `templates/requests.templ` and `internal/handler/requests.go`:
|
|
|
|
|
|
|
|
|
|
Restructure to show:
|
|
|
|
|
- Deal name as section header
|
|
|
|
|
- "General Request List" subsection
|
|
|
|
|
- Per buyer_group subsections (if buyer-specific items exist)
|
|
|
|
|
|
|
|
|
|
Add deal filter in header. Add buyer group filter.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 13. Analytics: Per-Buyer Stats
|
|
|
|
|
|
|
|
|
|
**Schema additions:**
|
|
|
|
|
```sql
|
|
|
|
|
ALTER TABLE deal_activity ADD COLUMN buyer_group TEXT DEFAULT '';
|
|
|
|
|
ALTER TABLE deal_activity ADD COLUMN time_spent_seconds INTEGER DEFAULT 0;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When logging view/download activity, capture buyer_group from profile and time_spent if provided.
|
|
|
|
|
|
|
|
|
|
In analytics template: when a deal is selected, show a "Buyer Activity" section with a table:
|
|
|
|
|
- Buyer Group | Files Downloaded | Last Accessed | Total Time | Files Viewed
|
|
|
|
|
- Expandable row: individual user breakdown (hx-get to load per-user stats)
|
|
|
|
|
|
|
|
|
|
Add handler `GET /analytics/buyers?deal_id=XXX` returning HTMX partial.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 14. Contacts: Deal Association
|
|
|
|
|
|
|
|
|
|
Add junction table:
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE IF NOT EXISTS contact_deals (
|
|
|
|
|
contact_id TEXT NOT NULL,
|
|
|
|
|
deal_id TEXT NOT NULL,
|
|
|
|
|
PRIMARY KEY (contact_id, deal_id)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
In contacts page: when deal filter is active, filter by contact_deals join.
|
|
|
|
|
|
|
|
|
|
In contact add/edit form (admin panel): add multi-select for which deals this contact is associated with.
|
|
|
|
|
|
|
|
|
|
Show deal name tags on each contact row.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 15. Audit Logs: Buyer Filter
|
|
|
|
|
|
|
|
|
|
Add buyer_group filter dropdown to audit log page alongside the existing deal filter. Query: `WHERE deal_id = ? AND (buyer_group = ? OR ? = '')`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 16. Subscription Plan Page (UI Mock)
|
|
|
|
|
|
|
|
|
|
Add `/subscription` route and nav item (gear/settings area or sidebar).
|
|
|
|
|
|
|
|
|
|
Show three plan cards:
|
|
|
|
|
- **Starter** — 3 deal rooms, 5 users, 10GB storage — $299/mo
|
|
|
|
|
- **Growth** — 15 deal rooms, 25 users, 100GB storage — $799/mo
|
|
|
|
|
- **Enterprise** — Unlimited rooms, unlimited users, 1TB storage — Custom
|
|
|
|
|
|
|
|
|
|
Show current plan (hardcode "Growth" for demo), usage bars (rooms used/limit, users used/limit, storage used/limit — all mock values based on actual DB counts for rooms/users, mock 23GB for storage).
|
|
|
|
|
|
|
|
|
|
Add "Upgrade" button per plan that shows a "Contact sales" toast. No real billing.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Implementation Notes
|
|
|
|
|
|
|
|
|
|
- **templ generate:** Run after EVERY template change. The `_templ.go` files must be regenerated or the build fails.
|
|
|
|
|
- **Additive migrations:** Always use `ALTER TABLE ADD COLUMN IF NOT EXISTS` pattern or `CREATE TABLE IF NOT EXISTS`. Never drop columns.
|
|
|
|
|
- **HTMX:** Use `hx-target`, `hx-swap`, `hx-post/get` for partial updates. Return bare HTML fragments from HTMX endpoints (no full Layout wrapper).
|
|
|
|
|
- **Activity logging:** Use `logActivity(dealID, userID, orgID, actType, resourceType, resourceName, resourceID)` pattern already in deals.go.
|
|
|
|
|
- **Error handling:** On bad input, redirect back with `?error=message` and show it in the template.
|
|
|
|
|
- **Buyer view toggle:** Check `profile.ViewAsBuyer` or `profile.Role == "viewer"` to apply buyer restrictions.
|
|
|
|
|
- **File storage:** Keep it simple — local disk at `data/uploads/`. No S3 needed for now.
|
|
|
|
|
- **Service restart:** After build, run `systemctl --user restart dealroom` or kill+restart the binary.
|
|
|
|
|
|
|
|
|
|
## After Each Section
|
|
|
|
|
|
|
|
|
|
1. `templ generate`
|
|
|
|
|
2. `go build -o bin/dealroom ./cmd/dealroom/main.go`
|
|
|
|
|
3. Fix any compile errors
|
|
|
|
|
4. `systemctl --user restart dealroom` (or kill/restart)
|
|
|
|
|
5. Quick smoke test: curl http://localhost:8080/ should redirect to login
|
|
|
|
|
6. `git add -A && git commit -m "feat: <section name>"`
|
|
|
|
|
|
|
|
|
|
## Final Steps
|
|
|
|
|
|
|
|
|
|
After all sections:
|
|
|
|
|
1. `git add -A && git commit -m "feat: complete Misha feature list"`
|
|
|
|
|
2. `git push origin main`
|
|
|
|
|
3. Run: `openclaw system event --text "Done: Built all Dealspace features — invite system, file upload, folder mgmt, buyer-specific requests, doc comments, analytics by buyer, contacts/audit by deal, subscription page" --mode now`
|