diff --git a/app/main.ts b/app/main.ts index a6d359d..5c106d2 100644 --- a/app/main.ts +++ b/app/main.ts @@ -20,9 +20,7 @@ import { baseSettings } from "./src/internal/system/controller/settings/baseSett import { addListeners, manualFixes, - settingsMigrate, } from "./src/internal/system/utlis/addListeners.js"; -import { swaggerOptions } from "./src/pkg/apiDocs/swaggerOptions.js"; import { auth } from "./src/pkg/auth/auth.js"; import { db } from "./src/pkg/db/db.js"; import { settings } from "./src/pkg/db/schema/settings.js"; @@ -35,6 +33,9 @@ import { sendNotify } from "./src/pkg/utils/notify.js"; import { returnFunc } from "./src/pkg/utils/return.js"; import { tryCatch } from "./src/pkg/utils/tryCatch.js"; import { setupIoServer } from "./src/ws/server.js"; +import { swaggerConfig, swaggerUiOptions } from "./src/internal/swagger/config.js"; +import { setupSwagger } from "./src/internal/swagger/swagger.js"; + const main = async () => { const env = validateEnv(process.env); @@ -177,13 +178,14 @@ const main = async () => { ); // docs and routes - const openapiSpec: any = swaggerJsdoc(swaggerOptions); - app.use( - basePath + "/api/docs", - swaggerUi.serve, - swaggerUi.setup(openapiSpec), - ); - + // const openapiSpec: any = swaggerJsdoc(swaggerConfig); + // app.use( + // basePath + "/api/docs", + // swaggerUi.serve, + // swaggerUi.setup(openapiSpec, swaggerUiOptions), + // ); + + setupSwagger(app, basePath) app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build"))); app.use( basePath + "/app", diff --git a/app/src/internal/auth/routes/login.ts b/app/src/internal/auth/routes/login.ts index cb6264f..a8beb09 100644 --- a/app/src/internal/auth/routes/login.ts +++ b/app/src/internal/auth/routes/login.ts @@ -24,6 +24,13 @@ router.post("/", async (req: Request, res: Response) => { .from(user) .where(eq(user.username, validated.username)); + if(userLogin.length === 0 ){ + return res.status(200).json({ + success: false, + message: `It appears you do not have a user yet please head over to the register page and create a user then try again.`, + + }); + } if ( !userLogin[0].lastLogin || differenceInDays(userLogin[0].lastLogin, new Date(Date.now())) > 120 diff --git a/app/src/internal/swagger/config.ts b/app/src/internal/swagger/config.ts new file mode 100644 index 0000000..99709c4 --- /dev/null +++ b/app/src/internal/swagger/config.ts @@ -0,0 +1,60 @@ +export const swaggerUiOptions = { + explorer: true, + customCss: '.swagger-ui .topbar { display: none }', + customSiteTitle: 'Your API Documentation', + swaggerOptions: { + persistAuthorization: true, + displayRequestDuration: true, + filter: true, + syntaxHighlight: { + activate: true, + theme: 'monokai' + } + } +}; + + +export const swaggerConfig = { + definition: { + openapi: '3.0.0', + info: { + title: 'Logistics Support Tool', + version: '1.8.0', + description: 'Complete API documentation for lst', + contact: { + name: 'API Support', + email: 'blake.matthes@alpla.com' + } + }, + servers: [ + { + url: 'http://localhost:4200', + description: 'Development server' + }, + { + url: 'https://api.yourapp.com', + description: 'Production server' + } + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT' + }, + apiKey: { + type: 'apiKey', + in: 'header', + name: 'X-API-Key' + } + } + }, + security: [ + { + bearerAuth: [] + } + ] + }, + apis: [] // We'll populate this dynamically +}; diff --git a/app/src/internal/swagger/endpoints/auth/login.ts b/app/src/internal/swagger/endpoints/auth/login.ts new file mode 100644 index 0000000..b49df1b --- /dev/null +++ b/app/src/internal/swagger/endpoints/auth/login.ts @@ -0,0 +1,129 @@ +const loginEndpoint = { + '/lst/api/user/login': { + post: { + tags: ['Authentication'], + summary: 'Login to get a token', + description: 'User enters username and password, gets back a JWT token and session data', + + // What the user sends you + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['username', 'password'], + properties: { + username: { + type: 'string', + example: 'smith01' + }, + password: { + type: 'string', + example: 'MyPassword123' + } + } + } + } + } + }, + + // What you send back to the user + responses: { + // SUCCESS - Login worked + 200: { + description: 'Login successful', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: true + }, + message: { + type: 'string', + example: 'Login successful' + }, + data: { + type: 'object', + properties: { + token: { + type: 'string', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' + }, + user: { + type: 'object', + properties: { + id: { + type: 'string', + example: '12345' + }, + email: { + type: 'string', + example: 'user@example.com' + }, + username: { + type: 'string', + example: 'johndoe' + } + } + } + } + } + } + } + } + } + }, + + // ERROR - Wrong password or email + 401: { + description: 'Wrong email or password', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + message: { + type: 'string', + example: 'Invalid credentials' + } + } + } + } + } + }, + + // ERROR - Missing fields + 400: { + description: 'Missing email or password', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + message: { + type: 'string', + example: 'Email and password are required' + } + } + } + } + } + } + } + } + } +}; + +export default loginEndpoint; \ No newline at end of file diff --git a/app/src/internal/swagger/schemas/auth.schema.ts b/app/src/internal/swagger/schemas/auth.schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/internal/swagger/schemas/common.schema.ts b/app/src/internal/swagger/schemas/common.schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/internal/swagger/swagger.ts b/app/src/internal/swagger/swagger.ts new file mode 100644 index 0000000..3528b21 --- /dev/null +++ b/app/src/internal/swagger/swagger.ts @@ -0,0 +1,31 @@ +import swaggerJsdoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +import { swaggerConfig, swaggerUiOptions } from './config.js'; +import { type Express } from 'express'; + +import loginEndpoint from './endpoints/auth/login.js'; + +const allPaths = { + ...loginEndpoint, + // When you add more endpoints, add them here: + // ...registerEndpoint, + // ...logoutEndpoint, +}; + + +const swaggerSpec = { + ...swaggerConfig.definition, + paths: allPaths +}; + +const specs = swaggerJsdoc({ + ...swaggerConfig, + definition: swaggerSpec +}); + +export function setupSwagger(app: Express, basePath: string): void { + // Swagger UI at /api-docs + app.use(basePath + "/api/docs", swaggerUi.serve, swaggerUi.setup(specs, swaggerUiOptions)); + + //console.log('📚 Swagger docs at http://localhost:3000/api-docs'); +} \ No newline at end of file diff --git a/app/src/pkg/apiDocs/swaggerOptions.ts b/app/src/pkg/apiDocs/swaggerOptions.ts deleted file mode 100644 index c205c37..0000000 --- a/app/src/pkg/apiDocs/swaggerOptions.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const swaggerOptions = { - definition: { - openapi: "3.0.0", - info: { - title: "Logistics Support Tool", - version: "1.0.0", - }, - }, - // globs where swagger-jsdoc should look for annotations: - apis: ["../../src/**/*.ts"], -};