diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index b55ef5c..79e30ad 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -130,7 +130,6 @@ export async function register(req, res) { logger?.write("failed to register user", 500); res.status(500).json({ error: "Internal server error" }); } finally { - client.release(); } } diff --git a/backend/app/routes/channel.route.js b/backend/app/routes/channel.route.js index 3663063..063ce3b 100644 --- a/backend/app/routes/channel.route.js +++ b/backend/app/routes/channel.route.js @@ -14,7 +14,44 @@ import {addLogger} from "../middlewares/logger.middleware.js"; const router = Router(); -// CREATE CHANNEL +/** + * @swagger + * tags: + * name: Channels + * description: API for managing channels + * /: + * post: + * summary: Create a new channel + * requestBody: + * required: true +* content: +* application/json: +* schema: +* type: object +* properties: +* name: +* type: string +* description: +* type: string +* owner: +* type: string + * responses: + * 201: + * description: Channel created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: string + * name: + * type: string + * description: + * type: string + * owner: + * type: string + */ router.post("/", [addLogger, isTokenValid, ChannelCreate.name, ChannelCreate.description, ChannelCreate.owner, validator, doUserExistsBody, doUserHaveChannel, isOwnerBody, doChannelNameExists], create); // GET CHANNEL BY ID diff --git a/backend/app/uploads/profiles/test.jpg b/backend/app/uploads/profiles/test.jpg deleted file mode 100644 index d8e9979..0000000 Binary files a/backend/app/uploads/profiles/test.jpg and /dev/null differ diff --git a/backend/logs/access.log b/backend/logs/access.log index c5bf8cf..3ab8444 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11999,3 +11999,76 @@ [2025-09-05 10:06:26.509] [undefined] GET(/:id): Successfully get channel with id 6 with status 200 [2025-09-05 10:06:37.217] [undefined] GET(/:id): try to get channel with id 5 [2025-09-05 10:06:37.227] [undefined] GET(/:id): Successfully get channel with id 5 with status 200 +[2025-09-05 16:39:37.274] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-05 16:41:47.205] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-05 17:01:58.738] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-05 17:04:07.324] [undefined] POST(/): try to register a user with username: test and email: test@test.com +[2025-09-05 17:04:08.497] [undefined] POST(/): successfully registered with status 200 +[2025-09-05 17:04:46.774] [undefined] POST(/): try to register a user with username: teste and email: teste@test.com +[2025-09-05 17:04:47.739] [undefined] POST(/): successfully registered with status 200 +[2025-09-05 17:05:50.681] [undefined] POST(/): try to register a user with username: testre and email: testre@test.com +[2025-09-05 17:05:51.611] [undefined] POST(/): successfully registered with status 200 +[2025-09-05 17:06:30.976] [undefined] POST(/login): try to login with username 'test' +[2025-09-05 17:06:31.027] [undefined] POST(/login): Successfully logged in with status 200 +[2025-09-05 17:10:52.763] [undefined] POST(/login): try to login with username 'test' +[2025-09-05 17:10:52.861] [undefined] POST(/login): Successfully logged in with status 200 +[2025-09-05 17:11:15.670] [undefined] GET(/search): Invalid token with status 401 +[2025-09-05 17:11:27.652] [undefined] POST(/login): try to login with username 'test' +[2025-09-05 17:11:27.751] [undefined] POST(/login): Successfully logged in with status 200 +[2025-09-05 17:11:41.375] [undefined] GET(/search): try to search user by username test +[2025-09-05 17:11:41.432] [undefined] GET(/search): successfully found user with username test with status 200 +[2025-09-05 17:11:51.707] [undefined] GET(/:id): try to retrieve user 2 +[2025-09-05 17:11:51.762] [undefined] GET(/:id): successfully retrieved user 2 with status 200 +[2025-09-05 17:12:15.622] [undefined] PUT(/:id): try to update user 2 +[2025-09-05 17:12:15.630] [undefined] PUT(/:id): successfully updated user 2 with status 200 +[2025-09-05 17:12:28.053] [undefined] DELETE(/:id): failed because he wasn't the owner of the user with status 403 +[2025-09-05 17:12:38.114] [undefined] GET(/username/:username): try to retrieve user string +[2025-09-05 17:12:38.169] [undefined] GET(/username/:username): successfully retrieved user string with status 200 +[2025-09-05 17:12:46.243] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-09-05 17:12:46.249] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404 +[2025-09-05 17:12:52.671] [undefined] GET(/:id/history): try to retrieve history of user 2 +[2025-09-05 17:12:52.726] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404 +[2025-09-05 17:12:58.929] [undefined] GET(/:id/subscriptions): try to retrieve all subscriptions of user 2 +[2025-09-05 17:12:58.936] [undefined] GET(/:id/subscriptions): no subscriptions found for user 2 with status 404 +[2025-09-05 17:13:04.939] [undefined] GET(/:id/subscriptions/videos): try to retrieve all subscriptions of user 2 +[2025-09-05 17:13:04.998] [undefined] GET(/:id/subscriptions/videos): no subscriptions found for user 2 with status 404 +[2025-09-05 17:13:25.706] [undefined] POST(/): try to create new channel with owner 2 and name chien +[2025-09-05 17:13:25.710] [undefined] POST(/): Successfully created new channel with name chien with status 200 +[2025-09-05 17:13:41.559] [undefined] GET(/): try to get all channels +[2025-09-05 17:13:41.564] [undefined] GET(/): Successfully get all channels with status 200 +[2025-09-05 17:13:52.689] [undefined] GET(/:id): try to get channel with id 2 +[2025-09-05 17:13:52.751] [undefined] GET(/:id): Successfully get channel with id 2 with status 200 +[2025-09-05 17:14:09.069] [undefined] PUT(/:id): try to update channel with id 2 +[2025-09-05 17:14:09.130] [undefined] PUT(/:id): Successfully updated channel with status 200 +[2025-09-05 17:14:19.755] [undefined] DELETE(/:id): failed because user do not own the channel with status 403 +[2025-09-05 17:14:29.922] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1 +[2025-09-05 17:53:33.682] [undefined] POST(/:id/subscribe): Invalid token with status 401 +[2025-09-05 17:53:46.173] [undefined] POST(/:id/subscribe): Invalid token with status 401 +[2025-09-05 17:53:56.975] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1 +[2025-09-05 17:55:37.631] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 2 +[2025-09-05 17:55:37.693] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200 +[2025-09-05 17:55:57.145] [undefined] GET(/:id/stats): try to get stats +[2025-09-05 17:55:57.151] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-09-05 17:56:13.319] [undefined] GET(/:id): try to get video 1 +[2025-09-05 17:56:13.350] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-05 17:56:24.622] [undefined] GET(/channel/:id): try to get video from channel 1 +[2025-09-05 17:56:24.627] [undefined] GET(/channel/:id): successfully get video from channel 1 with status 200 +[2025-09-05 17:56:30.445] [undefined] GET(/:id/like): try to toggle like on video 1 +[2025-09-05 17:56:30.453] [undefined] GET(/:id/like): no likes found adding likes for video 1 with status 200 +[2025-09-05 17:56:37.689] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-05 17:56:37.696] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-05 17:56:43.181] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-05 17:56:43.230] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-05 17:56:48.002] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-09-05 17:56:48.063] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-09-05 17:57:00.119] [undefined] POST(/): try to post comment +[2025-09-05 17:57:00.126] [undefined] POST(/): successfully post comment with status 200 +[2025-09-05 17:57:09.337] [undefined] GET(/video/:id): try to get comment from video 1 +[2025-09-05 17:57:09.343] [undefined] GET(/video/:id): successfully get comment with status 200 +[2025-09-05 17:57:15.838] [undefined] GET(/:id): try to get comment 1 +[2025-09-05 17:57:15.843] [undefined] GET(/:id): successfully get comment with status 200 +[2025-09-05 17:57:26.069] [undefined] POST(/): Playlist created with id 5 with status 200 +[2025-09-05 17:57:32.550] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 2 with status 200 +[2025-09-05 17:57:44.224] [undefined] POST(/:id): user not the owner of the playlist with id 1 with status 403 +[2025-09-05 17:58:14.088] [undefined] POST(/:id): Video added to playlist with id 2 with status 200 +[2025-09-05 17:58:21.531] [undefined] GET(/:id): Playlist retrieved with id 2 with status 200 diff --git a/backend/package-lock.json b/backend/package-lock.json index a7ab9bf..9a88007 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -22,7 +22,10 @@ "nodemailer": "^7.0.5", "passport": "^0.7.0", "passport-github2": "^0.1.12", - "pg": "^8.16.3" + "pg": "^8.16.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "yaml": "^2.8.1" }, "devDependencies": { "chai": "^5.2.0", @@ -35,6 +38,50 @@ "vitest": "^3.2.4" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -485,6 +532,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -799,6 +852,13 @@ "win32" ] }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -830,6 +890,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -1038,7 +1104,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/asap": { @@ -1069,7 +1134,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64url": { @@ -1132,7 +1196,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1230,6 +1293,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1480,6 +1549,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -1494,7 +1572,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -1679,6 +1756,18 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.0.1.tgz", @@ -1876,6 +1965,15 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2147,6 +2245,12 @@ "node": ">= 0.8" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2407,6 +2511,17 @@ "dev": true, "license": "ISC" }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2584,7 +2699,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2658,6 +2772,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2670,6 +2791,13 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -2694,6 +2822,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -2875,7 +3009,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3262,6 +3395,13 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3377,6 +3517,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4273,6 +4422,92 @@ "node": ">=4" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz", + "integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -4879,6 +5114,18 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -4981,6 +5228,36 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/backend/package.json b/backend/package.json index c7247bd..93c9231 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,7 +26,10 @@ "nodemailer": "^7.0.5", "passport": "^0.7.0", "passport-github2": "^0.1.12", - "pg": "^8.16.3" + "pg": "^8.16.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "yaml": "^2.8.1" }, "devDependencies": { "chai": "^5.2.0", diff --git a/backend/server.js b/backend/server.js index 1b3aeb0..ab22864 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,4 +1,7 @@ import express from "express"; +import swaggerui from "swagger-ui-express"; +import fs from "fs"; +import YAML from "yaml"; import dotenv from "dotenv"; import UserRoute from "./app/routes/user.route.js"; import ChannelRoute from "./app/routes/channel.route.js"; @@ -26,7 +29,6 @@ const app = express(); // Increase body size limits for file uploads app.use(express.urlencoded({extended: true, limit: '500mb'})); app.use(express.json({limit: '500mb'})); -app.use(cors()) app.use(session({ secret: "your-secret", @@ -34,6 +36,23 @@ app.use(session({ saveUninitialized: false, })); +// Swagger setup +const file = fs.readFileSync('./swagger.yaml', 'utf8'); +const swaggerDocument = YAML.parse(file); + +// Swagger UI options +const swaggerOptions = { + explorer: true, + swaggerOptions: { + requestInterceptor: (req) => { + req.headers['Content-Type'] = 'application/json'; + return req; + } + } +}; + +app.use('/api/api-docs', swaggerui.serve, swaggerui.setup(swaggerDocument, swaggerOptions)); + // --- Passport setup --- app.use(passport.initialize()); app.use(passport.session()); diff --git a/backend/swagger.yaml b/backend/swagger.yaml new file mode 100644 index 0000000..ba6adf4 --- /dev/null +++ b/backend/swagger.yaml @@ -0,0 +1,2317 @@ +openapi: 3.0.0 +info: + title: FreeTube - Video Sharing Platform API + version: 1.0.0 + description: API documentation for the FreeTube Video Sharing Platform + contact: + name: FreeTube Team + email: contact@freetube.com +servers: + - url: https://localhost + description: Local development server + +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + User: + type: object + properties: + id: + type: integer + username: + type: string + email: + type: string + format: email + picture: + type: string + created_at: + type: string + format: date-time + + Channel: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + owner: + type: integer + subscribers: + type: integer + created_at: + type: string + format: date-time + + Video: + type: object + properties: + id: + type: integer + title: + type: string + description: + type: string + file: + type: string + thumbnail: + type: string + visibility: + type: string + enum: [public, private, unlisted] + channel: + type: integer + views: + type: integer + likes: + type: integer + release_date: + type: string + format: date-time + tags: + type: array + items: + type: string + + Comment: + type: object + properties: + id: + type: integer + content: + type: string + video: + type: integer + author: + type: integer + created_at: + type: string + format: date-time + + Playlist: + type: object + properties: + id: + type: integer + name: + type: string + owner: + type: integer + created_at: + type: string + format: date-time + + Error: + type: object + properties: + message: + type: string + error: + type: string + +paths: + # USER ENDPOINTS + /api/users: + post: + tags: + - Users + summary: Register a new user + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + username: + type: string + email: + type: string + format: email + password: + type: string + minLength: 6 + profile: + type: string + format: binary + required: + - username + - email + - password + responses: + '201': + description: User created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Bad request - validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: User already exists + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/login: + post: + tags: + - Users + summary: Login user + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + password: + type: string + required: + - username + - password + responses: + '200': + description: Login successful + content: + application/json: + schema: + type: object + properties: + token: + type: string + user: + $ref: '#/components/schemas/User' + '401': + description: Invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/search: + get: + tags: + - Users + summary: Search users by username + security: + - BearerAuth: [] + parameters: + - name: username + in: query + required: true + schema: + type: string + responses: + '200': + description: Users found + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/{id}: + get: + tags: + - Users + summary: Get user by ID + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + tags: + - Users + summary: Update user + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + email: + type: string + format: email + responses: + '200': + description: User updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + delete: + tags: + - Users + summary: Delete user + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: User deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/username/{username}: + get: + tags: + - Users + summary: Get user by username + security: + - BearerAuth: [] + parameters: + - name: username + in: path + required: true + schema: + type: string + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/{id}/channel: + get: + tags: + - Users + summary: Get user's channel + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Channel found + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/{id}/history: + get: + tags: + - Users + summary: Get user's watch history + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: History retrieved + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/{id}/subscriptions: + get: + tags: + - Users + summary: Get user's subscriptions + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Subscriptions retrieved + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Channel' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/{id}/subscriptions/videos: + get: + tags: + - Users + summary: Get videos from subscribed channels + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Subscription videos retrieved + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/users/verify-email: + post: + tags: + - Users + summary: Verify user email + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + email: + type: string + format: email + required: + - token + - email + responses: + '200': + description: Email verified successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '400': + description: Invalid or expired token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # CHANNEL ENDPOINTS + /api/channels: + post: + tags: + - Channels + summary: Create a new channel + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: + type: string + owner: + type: integer + required: + - name + - description + - owner + responses: + '201': + description: Channel created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: Channel name already exists + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + get: + tags: + - Channels + summary: Get all channels + security: + - BearerAuth: [] + responses: + '200': + description: Channels retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Channel' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/channels/{id}: + get: + tags: + - Channels + summary: Get channel by ID + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Channel found + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + tags: + - Channels + summary: Update channel + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: + type: string + responses: + '200': + description: Channel updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + delete: + tags: + - Channels + summary: Delete channel + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Channel deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/channels/{id}/subscribe: + post: + tags: + - Channels + summary: Toggle subscription to channel + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + userId: + type: integer + required: + - userId + responses: + '200': + description: Subscription toggled successfully + content: + application/json: + schema: + type: object + properties: + subscribed: + type: boolean + subscriptions: + type: integer + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/channels/{id}/stats: + get: + tags: + - Channels + summary: Get channel statistics + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Channel statistics retrieved + content: + application/json: + schema: + type: object + properties: + total_views: + type: integer + subscribers: + type: integer + videos_count: + type: integer + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # VIDEO ENDPOINTS + /api/videos: + post: + tags: + - Videos + summary: Upload a new video + security: + - BearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + title: + type: string + description: + type: string + visibility: + type: string + enum: [public, private, unlisted] + channel: + type: integer + authorizedUsers: + type: array + items: + type: integer + required: + - file + - title + - description + - visibility + - channel + responses: + '201': + description: Video uploaded successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the channel owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/thumbnail: + post: + tags: + - Videos + summary: Upload or update video thumbnail + security: + - BearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + video: + type: integer + channel: + type: integer + required: + - file + - video + - channel + responses: + '200': + description: Thumbnail uploaded successfully + content: + application/json: + schema: + type: object + properties: + thumbnail: + type: string + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the channel owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}: + get: + tags: + - Videos + summary: Get video by ID + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Video found + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - no access to video + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + tags: + - Videos + summary: Update video metadata + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + title: + type: string + description: + type: string + visibility: + type: string + enum: [public, private, unlisted] + channel: + type: integer + responses: + '200': + description: Video updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + delete: + tags: + - Videos + summary: Delete video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Video deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/video: + put: + tags: + - Videos + summary: Update video file + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + channel: + type: integer + required: + - file + - channel + responses: + '200': + description: Video file updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/channel/{id}: + get: + tags: + - Videos + summary: Get videos by channel + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Videos retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Channel not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/like: + get: + tags: + - Videos + summary: Toggle like on video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Like toggled successfully + content: + application/json: + schema: + type: object + properties: + liked: + type: boolean + likes: + type: integer + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/tags: + put: + tags: + - Videos + summary: Update video tags + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + tags: + type: array + items: + type: string + responses: + '200': + description: Tags updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/similar: + get: + tags: + - Videos + summary: Get similar videos + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Similar videos retrieved + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/views: + get: + tags: + - Videos + summary: Add view to video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: View added successfully + content: + application/json: + schema: + type: object + properties: + views: + type: integer + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/likes/day: + get: + tags: + - Videos + summary: Get likes per day for video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Likes per day retrieved + content: + application/json: + schema: + type: array + items: + type: object + properties: + date: + type: string + format: date + likes: + type: integer + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/videos/{id}/authorized-users: + put: + tags: + - Videos + summary: Update authorized users for private video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + authorizedUsers: + type: array + items: + type: integer + responses: + '200': + description: Authorized users updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Video' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # COMMENT ENDPOINTS + /api/comments: + post: + tags: + - Comments + summary: Create a new comment + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + video: + type: integer + required: + - content + - video + responses: + '201': + description: Comment created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/comments/video/{id}: + get: + tags: + - Comments + summary: Get comments for a video + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Comments retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comment' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/comments/{id}: + get: + tags: + - Comments + summary: Get comment by ID + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Comment found + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + tags: + - Comments + summary: Update comment + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + video: + type: integer + required: + - content + - video + responses: + '200': + description: Comment updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the author + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + delete: + tags: + - Comments + summary: Delete comment + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Comment deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the author + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # PLAYLIST ENDPOINTS + /api/playlists: + post: + tags: + - Playlists + summary: Create a new playlist + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + required: + - name + responses: + '200': + description: Playlist created successfully + content: + application/json: + schema: + type: object + properties: + id: + type: integer + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/playlists/see-later: + get: + tags: + - Playlists + summary: Get "See Later" playlist + security: + - BearerAuth: [] + responses: + '200': + description: See Later playlist retrieved + content: + application/json: + schema: + $ref: '#/components/schemas/Playlist' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/playlists/{id}: + post: + tags: + - Playlists + summary: Add video to playlist + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + video: + type: integer + required: + - video + responses: + '200': + description: Video added to playlist successfully + content: + application/json: + schema: + type: object + properties: + id: + type: integer + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist or video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + get: + tags: + - Playlists + summary: Get playlist by ID + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Playlist retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Playlist' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + tags: + - Playlists + summary: Update playlist + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + required: + - name + responses: + '200': + description: Playlist updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Playlist' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + delete: + tags: + - Playlists + summary: Delete playlist + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Playlist deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/playlists/user/{id}: + get: + tags: + - Playlists + summary: Get playlists by user + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: User playlists retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Playlist' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: User not found or no playlists found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/playlists/{id}/video/{videoId}: + delete: + tags: + - Playlists + summary: Remove video from playlist + security: + - BearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + - name: videoId + in: path + required: true + schema: + type: integer + responses: + '200': + description: Video removed from playlist successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Forbidden - not the owner + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Playlist or video not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # SEARCH ENDPOINTS + /api/search: + get: + tags: + - Search + summary: Search videos and channels + parameters: + - name: q + in: query + required: true + schema: + type: string + description: Search query + - name: type + in: query + schema: + type: string + enum: [videos, channel] + default: videos + description: Type of search (videos or channel) + responses: + '200': + description: Search results retrieved successfully + content: + application/json: + schema: + oneOf: + - type: array + items: + $ref: '#/components/schemas/Video' + - type: array + items: + $ref: '#/components/schemas/Channel' + '400': + description: Bad request - query parameter required or invalid type + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # RECOMMENDATION ENDPOINTS + /api/recommendations: + get: + tags: + - Recommendations + summary: Get personalized recommendations + responses: + '200': + description: Recommendations retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/recommendations/trending: + get: + tags: + - Recommendations + summary: Get trending videos + responses: + '200': + description: Trending videos retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Video' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/recommendations/creators: + get: + tags: + - Recommendations + summary: Get top creators + responses: + '200': + description: Top creators retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Channel' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # MEDIA ENDPOINTS + /api/media/profile/{file}: + get: + tags: + - Media + summary: Get profile picture + parameters: + - name: file + in: path + required: true + schema: + type: string + responses: + '200': + description: Profile picture retrieved successfully + content: + image/*: + schema: + type: string + format: binary + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/media/video/{file}: + get: + tags: + - Media + summary: Get video file + parameters: + - name: file + in: path + required: true + schema: + type: string + responses: + '200': + description: Video file retrieved successfully + content: + video/*: + schema: + type: string + format: binary + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/media/thumbnail/{file}: + get: + tags: + - Media + summary: Get video thumbnail + parameters: + - name: file + in: path + required: true + schema: + type: string + responses: + '200': + description: Thumbnail retrieved successfully + content: + image/*: + schema: + type: string + format: binary + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + # OAUTH ENDPOINTS + /api/oauth/github: + get: + tags: + - OAuth + summary: Initiate GitHub OAuth login + responses: + '302': + description: Redirect to GitHub OAuth + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/oauth/callback: + get: + tags: + - OAuth + summary: GitHub OAuth callback + parameters: + - name: code + in: query + required: true + schema: + type: string + - name: state + in: query + schema: + type: string + responses: + '302': + description: Redirect after successful authentication + '400': + description: OAuth error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /api/oauth/me: + get: + tags: + - OAuth + summary: Get current authenticated user info + security: + - BearerAuth: [] + responses: + '200': + description: User info retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' \ No newline at end of file