From 148ed696b2811058d0994629bda0e1d9af5ced98 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Thu, 17 Jul 2025 09:52:37 -0400 Subject: [PATCH] feat: Add Swagger API documentation This commit introduces Swagger API documentation for all endpoints in the application. - Installs and . - Configures Swagger in to generate and serve API documentation at . - Adds JSDoc-style Swagger annotations to all routes in and the directory (, , , , , ). - Defines a cookie-based security scheme for authenticated routes. This allows for interactive API documentation and testing via the endpoint. --- index.js | 294 +++++++++++++++++++++++++++++- package-lock.json | 433 +++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + routes/bible.js | 151 +++++++++++++++ routes/payments.js | 74 ++++++++ routes/post.js | 382 ++++++++++++++++++++++++++++++++++++++ routes/profile.js | 426 +++++++++++++++++++++++++++++++++++++++++++ routes/songs.js | 100 ++++++++++ routes/subsplash.js | 39 ++++ 9 files changed, 1891 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 87883d0..22b8d94 100644 --- a/index.js +++ b/index.js @@ -39,9 +39,119 @@ app.use(limiter); // Authentication const { signup, login, logout, resetPassword } = require('./auth/authEmail.js'); +/** + * @swagger + * /signup: + * post: + * summary: Signs up a new user + * tags: [Auth] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * password: + * type: string + * format: password + * responses: + * 200: + * description: The user was successfully signed up. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * 400: + * description: Bad request. + */ app.route('/signup').get(signup).post(signup); +/** + * @swagger + * /login: + * post: + * summary: Logs in a user + * tags: [Auth] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * password: + * type: string + * format: password + * responses: + * 200: + * description: The user was successfully logged in. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * 401: + * description: Invalid credentials. + */ app.route('/login').get(login).post(login); +/** + * @swagger + * /logout: + * get: + * summary: Logs out a user + * tags: [Auth] + * responses: + * 200: + * description: The user was successfully logged out. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + */ app.get('/logout', logout); +/** + * @swagger + * /resetPassword: + * post: + * summary: Resets a user's password + * tags: [Auth] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * responses: + * 200: + * description: A password reset link has been sent to the user's email. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * 400: + * description: Bad request. + */ app.route('/resetPassword').post(resetPassword); // Routes @@ -61,6 +171,42 @@ app.use('/songs', sessionChecker, songsRoute); const subsplashRoute = require('./routes/subsplash.js'); app.use('/subsplash', subsplashRoute); +// Swagger API Docs +const swaggerJSDoc = require('swagger-jsdoc'); +const swaggerUi = require('swagger-ui-express'); + +const swaggerDefinition = { + openapi: '3.0.0', + info: { + title: 'EMI Backend API', + version: '1.0.0', + description: 'This is the REST API for the EMI Backend' + }, + servers: [ + { + url: 'http://localhost:3000', + description: 'Development server' + } + ], + components: { + securitySchemes: { + cookieAuth: { + type: 'apiKey', + in: 'cookie', + name: 'user_sid' + } + } + } +}; + +const options = { + swaggerDefinition, + apis: ['./index.js', './routes/*.js'] +}; + +const swaggerSpec = swaggerJSDoc(options); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + // Web Push Notifications const webPush = require('web-push'); const publicVapidKey = process.env.PUBLIC_VAPID_KEY; @@ -78,7 +224,31 @@ const DB = require("./mongoDB.js"); DB.getDB.then((DB) => { console.log("Main logic: DB connected!"); - // route for Home-Page + /** + * @swagger + * /: + * get: + * summary: Returns basic information about the logged-in user + * tags: [General] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * userInfo: + * type: object + * profileInfo: + * type: object + * 401: + * description: Unauthorized + */ app.get('/', sessionChecker, async (req, res) => { try { const userInfo = req.userInfo; @@ -97,7 +267,38 @@ DB.getDB.then((DB) => { } }); - // Check for an invitation for an email + /** + * @swagger + * /invite/{email}: + * get: + * summary: Checks if an invitation exists for a given email + * tags: [General] + * parameters: + * - in: path + * name: email + * required: true + * schema: + * type: string + * format: email + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * invitation: + * type: object + * 400: + * description: Provide a valid email + * 404: + * description: No invitation found for this email + * 409: + * description: This user is already registered + */ app.get("/invite/:email", async (req, res) => { try { const email = req.params.email.trim().toLowerCase(); @@ -130,7 +331,42 @@ DB.getDB.then((DB) => { }); - // Change the active profile for the user. + /** + * @swagger + * /changeProfile: + * post: + * summary: Changes the active profile for the user + * tags: [General] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * profileid: + * type: string + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * profile: + * type: object + * 400: + * description: Profile ID is required + * 403: + * description: Profile does not belong to the logged-in user + * 404: + * description: Profile does not exist + */ app.post('/changeProfile', sessionChecker, async (req, res) => { try { const user_sid = getUserId(req); @@ -157,7 +393,38 @@ DB.getDB.then((DB) => { } }); - // This is the endpoint to refresh the push notification token + /** + * @swagger + * /token: + * post: + * summary: Refreshes the push notification token + * tags: [General] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * 400: + * description: Token is required + * 500: + * description: Failed to update token + */ app.post('/token/', sessionChecker, async (req, res) => { try { const profileid = getProfileId(req); @@ -179,7 +446,24 @@ DB.getDB.then((DB) => { } }); - // Used for webpush notifications + /** + * @swagger + * /subscribe: + * post: + * summary: Subscribes a user to webpush notifications + * tags: [General] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 201: + * description: Created + */ app.post('/subscribe', sessionChecker, async (req, res) => { const subscription = req.body; res.status(201).json({}); diff --git a/package-lock.json b/package-lock.json index 53e8172..130d55d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "posthog-node": "^4.4.1", "socket.io": "^4.6.1", "stripe": "^8.178.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "web-push": "^3.4.5" }, "devDependencies": { @@ -34,6 +36,50 @@ "supertest": "^7.0.0" } }, + "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/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -137,6 +183,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "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/@mapbox/node-pre-gyp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", @@ -167,6 +219,13 @@ "node": ">=14" } }, + "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/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -185,6 +244,12 @@ "@types/node": "*" } }, + "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/node": { "version": "16.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", @@ -305,7 +370,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/array-flatten": { @@ -516,6 +580,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", @@ -757,6 +827,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", @@ -983,6 +1062,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/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1221,6 +1312,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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", @@ -1884,7 +1984,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" @@ -1928,6 +2027,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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.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.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/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2421,6 +2540,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/optional-require": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", @@ -3251,6 +3377,83 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "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-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.27.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz", + "integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==", + "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/tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -3326,6 +3529,15 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3563,6 +3775,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "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/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -3668,9 +3889,73 @@ "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" + } } }, "dependencies": { + "@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==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@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==" + }, + "@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==" + }, + "@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "requires": { + "@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" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3736,6 +4021,11 @@ } } }, + "@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==" + }, "@mapbox/node-pre-gyp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", @@ -3759,6 +4049,11 @@ "dev": true, "optional": true }, + "@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -3777,6 +4072,11 @@ "@types/node": "*" } }, + "@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==" + }, "@types/node": { "version": "16.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", @@ -3866,8 +4166,7 @@ "argparse": { "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 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-flatten": { "version": "1.1.1", @@ -4032,6 +4331,11 @@ "get-intrinsic": "^1.2.6" } }, + "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==" + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -4199,6 +4503,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + }, "component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -4361,6 +4670,14 @@ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, "dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -4525,6 +4842,11 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4979,7 +5301,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -5012,6 +5333,21 @@ "p-locate": "^5.0.0" } }, + "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==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -5346,6 +5682,12 @@ "wrappy": "1" } }, + "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==", + "peer": true + }, "optional-require": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", @@ -5940,6 +6282,58 @@ "has-flag": "^4.0.0" } }, + "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==", + "requires": { + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "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" + } + } + } + }, + "swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "requires": { + "@apidevtools/swagger-parser": "10.0.3" + } + }, + "swagger-ui-dist": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz", + "integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==", + "requires": { + "@scarf/scarf": "=1.4.0" + } + }, + "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==", + "requires": { + "swagger-ui-dist": ">=5.0.0" + } + }, "tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -5996,6 +6390,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6154,6 +6553,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==" + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -6226,6 +6630,25 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "requires": { + "commander": "^9.4.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true + } + } } } } diff --git a/package.json b/package.json index 21c279a..8085319 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "posthog-node": "^4.4.1", "socket.io": "^4.6.1", "stripe": "^8.178.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "web-push": "^3.4.5" }, "devDependencies": { diff --git a/routes/bible.js b/routes/bible.js index e33c07a..109123a 100644 --- a/routes/bible.js +++ b/routes/bible.js @@ -13,11 +13,47 @@ const defaultBibleId = "592420522e16049f-01"; //getMedia('y42zyf3').then(console.log) DB.getDB.then((DB) => { + /** + * @swagger + * tags: + * name: Bible + * description: Bible API + */ + +/** + * @swagger + * /bible: + * get: + * summary: Get a list of available Bibles + * tags: [Bible] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("", async (req, res) => { const bibles = await fetchAPI('bibles'); return res.json(bibles); }); +/** + * @swagger + * /bible/books: + * get: + * summary: Get the books of a Bible + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/books", async (req, res) => { const bibleId = req.query.bibleId || defaultBibleId; const bibles = await fetchAPI('bibles/' + bibleId +"/books"); @@ -30,12 +66,56 @@ DB.getDB.then((DB) => { return res.json(bibles); }); + /** + * @swagger + * /bible/books/{bookId}: + * get: + * summary: Get details for a specific book + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: bookId + * required: true + * schema: + * type: string + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/books/:bookId", async (req, res) => { const bookId = req.params.bookId; const bibles = await fetchAPI('bibles/' + bibleId +"/books/" + bookId); return res.json(bibles); }); + /** + * @swagger + * /bible/books/{bookId}/chapters: + * get: + * summary: Get the chapters of a book + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: bookId + * required: true + * schema: + * type: string + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/books/:bookId/chapters", async (req, res) => { const bookId = req.params.bookId; const bibleId = req.query.bibleId || defaultBibleId; @@ -43,6 +123,28 @@ DB.getDB.then((DB) => { return res.json(bibles); }); + /** + * @swagger + * /bible/chapters/{chapterId}: + * get: + * summary: Get the content of a chapter + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: chapterId + * required: true + * schema: + * type: string + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/chapters/:chapterId", async (req, res) => { const chapterId = req.params.chapterId; const bibleId = req.query.bibleId || defaultBibleId; @@ -50,6 +152,28 @@ DB.getDB.then((DB) => { return res.json(bibles); }); + /** + * @swagger + * /bible/chapters/{chapterId}/verses: + * get: + * summary: Get the verses of a chapter + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: chapterId + * required: true + * schema: + * type: string + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/chapters/:chapterId/verses", async (req, res) => { const chapterId = req.params.chapterId; const bibleId = req.query.bibleId || defaultBibleId; @@ -57,6 +181,33 @@ DB.getDB.then((DB) => { return res.json(bibles); }); + /** + * @swagger + * /bible/search: + * get: + * summary: Search the Bible + * tags: [Bible] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: query + * required: true + * schema: + * type: string + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * - in: query + * name: bibleId + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/search", async (req, res) => { const query = req.query.query; const limit = req.query.limit || 10; diff --git a/routes/payments.js b/routes/payments.js index 3f2b129..9928933 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -39,6 +39,13 @@ DB.getDB.then((DB) => { // }); // }); + /** + * @swagger + * tags: + * name: Payments + * description: Payment processing + */ + let intent = async (req, res) => { const userid = req.body.userid; const price = req.body.price || 500; @@ -94,9 +101,76 @@ DB.getDB.then((DB) => { }; + /** + * @swagger + * /payments/create-payment-intent: + * post: + * summary: Creates a Stripe Payment Intent + * tags: [Payments] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * userid: + * type: string + * price: + * type: number + * description: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/create-payment-intent", intent); + /** + * @swagger + * /payments/intent: + * post: + * summary: Creates a Stripe Payment Intent (Alias for /create-payment-intent) + * tags: [Payments] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * userid: + * type: string + * price: + * type: number + * description: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/intent", intent); + /** + * @swagger + * /payments/register: + * post: + * summary: Registers a payment after a successful Stripe transaction + * tags: [Payments] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * userid: + * type: string + * result: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/register", async (req, res) => { const userid = req.body.userid; const result = req.body.result; diff --git a/routes/post.js b/routes/post.js index f87d47d..2168d11 100644 --- a/routes/post.js +++ b/routes/post.js @@ -77,6 +77,25 @@ DB.getDB.then((DB) => { return mergedPosts; }; + /** + * @swagger + * tags: + * name: Posts + * description: Post management + */ + +/** + * @swagger + * /post/organic: + * get: + * summary: Get the organic feed for the current user + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/organic", async (req, res) => { const profileid = getProfileId(req); let organicPosts = await DB.getFeed(profileid); @@ -86,6 +105,18 @@ DB.getDB.then((DB) => { return res.json(posts); }); + /** + * @swagger + * /post: + * get: + * summary: Get the feed with promotional content + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/", async (req, res) => { const profileid = getProfileId(req); //Add non-organic posts @@ -95,6 +126,26 @@ DB.getDB.then((DB) => { return res.json(posts); }); + /** + * @swagger + * /post/tag/{tag}: + * get: + * summary: Get posts with a specific tag + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: tag + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + * 400: + * description: Tag is required + */ router.get("/tag/:tag", async (req, res) => { const profileid = getProfileId(req); const tag = req.query.tag || req.params.tag; @@ -109,6 +160,24 @@ DB.getDB.then((DB) => { return res.json(posts); }); + /** + * @swagger + * /post/usr/{id}: + * get: + * summary: Get posts from a specific user + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/usr/:id", async (req, res) => { const profileId = req.params.id; const viewerProdileId = getProfileId(req); @@ -124,6 +193,24 @@ DB.getDB.then((DB) => { return res.json(posts); }); + /** + * @swagger + * /post/usr/{id}/images: + * get: + * summary: Get all image posts from a user + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/usr/:id/images", async (req, res) => { const profileid = req.params.id; const viewerProfileId = getProfileId(req); @@ -134,6 +221,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/usr/{id}/embedded: + * get: + * summary: Get all embedded posts from a user + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/usr/:id/embedded", async (req, res) => { const profileid = req.params.id; const viewerProfileId = getProfileId(req); @@ -144,6 +249,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/usr/{id}/media: + * get: + * summary: Get all media posts from a user + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/usr/:id/media", async (req, res) => { const profileid = req.params.id; const viewerProfileId = getProfileId(req); @@ -154,11 +277,49 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/video/{id}: + * get: + * summary: Get video details by ID + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/video/:id", async (req, res) => { videoId = req.params.id; return res.json([]); }); + /** + * @swagger + * /post: + * post: + * summary: Create a new post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: OK + * 403: + * description: Not authorized to post to this group + */ router.post("/", async (req, res) => { let post = { profileid: getProfileId(req), @@ -198,6 +359,27 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/react: + * post: + * summary: React to a post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/react", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -212,6 +394,27 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/unreact: + * post: + * summary: Remove a reaction from a post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/unreact", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -221,6 +424,27 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/bookmark: + * post: + * summary: Bookmark a post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/bookmark", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -230,6 +454,27 @@ DB.getDB.then((DB) => { }); }) + /** + * @swagger + * /post/unbookmark: + * post: + * summary: Remove a bookmark from a post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/unbookmark", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -239,6 +484,29 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/comment: + * post: + * summary: Add a comment to a post + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * content: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/comment/", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -258,6 +526,30 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/comment/react: + * post: + * summary: React to a comment + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * commentDate: + * type: string + * format: date-time + * responses: + * 200: + * description: OK + */ router.post("/comment/react", async (req, res) => { let userid = getProfileId(req); let postid = req.body.postid; @@ -272,6 +564,30 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/comment/unreact: + * post: + * summary: Remove a reaction from a comment + * tags: [Posts] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * postid: + * type: string + * commentDate: + * type: string + * format: date-time + * responses: + * 200: + * description: OK + */ router.post("/comment/unreact", async (req, res) => { let profileid = getProfileId(req); let postid = req.body.postid; @@ -282,6 +598,18 @@ DB.getDB.then((DB) => { }) }); + /** + * @swagger + * /post/images: + * get: + * summary: Get all image posts for the current user + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/images", async (req, res) => { const profileid = getProfileId(req); const posts = await DB.getMediaTagPostOfUser(profileid, profileid); @@ -291,6 +619,18 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/embedded: + * get: + * summary: Get all embedded posts for the current user + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/embedded", async (req, res) => { const profileid = getProfileId(req); const posts = await DB.getMediaTagPostOfUser(profileid, profileid, "@iframe:"); @@ -300,6 +640,18 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/media: + * get: + * summary: Get all media posts for the current user + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/media", async (req, res) => { const profileid = getProfileId(req); const posts = await DB.getMediaTagPostOfUser(profileid, profileid, "@youtube:|@vimeo:|@hls:"); @@ -309,6 +661,18 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/course/recent: + * get: + * summary: Get recently watched media from courses + * tags: [Posts] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/course/recent", async (req, res) => { const profileid = getProfileId(req); const profile = await DB.getProfileCache(profileid); @@ -356,6 +720,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /post/{id}: + * get: + * summary: Get a specific post by ID + * tags: [Posts] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/:id", async (req, res) => { const postId = req.params.id; const post = await DB.getPost(postId); diff --git a/routes/profile.js b/routes/profile.js index 1af57cb..f61b149 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -14,6 +14,36 @@ DB.getDB.then((DB) => { return profile.userid === String(userid); } + /** + * @swagger + * tags: + * name: Profiles + * description: User profile management + */ + +/** + * @swagger + * /user/mine: + * get: + * summary: Get all profiles for the logged-in user + * tags: [Profiles] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * profiles: + * type: array + * items: + * type: object + */ router.get("/mine", async (req, res) => { let userid = getUserId(req); let profiles = await DB.getUserProfiles(userid); @@ -23,6 +53,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/new: + * get: + * summary: (DEPRECATED) Create a new profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: content + * required: true + * schema: + * type: object + * responses: + * 200: + * description: OK + */ router.get("/new", async (req, res) => { //Deprecated please use route post("/") let profile = { userid: getUserId(req), @@ -36,6 +84,27 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user: + * post: + * summary: Create a new profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/", async (req, res) => { let profile = { userid: getUserId(req), @@ -57,6 +126,32 @@ DB.getDB.then((DB) => { }); + /** + * @swagger + * /user/invite: + * post: + * summary: Invite a new user by email + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * email: + * type: string + * format: email + * responses: + * 200: + * description: OK + * 400: + * description: Bad request + */ router.post("/invite", async (req, res) => { try { const userid = getUserId(req); @@ -94,6 +189,25 @@ DB.getDB.then((DB) => { } }); + /** + * @swagger + * /user/invite/{email}: + * get: + * summary: Get invitation details for an email + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: email + * required: true + * schema: + * type: string + * format: email + * responses: + * 200: + * description: OK + */ router.get("/invite/:email", async (req, res) => { const userid = getUserId(req); const email = req.params.email; @@ -106,6 +220,18 @@ DB.getDB.then((DB) => { return res.json({ status: "ok", ...r }); }); + /** + * @swagger + * /user/groups: + * get: + * summary: Get a list of all groups + * tags: [Profiles] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/groups", async (req, res) => { let groups = await DB.getGroups(); return res.json({ @@ -114,6 +240,18 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/following: + * get: + * summary: Get a list of groups the current profile is following + * tags: [Profiles] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/groups/following", async (req, res) => { const profileId = getProfileId(req); let groups = await DB.getFollowingGroups(profileId); @@ -123,6 +261,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups: + * post: + * summary: Create a new group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/groups", async (req, res) => { let profile = { userid: getUserId(req), @@ -137,6 +293,18 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/courses: + * get: + * summary: Get a list of all courses + * tags: [Profiles] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/courses", async (req, res) => { let groups = await DB.getCourses(); return res.json({ @@ -145,6 +313,29 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/accept: + * post: + * summary: Accept a request to join a private group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * groupid: + * type: string + * profileid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/groups/accept", async (req, res) => { //This function should be called to accept the join request //of an user that attempt to join a private group. @@ -164,6 +355,29 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/reject: + * post: + * summary: Reject a request to join a private group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * groupid: + * type: string + * profileid: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/groups/reject", async (req, res) => { //This function should be called to reject the join request //of an user that attempt to join a private group. @@ -183,6 +397,28 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/search: + * get: + * summary: Search for groups + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: query + * required: true + * schema: + * type: string + * - in: query + * name: courses + * schema: + * type: boolean + * responses: + * 200: + * description: OK + */ router.get("/groups/search", async (req, res) => { let query = req.query.query; let coursesB = req.query.courses ? true : false; @@ -193,6 +429,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/{id}: + * get: + * summary: Get details for a specific group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/groups/:id", async (req, res) => { const groupid = req.params.id; let groups = await DB.getGroup(groupid); @@ -202,6 +456,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/{id}/subscribe: + * get: + * summary: Subscribe to a group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/groups/:id/subscribe", async (req, res) => { const groupid = req.params.id; const profileid = getProfileId(req); @@ -214,6 +486,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/groups/{id}/unsubscribe: + * get: + * summary: Unsubscribe from a group + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/groups/:id/unsubscribe", async (req, res) => { const groupid = req.params.id; const profileid = getProfileId(req); @@ -224,6 +514,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/search: + * get: + * summary: Search for profiles + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: query + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/search", async (req, res) => { let query = req.query.query; let profiles = await DB.searchProfile(query); @@ -233,6 +541,29 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/setData: + * post: + * summary: Set custom data for a profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * key: + * type: string + * value: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/setData", (req, res) => { const key = req.body.key; const value = req.body.value; @@ -243,6 +574,29 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/myProfile: + * post: + * summary: Update the current user's profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * profile: + * type: object + * data: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/myProfile", async (req, res) => { let profile = { userid: getUserId(req), @@ -256,6 +610,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/{id}: + * get: + * summary: Get a specific profile by ID + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/:id", async (req, res) => { let profileId = req.params.id; let profile = await DB.getProfile(profileId); @@ -265,6 +637,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/{id}: + * delete: + * summary: Delete a profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.delete("/:id", async (req, res) => { const profileId = req.params.id; const userid = getUserId(req); @@ -278,6 +668,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/{id}/follow: + * get: + * summary: Follow a profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/:id/follow", async (req, res) => { let followProfileId = req.params.id; const profileid = getProfileId(req); @@ -288,6 +696,24 @@ DB.getDB.then((DB) => { }); }); + /** + * @swagger + * /user/{id}/unfollow: + * get: + * summary: Unfollow a profile + * tags: [Profiles] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/:id/unfollow", async (req, res) => { let followProfileId = req.params.id; const profileid = getProfileId(req); diff --git a/routes/songs.js b/routes/songs.js index 760cbc5..a258536 100644 --- a/routes/songs.js +++ b/routes/songs.js @@ -15,6 +15,25 @@ DB.getDB.then((DB)=>{ return DB.ObjectID(req.cookies.profile_id || req.query.profile_id || req.body.profile_id); } + /** + * @swagger + * tags: + * name: Songs + * description: Song management + */ + +/** + * @swagger + * /songs: + * get: + * summary: Get all songs + * tags: [Songs] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: OK + */ router.get("/", async (req, res) => { let profileId = req.params.id; let songs = await DB.getSongs(); @@ -24,6 +43,24 @@ DB.getDB.then((DB)=>{ }); }); + /** + * @swagger + * /songs: + * post: + * summary: Create a new song + * tags: [Songs] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: OK + */ router.post("/", async (req, res) => { let post = { userid: getUserId(req), @@ -39,6 +76,24 @@ DB.getDB.then((DB)=>{ }) }); + /** + * @swagger + * /songs/{id}: + * get: + * summary: Get a specific song by ID + * tags: [Songs] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/:id", async (req, res) => { let profileId = req.params.id; let profile = await DB.getProfile(profileId); @@ -53,6 +108,24 @@ DB.getDB.then((DB)=>{ return true; } + /** + * @swagger + * /songs/{id}: + * delete: + * summary: Delete a song + * tags: [Songs] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.delete("/:id", async (req, res) => { const userid = getUserId(req); const songId = req.params.id; @@ -66,6 +139,33 @@ DB.getDB.then((DB)=>{ }); }); + /** + * @swagger + * /songs/{id}: + * post: + * summary: Update a song + * tags: [Songs] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: string + * responses: + * 200: + * description: OK + */ router.post("/:id", async (req, res) => { const userid = getUserId(req); const songId = req.params.id; diff --git a/routes/subsplash.js b/routes/subsplash.js index 34d92aa..c1e2b2e 100644 --- a/routes/subsplash.js +++ b/routes/subsplash.js @@ -67,12 +67,51 @@ const getMedia = async (eventId) => { }; //getMedia('y42zyf3').then(console.log) +/** + * @swagger + * tags: + * name: Subsplash + * description: Subsplash API integration + */ + DB.getDB.then((DB) => { + /** + * @swagger + * /subsplash/events/{calendarId}: + * get: + * summary: Get events from a Subsplash calendar + * tags: [Subsplash] + * parameters: + * - in: path + * name: calendarId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/events/:calendarId", async (req, res) => { const events = await getEvents(req.params.calendarId) return res.json(events); }); + /** + * @swagger + * /subsplash/media/{seriesId}: + * get: + * summary: Get media from a Subsplash media series + * tags: [Subsplash] + * parameters: + * - in: path + * name: seriesId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: OK + */ router.get("/media/:seriesId", async (req, res) => { const events = await getMedia(req.params.seriesId) return res.json(events);