diff --git a/.gitea/workflows/licence-check.yml b/.gitea/workflows/licence-check.yml index 267bda2..6f5235b 100644 --- a/.gitea/workflows/licence-check.yml +++ b/.gitea/workflows/licence-check.yml @@ -7,16 +7,24 @@ on: - "package.json" - "package-lock.json" - ".gitea/workflows/licence-check.yml" + - "**/*.js" + - "**/*.ts" + - "**/*.jsx" + - "**/*.tsx" pull_request: branches: ["**", "!main", "!release/**"] paths: - "package.json" - "package-lock.json" - ".gitea/workflows/licence-check.yml" + - "**/*.js" + - "**/*.ts" + - "**/*.jsx" + - "**/*.tsx" jobs: licence-check: - name: Dependency licence compatibility + name: Licence compatibility and copyright header verification runs-on: ubuntu-latest continue-on-error: true steps: @@ -36,3 +44,40 @@ jobs: --onlyAllow "MIT;ISC;MIT-0;BSD-2-Clause;BSD-3-Clause;Apache-2.0;CC0-1.0;BlueOak-1.0.0" \ --excludePrivatePackages \ && echo "All production dependency licences are compatible with MIT." + + - name: Check copyright headers in source files + run: | + #!/bin/bash + set -e + + # Find all source files, excluding build artifacts and node_modules + SOURCE_FILES=$(find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \ + ! -path "./node_modules/*" \ + ! -path "./.git/*" \ + ! -path "./dist/*" \ + ! -path "./build/*" \ + ! -path "./.gitea/*") + + MISSING_HEADER=0 + + # Check each file for MIT-compliant copyright header + while IFS= read -r file; do + if [ -z "$file" ]; then + continue + fi + + # Check if file starts with a copyright header containing: Copyright, year (4 digits), name, and MIT License + if ! head -n 5 "$file" | grep -qiE "Copyright.*[0-9]{4}.*MIT"; then + echo "❌ Missing MIT-compliant copyright header in: $file" + echo " Required format: // Copyright (c) YYYY Name. MIT License." + MISSING_HEADER=$((MISSING_HEADER + 1)) + fi + done <<< "$SOURCE_FILES" + + if [ $MISSING_HEADER -gt 0 ]; then + echo "" + echo "⚠️ Found $MISSING_HEADER file(s) with missing or non-compliant copyright headers." + exit 1 + else + echo "✅ All source files have MIT-compliant copyright headers." + fi diff --git a/client/src/App.jsx b/client/src/App.jsx index 6a06d22..17eaaf7 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. import { useState, useEffect } from 'react'; import axios from 'axios'; import './App.css'; diff --git a/client/src/main.jsx b/client/src/main.jsx index 6aee80b..93ce542 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' diff --git a/client/vite.config.js b/client/vite.config.js index 0f5e78c..bf237b5 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f47c065..1ec0d7e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -161,7 +161,6 @@ sofarr/ │ └── integration/ # Supertest integration tests (nock for external HTTP) ├── docs/ │ ├── ARCHITECTURE.md # This document -│ └── diagrams/ # PlantUML source files ├── .gitea/workflows/ │ ├── ci.yml # Security audit + test/coverage CI jobs │ ├── build-image.yml # Docker image build and push @@ -901,21 +900,23 @@ volumes: ### CI / CD -The `.gitea/workflows/` directory contains three pipeline definitions: +The `.gitea/workflows/` directory contains five pipeline definitions: | File | Trigger | Purpose | |------|---------|--------| -| `ci.yml` | Every push / PR | Security audit (`npm audit --audit-level=high`) + tests with V8 coverage | -| `build-image.yml` | Push to `main` / `develop` | Build and push Docker image to `docker.i3omb.com` | -| `create-release.yml` | Tag push (`v*`) | Create a Gitea release | +| `ci.yml` | Every push / PR (all branches) | Security audit (`npm audit --audit-level=high`) + tests with V8 coverage | +| `build-image.yml` | Push to `release/**` or `develop` | Build and push Docker image to `reg.i3omb.com`. `release/**` pushes versioned + `latest` tags; `develop` pushes a `:develop` tag. | +| `create-release.yml` | Tag push (`v*`) | Generate release notes from git log and create a Gitea release | +| `docs-check.yml` | Push / PR touching `**.md` (non-main / non-release branches) | Markdown lint + Mermaid diagram parse validation | +| `licence-check.yml` | Push / PR touching `package.json` or `package-lock.json` | Verify all production dependency licences are compatible with MIT | -> **Diagrams** are written in Mermaid and render natively in Gitea — no CI workflow required. See [Section 13](#13-diagrams). +> **Diagrams** are written in Mermaid and render natively in Gitea — no separate diagram files or CI render step required. See [Section 13](#13-diagrams). --- ## 13. Diagrams -All diagrams are written in [Mermaid](https://mermaid.js.org/) and render natively in Gitea and GitHub markdown. +All diagrams are written in [Mermaid](https://mermaid.js.org/) and render natively in Gitea and GitHub markdown. No external tooling or PNG exports are required — the source is the diagram. ### 13.1 Component Diagram @@ -1302,6 +1303,13 @@ classDiagram +availability string +hash string +completedAt string + +canBlocklist boolean + +addedOn number + +arrQueueId number + +arrType string + +arrInstanceUrl string + +arrContentId number + +arrContentType string } class TagBadge { +label string @@ -1346,6 +1354,7 @@ classDiagram +num_seeds number +num_leechs number +availability number + +added_on number } class SonarrQueueRecord { +seriesId number diff --git a/docs/diagrams/activity-matching.png b/docs/diagrams/activity-matching.png deleted file mode 100644 index d3d5ef7..0000000 Binary files a/docs/diagrams/activity-matching.png and /dev/null differ diff --git a/docs/diagrams/class-data.png b/docs/diagrams/class-data.png deleted file mode 100644 index 84b04da..0000000 Binary files a/docs/diagrams/class-data.png and /dev/null differ diff --git a/docs/diagrams/class-server.png b/docs/diagrams/class-server.png deleted file mode 100644 index a73523d..0000000 Binary files a/docs/diagrams/class-server.png and /dev/null differ diff --git a/docs/diagrams/component.png b/docs/diagrams/component.png deleted file mode 100644 index 41ee079..0000000 Binary files a/docs/diagrams/component.png and /dev/null differ diff --git a/docs/diagrams/seq-auth.png b/docs/diagrams/seq-auth.png deleted file mode 100644 index fb89f06..0000000 Binary files a/docs/diagrams/seq-auth.png and /dev/null differ diff --git a/docs/diagrams/seq-dashboard.png b/docs/diagrams/seq-dashboard.png deleted file mode 100644 index 19f0745..0000000 Binary files a/docs/diagrams/seq-dashboard.png and /dev/null differ diff --git a/docs/diagrams/seq-polling.png b/docs/diagrams/seq-polling.png deleted file mode 100644 index ba4a7e9..0000000 Binary files a/docs/diagrams/seq-polling.png and /dev/null differ diff --git a/docs/diagrams/state-poller.png b/docs/diagrams/state-poller.png deleted file mode 100644 index be30954..0000000 Binary files a/docs/diagrams/state-poller.png and /dev/null differ diff --git a/docs/diagrams/state-ui.png b/docs/diagrams/state-ui.png deleted file mode 100644 index 1442d70..0000000 Binary files a/docs/diagrams/state-ui.png and /dev/null differ diff --git a/public/app.js b/public/app.js index 0016d07..ddd4f99 100644 --- a/public/app.js +++ b/public/app.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. let currentUser = null; let downloads = []; let isAdmin = false; diff --git a/server/app.js b/server/app.js index 6f15371..da9d319 100644 --- a/server/app.js +++ b/server/app.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Express application factory — imported by both server/index.js (production) * and the test suite. Keeping app creation separate from app.listen() means diff --git a/server/index.js b/server/index.js index 3e40136..9de9820 100644 --- a/server/index.js +++ b/server/index.js @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Gordon Bolton. MIT License. +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); diff --git a/server/middleware/requireAuth.js b/server/middleware/requireAuth.js index 2504fcb..454614c 100644 --- a/server/middleware/requireAuth.js +++ b/server/middleware/requireAuth.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. function requireAuth(req, res, next) { const signed = !!process.env.COOKIE_SECRET; const raw = signed ? req.signedCookies.emby_user : req.cookies.emby_user; diff --git a/server/middleware/verifyCsrf.js b/server/middleware/verifyCsrf.js index 6026870..6eca0ad 100644 --- a/server/middleware/verifyCsrf.js +++ b/server/middleware/verifyCsrf.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * CSRF protection using the double-submit cookie pattern. * diff --git a/server/routes/auth.js b/server/routes/auth.js index caab701..6b2b913 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const axios = require('axios'); const crypto = require('crypto'); diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index 290b42c..c6b389e 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const router = express.Router(); const requireAuth = require('../middleware/requireAuth'); diff --git a/server/routes/emby.js b/server/routes/emby.js index f349e7f..0aa37ea 100644 --- a/server/routes/emby.js +++ b/server/routes/emby.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const axios = require('axios'); const router = express.Router(); diff --git a/server/routes/history.js b/server/routes/history.js index a468574..e91e745 100644 --- a/server/routes/history.js +++ b/server/routes/history.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const router = express.Router(); const axios = require('axios'); diff --git a/server/routes/radarr.js b/server/routes/radarr.js index e9f348b..6d74211 100644 --- a/server/routes/radarr.js +++ b/server/routes/radarr.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const axios = require('axios'); const router = express.Router(); diff --git a/server/routes/sabnzbd.js b/server/routes/sabnzbd.js index 19ff152..fc8a54b 100644 --- a/server/routes/sabnzbd.js +++ b/server/routes/sabnzbd.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const axios = require('axios'); const router = express.Router(); diff --git a/server/routes/sonarr.js b/server/routes/sonarr.js index 889b9ab..aba4bdc 100644 --- a/server/routes/sonarr.js +++ b/server/routes/sonarr.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const axios = require('axios'); const router = express.Router(); diff --git a/server/utils/cache.js b/server/utils/cache.js index daf4253..30640cf 100644 --- a/server/utils/cache.js +++ b/server/utils/cache.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const { logToFile } = require('./logger'); class MemoryCache { diff --git a/server/utils/config.js b/server/utils/config.js index c71f8cd..ca989f5 100644 --- a/server/utils/config.js +++ b/server/utils/config.js @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Gordon Bolton. MIT License. +// Copyright (c) 2026 Gordon Bolton. MIT License. const { logToFile } = require('./logger'); // Validate that a configured service URL is well-formed and uses http(s). diff --git a/server/utils/historyFetcher.js b/server/utils/historyFetcher.js index 3e0707f..8f5066d 100644 --- a/server/utils/historyFetcher.js +++ b/server/utils/historyFetcher.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const axios = require('axios'); const cache = require('./cache'); const { getSonarrInstances, getRadarrInstances } = require('./config'); diff --git a/server/utils/loadSecrets.js b/server/utils/loadSecrets.js index e50058c..e028623 100644 --- a/server/utils/loadSecrets.js +++ b/server/utils/loadSecrets.js @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Gordon Bolton. MIT License. +// Copyright (c) 2026 Gordon Bolton. MIT License. // // Docker secrets support: if an environment variable named FOO_FILE is set, // read its contents from the file at that path and expose it as FOO. diff --git a/server/utils/logger.js b/server/utils/logger.js index 3160d2e..00c6df9 100644 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const fs = require('fs'); const path = require('path'); diff --git a/server/utils/poller.js b/server/utils/poller.js index a631ca5..91df51c 100644 --- a/server/utils/poller.js +++ b/server/utils/poller.js @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Gordon Bolton. MIT License. +// Copyright (c) 2026 Gordon Bolton. MIT License. const axios = require('axios'); const cache = require('./cache'); const { getTorrents } = require('./qbittorrent'); diff --git a/server/utils/qbittorrent.js b/server/utils/qbittorrent.js index 7c218ce..0915037 100644 --- a/server/utils/qbittorrent.js +++ b/server/utils/qbittorrent.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. const axios = require('axios'); const { logToFile } = require('./logger'); const { getQbittorrentInstances } = require('./config'); diff --git a/server/utils/sanitizeError.js b/server/utils/sanitizeError.js index 2a9a60c..7e6a9ba 100644 --- a/server/utils/sanitizeError.js +++ b/server/utils/sanitizeError.js @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Gordon Bolton. MIT License. +// Copyright (c) 2026 Gordon Bolton. MIT License. // Query-param secrets (SABnzbd apikey, generic token/password params) const QUERY_SECRET_PATTERN = /([?&](?:apikey|token|password|api_key|key|secret)=)[^&\s#]*/gi; // HTTP auth header values (X-Api-Key, X-MediaBrowser-Token, Authorization, X-Emby-Authorization) diff --git a/server/utils/tokenStore.js b/server/utils/tokenStore.js index 9a66000..551f88a 100644 --- a/server/utils/tokenStore.js +++ b/server/utils/tokenStore.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Persistent token store backed by a JSON file. * diff --git a/tests/integration/auth.test.js b/tests/integration/auth.test.js index ca98d33..bca64f2 100644 --- a/tests/integration/auth.test.js +++ b/tests/integration/auth.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Integration tests for authentication routes. * diff --git a/tests/integration/health.test.js b/tests/integration/health.test.js index fbe806b..a6530f0 100644 --- a/tests/integration/health.test.js +++ b/tests/integration/health.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Integration tests for health and readiness endpoints. * diff --git a/tests/integration/history.test.js b/tests/integration/history.test.js index d6f924d..4257cda 100644 --- a/tests/integration/history.test.js +++ b/tests/integration/history.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Integration tests for GET /api/history/recent * diff --git a/tests/setup.js b/tests/setup.js index 83ec3d4..8f652d0 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. import { vi, beforeEach, afterEach } from 'vitest'; import os from 'os'; import path from 'path'; diff --git a/tests/unit/config.test.js b/tests/unit/config.test.js index 544e5de..80a74b2 100644 --- a/tests/unit/config.test.js +++ b/tests/unit/config.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/utils/config.js * diff --git a/tests/unit/historyFetcher.test.js b/tests/unit/historyFetcher.test.js index 040a358..9231607 100644 --- a/tests/unit/historyFetcher.test.js +++ b/tests/unit/historyFetcher.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Unit tests for server/utils/historyFetcher.js * diff --git a/tests/unit/qbittorrent.test.js b/tests/unit/qbittorrent.test.js index 72cc467..ddf149d 100644 --- a/tests/unit/qbittorrent.test.js +++ b/tests/unit/qbittorrent.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/utils/qbittorrent.js pure utility functions. * diff --git a/tests/unit/requireAuth.test.js b/tests/unit/requireAuth.test.js index 87cefb4..f7f6533 100644 --- a/tests/unit/requireAuth.test.js +++ b/tests/unit/requireAuth.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/middleware/requireAuth.js * diff --git a/tests/unit/sanitizeError.test.js b/tests/unit/sanitizeError.test.js index a6f3266..3f5c375 100644 --- a/tests/unit/sanitizeError.test.js +++ b/tests/unit/sanitizeError.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/utils/sanitizeError.js * diff --git a/tests/unit/tokenStore.test.js b/tests/unit/tokenStore.test.js index 53e0c3c..910704f 100644 --- a/tests/unit/tokenStore.test.js +++ b/tests/unit/tokenStore.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/utils/tokenStore.js * diff --git a/tests/unit/verifyCsrf.test.js b/tests/unit/verifyCsrf.test.js index 3f0e631..97d52af 100644 --- a/tests/unit/verifyCsrf.test.js +++ b/tests/unit/verifyCsrf.test.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Tests for server/middleware/verifyCsrf.js * diff --git a/vitest.config.js b/vitest.config.js index 5df0b1e..a314db6 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,3 +1,4 @@ +// Copyright (c) 2026 Gordon Bolton. MIT License. import { defineConfig } from 'vitest/config'; export default defineConfig({