Initial commit: YouTube Studio Flow (backend, frontend, infrastructure, docs)

This commit is contained in:
2026-08-11 12:27:44 +02:00
commit d5af006443
304 changed files with 74604 additions and 0 deletions
@@ -0,0 +1,56 @@
# Team Settings
## User Perspective
The Settings page (`/settings`) covers:
- **Team members**: view, invite, change role, remove
- **Connected channels**: view connected YouTube channels
- **Render settings** (ADMIN+):
- Date format for date tokens (`{video.scheduledAt}`, `{video.recordingDate}`)
- Timezone
- Publishing schedule (weekly time slots for the "Next free slot" feature in the video editor)
- Show Canva link in video editor (for teams that use Canva for thumbnails)
- Disabled lint rules
- Show deleted videos in video list
- **Remote conflict detection** (ADMIN+):
- Enable scheduled conflict detection (opt-in per team)
- Videos per run (batch size)
- Only check videos older than N days
## Developer Perspective
### Team Settings Fields
| Field | Type | Description |
|---|---|---|
| `dateFormat` | `String?` | strftime-like format for date tokens |
| `timezone` | `String` | IANA timezone string, default "UTC" |
| `publishingSchedule` | `Json?` | Array of weekly time slots |
| `showCanvaLink` | `Boolean` | Shows Canva search link in video editor |
| `disabledLintRules` | `String[]` | Rule codes excluded from lint runs |
| `showDeletedVideos` | `Boolean` | Shows the Deleted tab in video list |
| `conflictDetectionEnabled` | `Boolean` | Opt-in for the scheduled remote-conflict sweep (default `false`) |
| `conflictDetectionBatchSize` | `Int` | Max videos checked per run (1500, default 50) |
| `conflictDetectionMinAgeDays` | `Int` | Skip videos whose `lastSyncedAt` is within N days (≥0, default 7) |
### Disabling Lint Rules (Side Effect)
When `disabledLintRules` is updated via `PATCH /teams/:teamId/settings`:
1. All unresolved `LintResult` rows for the newly disabled rules are deleted
2. `Video.lintStatus` is recomputed for all affected videos
### Publishing Schedule
Used by the "Next free slot" button in the video editor's Basic Info section. The schedule defines which days/times are available for publishing. `GET /teams/:teamId/next-publish-slot?channelId=` returns the next available ISO datetime.
The slot-finder walks candidate slots day by day and checks whether any existing `scheduledAt` on the channel falls within the **collision window**. Two hardcoded constants govern this behavior (`teams.service.ts`):
| Constant | Value | Effect |
|---|---|---|
| Collision window | ±30 minutes | A slot is considered taken if any video on that channel is already scheduled within 30 minutes either side of it |
| Lookahead limit | 90 days | If no free slot is found within 90 days, the endpoint returns `{ slot: null }` |
These values are not configurable per-team. The 30-minute collision window means teams that publish multiple videos per day with closely spaced slots may find legitimate adjacent slots blocked if a video is already scheduled in between. See the backlog.
### Remote Conflict Detection Settings
The three `conflictDetection*` fields control the per-team share of the global sweep queued by `ConflictDetectionScheduler`. The sweep itself only runs if the operator has set `CONFLICT_DETECTION_ENABLED=true` on the worker (see [[02 - Environment Variables]]) — the team toggle alone does nothing without it. Validation in `TeamsService.updateSettings` enforces `1 ≤ batchSize ≤ 500` and `minAgeDays ≥ 0`. See [[05 - Queue System]] for how the processor uses these values, and [[02 - Videos API]] for how detected conflicts are resolved (`POST /videos/:id/accept-remote` vs `POST /videos/:id/sync`).
## Related
- [[09 - Teams API]]
- [[03 - Metadata Linting]]