Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
28e2fc4
Add files via upload
27180781 May 7, 2026
e5f1062
feat: redesign admin settings UI with categorized visual layout
27180781 May 7, 2026
a8d3a1e
chore: trigger redeploy
27180781 May 7, 2026
72da5a4
feat: integrate Magnet ad platform with frequency rules and lazy loading
27180781 May 7, 2026
925d900
feat: add Magnet click & earnings stats panel in admin
27180781 May 7, 2026
7d87793
fix: ensure chat messages render even if magnet ad logic fails
27180781 May 7, 2026
4bf5dc5
fix: revert chat template to original messages loop, inject ads inline
27180781 May 7, 2026
d753607
fix: revert chat component to pre-magnet state to restore message ren…
27180781 May 7, 2026
85505a0
test: re-add MagnetAdsService injection without template changes
27180781 May 7, 2026
ad7052d
test: add @if ad-slot block in template with plain div placeholder
27180781 May 7, 2026
289756e
test: nest @if inside the existing <nb-list-item>
27180781 May 7, 2026
d6c47af
feat: re-enable magnet ad slot rendering inside the chat list
27180781 May 7, 2026
632210e
feat: render magnet ad inside an admin-style message bubble
27180781 May 7, 2026
7d933ec
feat: add analytics tag injection into <head>
27180781 May 7, 2026
071e50b
docs: document UI redesign, magnet ads, analytics tag, and new endpoints
27180781 May 7, 2026
edc724e
chore: trigger redeploy
27180781 May 7, 2026
6d55d7d
Add multi-tenant role system and channel management foundation
claude May 17, 2026
04cac12
Update auth.go Session to use new GlobalRole/ChannelRole system
claude May 17, 2026
6df0607
Rewrite all backend files for multi-tenant channel support
claude May 17, 2026
257df58
Add global Magnet ads override system for super admin
claude May 17, 2026
0fabb76
Add backend/channel binary to .gitignore
claude May 17, 2026
ea1854a
Add global iframe-ads override system for super admin (same pattern a…
claude May 17, 2026
532f35f
Add super admin panel foundation (frontend, WIP)
claude May 17, 2026
2825add
Complete super admin panel frontend
claude May 17, 2026
581a20f
Add Cloudflare R2 storage with local fallback
claude May 17, 2026
21042ed
Add storage quota system, super admin storage panel, and TinyPNG comp…
claude May 17, 2026
8fc53e7
Add CHANGELOG.md documenting the multi-tenant system
claude May 17, 2026
3680779
Add public landing page with channel request flow
claude May 17, 2026
f360334
Fix multi-tenant isolation, performance, and rate limiting
claude May 18, 2026
ccf083f
Implement all scalability and performance recommendations
claude May 18, 2026
c238eba
Fix per-channel URL routing, OAuth returnUrl, and all API slug paths
claude May 18, 2026
e3202af
Fix three logic bugs found in flow review
claude May 18, 2026
fae823e
Fix offline handling: banner, reconnect, missed-message sync
claude May 18, 2026
9498764
Fix scroll jumps and message duplicates on reconnect
claude May 18, 2026
66006b8
Fix three regressions: channel switch stale state, empty-messages cra…
claude May 18, 2026
541c999
Fix 9 bugs across channel management, admin panel, and backend reliab…
claude May 18, 2026
6698a9e
Update Dockerfile
27180781 May 18, 2026
3d23f2c
Fix Docker build: upgrade to Go 1.25, add dep caching, remove go mod …
claude May 18, 2026
696959c
chore: normalize Dockerfile AS keyword casing
claude May 18, 2026
83f49be
Fix per-channel URL routing, OAuth returnUrl, and all API slug paths
claude May 18, 2026
8ff774e
Fix three logic bugs found in flow review
claude May 18, 2026
680ebaa
Fix offline handling: banner, reconnect, missed-message sync
claude May 18, 2026
e4f66ff
Fix scroll jumps and message duplicates on reconnect
claude May 18, 2026
4f13ea7
Fix three regressions: channel switch stale state, empty-messages cra…
claude May 18, 2026
18ac6d3
Fix 9 bugs across channel management, admin panel, and backend reliab…
claude May 18, 2026
b83526b
Fix Docker build: upgrade to Go 1.25, add dep caching, remove go mod …
claude May 18, 2026
f2adf81
chore: normalize Dockerfile AS keyword casing
claude May 18, 2026
6a1f669
Fix duplicate /api/super-admin route causing startup panic
claude May 18, 2026
8efa495
Fix styling flash: suppress landing page until auth check completes
claude May 18, 2026
ee381c4
Remove bootstrap-icons npm package (loaded via CDN)
claude May 18, 2026
ff052c8
chore: trigger redeploy after disk cleanup
claude May 18, 2026
920f31e
Fix super-admin panel: wrap with nb-layout for Nebular theme to apply
claude May 18, 2026
b029d71
Add logout button to super-admin panel header
claude May 18, 2026
51f4e30
Fix ngModel binding in create-channel form: add name attributes
claude May 18, 2026
6b14605
Show channel request form for users without channel access
claude May 18, 2026
71c8a12
Fix channel request approval: auto-lowercase slug before submitting
claude May 18, 2026
80111ad
Add Enter-sends-message setting and paste-image support
claude May 18, 2026
28fec5a
Fix TS2367: cast value comparison to any for boolean setting check
claude May 18, 2026
0069460
Fix 'Enter sends message' setting not working
claude May 18, 2026
834d81d
Move FCM config to super-admin global settings, per-channel toggle only
claude May 18, 2026
719bcc3
Merge master into claude/user-managed-channels-gF1Zt
claude May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ frontend/node_modules/
frontend/dist/*
package-lock.json
backend/__debug*
backend/channel
caddy_data/
redis_data/
channel_data/*
Expand Down
265 changes: 265 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# TheChannel — Multi-Tenant System — Change Log

## Overview

This document describes all changes made to transform TheChannel from a single-channel application into a **multi-tenant platform** where each user can own a private channel, with a central super-admin panel for full control.

---

## Architecture Changes

### Multi-Tenant Backend (Go)

All data is now stored with per-channel Redis key prefixes:

| Old key pattern | New key pattern |
|---|---|
| `messages:{id}` | `channel:{slug}:messages:{id}` |
| `m_times` | `channel:{slug}:m_times` |
| `settings:list` | `channel:{slug}:settings` |
| (none) | `global:settings` (FCM/VAPID) |

A single Go backend instance and a single Redis/Kvrocks instance serve **all channels**. No new CapRover projects needed per channel.

---

## New Files

### Backend

| File | Description |
|---|---|
| `backend/channels.go` | Channel CRUD, features management, middleware |
| `backend/storage.go` | Cloudflare R2 client (`initR2`, `r2Upload`, `r2Download`, `r2Delete`, `r2Exists`) |
| `backend/storage_handlers.go` | Storage quota HTTP handlers for channel admin and super admin |

### Frontend

| File | Description |
|---|---|
| `frontend/src/app/guards/super-admin.guard.ts` | Route guard for `/super-admin` — only allows `globalRole === 'super_admin'` |
| `frontend/src/app/services/super-admin.service.ts` | All super admin API calls |
| `frontend/src/app/components/super-admin/super-admin-panel.component.*` | Main super admin shell |
| `frontend/src/app/components/super-admin/channels/channels-list.component.*` | Channel list with CRUD actions |
| `frontend/src/app/components/super-admin/channels/channel-features.component.*` | Per-channel feature toggles (12 features) |
| `frontend/src/app/components/super-admin/channels/channel-users.component.*` | Per-channel user/role management |
| `frontend/src/app/components/super-admin/global-ads/global-ads.component.*` | Global iframe-ads config + lock |
| `frontend/src/app/components/super-admin/global-magnet/global-magnet.component.*` | Global Magnet ads config + frequency + lock |
| `frontend/src/app/components/super-admin/global-users/global-users.component.*` | Read-only global user list with roles |
| `frontend/src/app/components/super-admin/global-settings/global-settings.component.*` | Global key-value settings editor (FCM/VAPID) |
| `frontend/src/app/components/super-admin/statistics/super-admin-statistics.component.*` | Magnet stats + statistics reset |
| `frontend/src/app/components/super-admin/storage/super-admin-storage.component.ts` | Per-channel storage quota + usage bar (super admin view) |
| `frontend/src/app/components/super-admin/global-storage/global-storage.component.ts` | Global default storage quota editor |
| `frontend/src/app/components/admin/storage/storage.component.ts` | Channel admin storage view — usage bar, warnings, auto-cleanup toggle |

---

## Modified Files

### Backend

#### `backend/privileges.go` — Complete rewrite
- New role system: `GlobalRole` (`super_admin`) and `ChannelRole` (`owner`, `moderator`, `writer`)
- `User` struct now holds `GlobalRole` and `ChannelRoles map[string]ChannelRole`
- Middleware: `requireSuperAdmin`, `protectedWithChannelRole`

#### `backend/auth.go`
- `Session` struct updated with `GlobalRole` and `ChannelRoles`
- `registeringEmail(slug, email string)` — `slug=""` registers globally, slug set registers into a channel

#### `backend/db.go` — Complete rewrite
- All message/settings functions are now channel-scoped (slug prefix)
- Channel CRUD: `dbCreateChannel`, `dbGetChannel`, `dbListChannels`, `dbDeleteChannel`, `dbSetChannelFeatures`, `dbAssignChannelRole`
- Storage quota functions:
- `dbGetGlobalStorageQuota` / `dbSetGlobalStorageQuota`
- `dbGetChannelStorageQuota` / `dbSetChannelStorageQuota` (0 = use global)
- `dbGetEffectiveStorageQuota` (returns channel-specific or global default)
- `dbGetChannelStorageUsed` / `dbIncrChannelStorageUsed` / `dbDecrChannelStorageUsed`
- `dbAddChannelFile` / `dbRemoveChannelFile` (sorted set by upload timestamp)
- `dbGetOldestChannelFiles` (for auto-cleanup)
- `dbGetChannelAutoCleanup` / `dbSetChannelAutoCleanup`
- `dbIncrFileHashRefs` / `dbDecrFileHashRefs` (deduplication reference counter)
- Global ads/magnet config storage: `dbGetGlobalMagnetConfig`, `dbSetGlobalMagnetConfig`, `dbGetGlobalAdsConfig`, `dbSetGlobalAdsConfig`

#### `backend/files.go`
- `FileMetadata` struct: added `Size int64` and `ChannelSlug string`
- `dbSaveFileMetadata` / `dbGetFileMetadata` — now uses Redis JSON; YAML fallback for legacy local files
- `uploadFile` — reads bytes to memory, checks quota, deduplicates by SHA-256 hash, increments ref counter, tracks in channel sorted set
- `enforceStorageQuota` — checks quota before upload; runs auto-cleanup (target: 80% usage) if enabled
- `deleteFileByID` — marks deleted, decrements storage counter, removes from R2/disk only when refs reach 0
- **TinyPNG integration**: `compressWithTinyPng(ctx, apiKey, data, mimeType)` — compresses PNG/JPEG/WebP via TinyPNG API before upload if `tinypng_api_key` is set in channel settings

#### `backend/settings.go`
- `SettingConfig` struct: added `TinyPngApiKey string`
- `ToConfig()`: added `"tinypng_api_key"` case

#### `backend/ads.go`
- `isChannelMagnetLocked` / `isChannelAdsLocked` — checks if super admin locked ads for this channel
- `getMagnetAdsSettings` / `getAdsSettings` — returns global config if locked, channel config otherwise
- `getGlobalMagnetConfig` / `setGlobalMagnetConfig` — super admin global Magnet config
- `getGlobalAdsConfig` / `setGlobalAdsConfig` — super admin global iframe-ads config
- `syncMagnetLockFlags` / `syncAdsLockFlags` — goroutines that update `MagnetLockedByAdmin`/`AdsLockedByAdmin` on all channels when global config changes

#### `backend/main.go` — Full routing rewrite

```
/auth/google
/auth/login
/auth/logout
/api/user-info

/api/super-admin/* (login + requireSuperAdmin)
GET /channels
POST /channels/create
GET /channels/{slug}
DELETE /channels/{slug}
PUT /channels/{slug}/features
GET /channels/{slug}/users
POST /channels/{slug}/users
GET /channels/{slug}/storage
PUT /channels/{slug}/storage
GET /users/list
POST /users/set
GET /global-settings/get
POST /global-settings/set
GET /ads/config
POST /ads/config
GET /magnet/config
POST /magnet/config
GET /magnet/stats
GET /storage/config
POST /storage/config
POST /statistics/reset

/api/channel/{slug}/import/post (API key auth)

/api/channel/{slug}/* (channelMiddleware + channelIfRequireAuth)
GET /info
GET /messages
GET /events
GET /files/{fileid}
POST /files
GET /emojis/list
GET /notifications-config
GET /ads/settings
GET /ads/magnet
(login required):
POST /notifications-subscribe
POST /reactions/set-reactions
POST /messages/report
GET /user-info

/admin/* (channel owner+)
PUT /info
POST /messages
PUT /messages/{id}
DELETE /messages/{id}
POST /emojis
GET/POST /settings
GET/POST /users
GET/PUT /storage
POST /storage/auto-cleanup
GET/PUT /scheduled-messages
GET /statistics
GET/POST /reports
```

### Frontend

#### `frontend/src/app/models/channel.model.ts`
- Added `slug?: string` field

#### `frontend/src/app/models/user.model.ts`
- Added `globalRole: string` and `channelRoles: Record<string, string>`

#### `frontend/src/app/app.routes.ts`
- Added `/super-admin` route guarded by `AuthGuard` + `SuperAdminGuard`

#### `frontend/src/app/components/admin/admin-panel.component.ts`
- Added `StorageComponent` import
- Added `"אחסון"` menu item with `hard-drive-outline` icon
- Added `readonly storage = "storage"` constant
- Added `channelSlug` getter via `ChatService`
- Added `case 'hard-drive-outline'` in menu switch

#### `frontend/src/app/components/admin/admin-panel.component.html`
- Added `@case (storage)` rendering `<app-channel-storage [slug]="channelSlug">`

#### `frontend/src/app/components/admin/settings/settings.schema.ts`
- Added new category **"אחסון ומדיה"** with `tinypng_api_key` field (password type)

---

## Features Added

### 1. Multi-Tenant Channels
- Each user gets their own private channel with a unique slug
- Single Go + Redis instance serves all channels
- No CapRover project needed per channel

### 2. Role System
- **GlobalRole**: `super_admin`
- **ChannelRole**: `owner` > `moderator` > `writer`
- All API routes protected with appropriate role middleware

### 3. Super Admin Panel (`/super-admin`)
- Accessible only to users with `globalRole: "super_admin"`
- **Channels**: List all channels, create, delete, edit features, manage users, manage storage
- **Iframe Ads**: Global iframe-ad src/width with option to lock all channels or specific channels
- **Magnet Ads**: Global Magnet config (snippet, mode, frequency settings) with lock option
- **Global Storage**: Set the default storage quota (GB) for all channels
- **Users**: Read-only list of all users with their global and channel roles
- **Global Settings**: FCM/VAPID key-value editor
- **Statistics**: View Magnet stats, reset all statistics

### 4. Storage Quota System
- Super admin sets a **global default quota** (default: 5 GB per channel)
- Super admin can **override per-channel** from the channels list → "אחסון"
- Channel owners see **usage bar** with color-coded status:
- Green (`ok`): < 80% used
- Orange (`warning`): 80–90% used
- Red (`critical`): > 90% used
- **Auto-cleanup toggle**: When enabled, automatically deletes oldest files to reach 80% usage before new uploads, ensuring uploads always succeed
- File deduplication: identical files (same SHA-256 hash) share one physical copy; storage only freed when last reference is removed

### 5. Cloudflare R2 Storage
- All media uploads go to R2 (S3-compatible)
- Local disk used as fallback when R2 is not configured
- Configure via environment variables: `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`, `R2_PUBLIC_URL`
- Files stored at path: `files/{hash[0:2]}/{hash[2:4]}/{hash}`

### 6. TinyPNG Image Compression
- Channel owners add their TinyPNG API key in **Admin → הגדרות → אחסון ומדיה**
- When set, PNG/JPEG/WebP images are automatically compressed via TinyPNG API **before** upload
- Compressed file is what gets stored and counted toward quota — saves significant space
- Falls back silently to original file if compression fails or API is unavailable
- Non-image files (video, documents, etc.) are never sent to TinyPNG

### 7. Global Ads Override
- Super admin can push **global iframe-ads** settings to all channels or specific channels
- Super admin can push **global Magnet ads** settings (including frequency controls) to all channels or specific channels
- Locked channels display the global config and cannot be overridden by channel owners

---

## Environment Variables

Add to your `.env` / CapRover environment:

```
# Cloudflare R2
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET_NAME=your_bucket_name
R2_PUBLIC_URL=https://pub-xxxx.r2.dev # optional, for direct CDN links
```

---

## Dependencies Added

### Go (`backend/go.mod`)
- `github.com/aws/aws-sdk-go-v2` — AWS SDK v2 (used for R2 via S3-compatible API)
- `github.com/aws/aws-sdk-go-v2/credentials`
- `github.com/aws/aws-sdk-go-v2/service/s3`
9 changes: 6 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
FROM node:20 as builder1
FROM node:20 AS builder1

WORKDIR /app
COPY ./frontend .
RUN npm install \
&& npm run build

FROM golang:1.24 AS builder2
FROM golang:1.25 AS builder2

WORKDIR /app

# Copy dependency manifests first so this layer is cached when only source changes
COPY ./backend/go.mod ./backend/go.sum ./
RUN go mod download

COPY ./backend .
COPY --from=builder1 /app/dist/channel/browser/favicon.ico assets
RUN go mod tidy
RUN go build -o the-channel .

FROM debian:latest
Expand Down
Loading