From d3b948385c630ec0ee11fdc99af032167b4f08c9 Mon Sep 17 00:00:00 2001 From: astria Date: Fri, 5 Sep 2025 19:59:16 +0200 Subject: [PATCH] Added swagger.yaml --- backend/app/controllers/user.controller.js | 1 - backend/app/routes/channel.route.js | 39 +- backend/app/uploads/profiles/test.jpg | Bin 10613 -> 0 bytes backend/logs/access.log | 73 + backend/package-lock.json | 291 ++- backend/package.json | 5 +- backend/server.js | 21 +- backend/swagger.yaml | 2317 ++++++++++++++++++++ 8 files changed, 2736 insertions(+), 11 deletions(-) delete mode 100644 backend/app/uploads/profiles/test.jpg create mode 100644 backend/swagger.yaml 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 d8e99796710e6e338b8c179a43515971b911a609..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10613 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 h>Ve?1);pcjjn#b&kMj_-eO#B|`L@m>4wEG0*@2 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