From e86a35118d2b3fe1ecad9b0ab45d910ddc0a9e1d Mon Sep 17 00:00:00 2001 From: emkartal1 Date: Sat, 2 Dec 2023 13:54:30 +0100 Subject: [PATCH] Add swagger :books: --- src/Api/package.json | 6 +- src/Api/src/app.ts | 20 +- .../src/controllers/interfaces/IController.ts | 4 +- src/Api/src/controllers/spotifyController.ts | 71 ++++ src/Api/src/controllers/userController.ts | 384 +++++++++++++++++- src/Api/src/utils/swagger.ts | 34 ++ src/FLAD/redux/thunk/authThunk.tsx | 4 +- 7 files changed, 510 insertions(+), 13 deletions(-) create mode 100644 src/Api/src/utils/swagger.ts diff --git a/src/Api/package.json b/src/Api/package.json index 2a2cb9e..e744c55 100644 --- a/src/Api/package.json +++ b/src/Api/package.json @@ -17,6 +17,8 @@ "@types/cors": "^2.8.13", "@types/express": "^4.17.16", "@types/jsonwebtoken": "^9.0.1", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.6", "nodemon": "^2.0.20", "ts-node": "^10.9.1", "typescript": "^4.9.5" @@ -33,6 +35,8 @@ "express": "^4.18.2", "joi": "^17.8.1", "jsonwebtoken": "^9.0.0", - "mongoose": "^6.9.0" + "mongoose": "^6.9.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.0" } } diff --git a/src/Api/src/app.ts b/src/Api/src/app.ts index 0cc5b0a..90e16e8 100644 --- a/src/Api/src/app.ts +++ b/src/Api/src/app.ts @@ -2,22 +2,23 @@ import express, { Application } from 'express'; import cors from 'cors'; import cookieParser from 'cookie-parser'; import bodyParser from 'body-parser'; -import Controller from './controllers/interfaces/IController'; +import IController from './controllers/interfaces/IController'; import mongoose from 'mongoose'; +import swaggerUi from "swagger-ui-express"; +import { specs } from './utils/swagger'; class App { public express: Application; public port: number; - public dataBase: null; - public server: any; - constructor(controllers: Controller[], port: number) { + constructor(controllers: IController[], port: number) { this.express = express(); this.port = port; this.initDatabase(); this.initMiddleware(); this.initControllers(controllers); + this.initSwagger(); } private initMiddleware(): void { @@ -31,15 +32,15 @@ class App { })); } - private initControllers(controllers: Controller[]): void { - controllers.forEach((controller: Controller) => { + private initControllers(controllers: IController[]): void { + controllers.forEach((controller: IController) => { this.express.use('/api', controller.router); }); } public listen(): void { this.express.listen(this.port, () => { - console.log(`[server] : App listening on the port ${this.port}`); + console.log(`⚡️[server] : App listening on the port ${this.port}`); }); } @@ -49,6 +50,11 @@ class App { .then(() => console.log("Connect to MongoDB database successfully")) .catch(error => console.log("Error connecting : " + error)); } + + public initSwagger(): void { + this.express.use("/swagger", swaggerUi.serve, swaggerUi.setup(specs),); + console.log(`Docs available at /${this.port}/swagger`); + } } export default App; \ No newline at end of file diff --git a/src/Api/src/controllers/interfaces/IController.ts b/src/Api/src/controllers/interfaces/IController.ts index de84ab3..76dd956 100644 --- a/src/Api/src/controllers/interfaces/IController.ts +++ b/src/Api/src/controllers/interfaces/IController.ts @@ -1,8 +1,8 @@ import { Router } from 'express'; -interface Controller { +interface IController { path: string; router: Router; } -export default Controller; \ No newline at end of file +export default IController; \ No newline at end of file diff --git a/src/Api/src/controllers/spotifyController.ts b/src/Api/src/controllers/spotifyController.ts index ddf7447..57252f6 100644 --- a/src/Api/src/controllers/spotifyController.ts +++ b/src/Api/src/controllers/spotifyController.ts @@ -18,8 +18,79 @@ class SpotifyController implements IController { } initRoutes() { + /** + * @swagger + * /api/spotify/exchange: + * get: + * summary: Initiate the Spotify login flow + * description: Redirect the user to the Spotify login page for authorization + * tags: + * - Spotify + * parameters: + * - in: query + * name: redirectUrl + * schema: + * type: string + * description: The URL to redirect the user after Spotify authorization (optional) + * responses: + * 302: + * description: Redirecting to Spotify login page + * 400: + * description: Bad request - Cannot connect to Spotify + */ this.router.get(`${this.path}/exchange`, this.login); + + /** + * @swagger + * /api/spotify/callback: + * get: + * summary: Handle Spotify callback and exchange code for access token + * description: Handle Spotify callback and exchange the received code for an access token + * tags: + * - Spotify + * responses: + * 302: + * description: Redirecting with access token information + * 400: + * description: Bad request - Error connecting to Spotify + */ this.router.get(`${this.path}/callback`, this.getAccessToken); + + /** + * @swagger + * /api/spotify/refresh: + * get: + * summary: Refresh the Spotify access token using a refresh token + * description: Refresh the Spotify access token using a refresh token + * tags: + * - Spotify + * parameters: + * - in: query + * name: refresh_token + * schema: + * type: string + * required: true + * description: The refresh token obtained during the initial authorization + * responses: + * 200: + * description: Successfully refreshed access token + * content: + * application/json: + * schema: + * type: object + * properties: + * access_token: + * type: string + * description: The new access token + * refresh_token: + * type: string + * description: The new refresh token + * expires_in: + * type: number + * description: The time until the access token expires (in seconds) + * 400: + * description: Bad request - Cannot refresh the access token + */ this.router.get(`${this.path}/refresh`, this.getRefreshToken); } diff --git a/src/Api/src/controllers/userController.ts b/src/Api/src/controllers/userController.ts index 1b337a4..db9a107 100644 --- a/src/Api/src/controllers/userController.ts +++ b/src/Api/src/controllers/userController.ts @@ -24,28 +24,410 @@ class UserController implements IController { } private initRoutes(): void { + /** + * @swagger + * /api/auth/register: + * post: + * summary: Register a new user + * description: Register a new user with the provided details + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * default: john.doe@example.com + * password: + * type: string + * default: stringPassword123 + * name: + * type: string + * default: john_doe + * tokenSpotify: + * type: string + * responses: + * 201: + * description: User registered successfully + * 400: + * description: Bad request - Invalid input data + * 401: + * description: Unauthorized - Spotify token is invalid + * 409: + * description: Conflict - Email or username is already in use + * 500: + * description: Internal Server Error - Spotify account not authorized or not found + */ this.router.post( `${this.authPath}/register`, validationMiddleware(validator.register), this.register ); + + /** + * @swagger + * /api/auth/login: + * post: + * summary: Login a user + * description: Login with the provided email and password + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * default: john.doe@example.com + * password: + * type: string + * default: stringPassword123 + * responses: + * 200: + * description: User logged in successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * 400: + * description: Bad request - Invalid input data + */ this.router.post( `${this.authPath}/login`, validationMiddleware(validator.login), this.login ); + + /** + * @swagger + * /api/user: + * get: + * summary: Get user information + * description: Get information about the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: User logged in successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + */ this.router.get(`${this.path}`, authenticator, this.getUser); + + + /** + * @swagger + * /api/users: + * get: + * summary: Get information about multiple users + * description: Get information about multiple users based on provided user ids + * tags: + * - User + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: ids + * schema: + * type: string + * description: Comma-separated list of user ids + * responses: + * 200: + * description: Users information retrieved successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 400: + * description: Bad request - Invalid input data + */ this.router.get(`${this.path}s`, authenticator, this.getUsers); + + /** + * @swagger + * /api/user: + * delete: + * summary: Delete the authenticated user + * description: Delete the authenticated user and associated data + * tags: + * - User + * security: + * - bearerAuth: [] + * responses: + * 204: + * description: User deleted successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 404: + * description: User not found + */ this.router.delete(`${this.path}`, authenticator, this.deleteUser); + + /** + * @swagger + * /api/user/nextTo: + * get: + * summary: Get users near the authenticated user + * description: Get information about users near the authenticated user based on location + * tags: + * - User + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: longitude + * schema: + * type: number + * description: Longitude of the user's current location + * - in: query + * name: latitude + * schema: + * type: number + * description: Latitude of the user's current location + * - in: query + * name: currentMusic + * schema: + * type: string + * description: The ID of the currently playing music + * responses: + * 201: + * description: Users near the authenticated user retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 400: + * description: Bad request - Invalid input data + */ this.router.get(`${this.path}/nextTo`, authenticator, this.getUserNext); + + /** + * @swagger + * /api/user/musics/{id}: + * delete: + * summary: Delete a music from the authenticated user's liked list + * description: Delete a music from the authenticated user's liked list by music id + * tags: + * - User + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * description: The ID of the music to delete + * responses: + * 200: + * description: Music deleted successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 404: + * description: Music not found + */ this.router.delete(`${this.path}/musics/:id`, authenticator, this.deleteMusic); + + /** + * @swagger + * /api/user/musics: + * post: + * summary: Add a music to the authenticated user's liked list + * description: Add a music to the authenticated user's liked list + * tags: + * - User + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * musicId: + * type: string + * description: The ID of the music to add + * userId: + * type: string + * description: The ID of the user who liked the music + * responses: + * 201: + * description: Music added to liked list successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 400: + * description: Bad request - Invalid input data + */ this.router.post(`${this.path}/musics`, authenticator, this.addMusic); + + /** + * @swagger + * /api/user/musics: + * get: + * summary: Get the list of musics liked by the authenticated user + * description: Get the list of musics liked by the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: List of musics retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * musics: + * type: array + * 401: + * description: Unauthorized - Invalid or missing authentication token + */ this.router.get(`${this.path}/musics`, authenticator, this.getMusics); + + /** + * @swagger + * /api/user/name: + * put: + * summary: Update the name of the authenticated user + * description: Update the name of the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: The new name for the user + * responses: + * 200: + * description: User name updated successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 400: + * description: Bad request - Invalid input data + * 409: + * description: Conflict - The provided name is already in use by another user + */ this.router.put(`${this.path}/name`, authenticator, this.setName); + + /** + * @swagger + * /api/user/email: + * put: + * summary: Update the email of the authenticated user + * description: Update the email of the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * description: The new email for the user + * responses: + * 200: + * description: User email updated successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 400: + * description: Bad request - Invalid input data + * 409: + * description: Conflict - The provided email is already in use by another user + */ this.router.put(`${this.path}/email`, authenticator, this.setEmail); + + /** + * @swagger + * /api/user/image: + * put: + * summary: Update the profile image of the authenticated user + * description: Update the profile image of the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * image: + * type: string + * format: base64 + * description: The new profile image for the user (base64 encoded) + * responses: + * 200: + * description: User profile image updated successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 500: + * description: Internal Server Error - Unable to update the profile image + */ this.router.put(`${this.path}/image`, authenticator, this.setImage); - this.router.put(`${this.path}/password`, authenticator, this.setPassword); + /** + * @swagger + * /api/user/password: + * put: + * summary: Update the password of the authenticated user + * description: Update the password of the authenticated user + * tags: + * - User + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * oldPassword: + * type: string + * description: The current password of the user + * newPassword: + * type: string + * description: The new password for the user + * responses: + * 200: + * description: User password updated successfully + * 401: + * description: Unauthorized - Invalid or missing authentication token + * 500: + * description: Internal Server Error - Unable to update the password + */ + this.router.put(`${this.path}/password`, authenticator, this.setPassword); } private register = async ( diff --git a/src/Api/src/utils/swagger.ts b/src/Api/src/utils/swagger.ts new file mode 100644 index 0000000..e4f8a22 --- /dev/null +++ b/src/Api/src/utils/swagger.ts @@ -0,0 +1,34 @@ +import swaggerJsdoc from "swagger-jsdoc"; + +const options = { + definition: { + openapi: "3.0.1", + info: { + title: "FLAD API", + version: "1.0.0", + description: + "This is the Express API for the Flad project.", + contact: { + name: "Flad Dev", + url: "code", + email: "fladdevpro@gmail.com", + }, + }, + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + } + } + }, + security: [{ + bearerAuth: ["read"] + }] + }, + apis: ["./dist/**/*.js"], +}; + + +export const specs = swaggerJsdoc(options); \ No newline at end of file diff --git a/src/FLAD/redux/thunk/authThunk.tsx b/src/FLAD/redux/thunk/authThunk.tsx index e2e43f4..1f29bbc 100644 --- a/src/FLAD/redux/thunk/authThunk.tsx +++ b/src/FLAD/redux/thunk/authThunk.tsx @@ -7,7 +7,7 @@ import { MusicServiceProvider } from "../../models/MusicServiceProvider"; const keyRemember = 'rememberUser'; -export const register = (resgisterCredential: RegisterCredentials) => { +export const register = (registerCredential: RegisterCredentials) => { //@ts-ignore return async dispatch => { try { @@ -18,7 +18,7 @@ export const register = (resgisterCredential: RegisterCredentials) => { } const resp = await axios.post( configs.API_URL + '/auth/register', - resgisterCredential, + registerCredential, config ) const token = resp.data.token;