Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"pg-format": "^1.0.4",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"uuid": "^11.0.5"
},
"devDependencies": {
Expand Down
37 changes: 37 additions & 0 deletions src/config/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { SwaggerOptions } from 'swagger-jsdoc';

const swaggerDefinition = {
openapi: '3.0.0',
info: {
title: 'Backend as a Service API',
version: '1.0.0',
description: 'API Documentation for BaaS Project',
},
servers: [
{
url: 'http://localhost:3000/api',
description: 'Local server',
},
],
components: {
securitySchemes: {
BearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
ApiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'x-api-key',
},
},
},
};

const options: SwaggerOptions = {
swaggerDefinition,
apis: ['src/routes/*.ts'],
};

export default options;
18 changes: 2 additions & 16 deletions src/controllers/query-controller.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
import { Request, Response } from 'express';
import { sequelize } from '../config/database';
import { QueryTypes } from 'sequelize';
import { errorResponse, successResponse } from '../utils/response';
import { QueryExecutor } from '../operations/query';

export const executeQuery = async (req: Request, res: Response) => {
try {
const { query } = req.body;

if (!query || typeof query !== 'string') {
return errorResponse(res, 'Query is required and must be a string', 400);
}

const forbiddenPatterns = [
/DROP\s+TABLE/i,
/ALTER\s+/i,
/DELETE\s+FROM\s+[^\s]+(\s*;|$)/i,
];
if (forbiddenPatterns.some((pattern) => pattern.test(query))) {
return errorResponse(res, 'Query contains forbidden operations', 403);
}

const result = await sequelize.query(query, { type: QueryTypes.RAW });
const result = await QueryExecutor.execute(query);
return successResponse(res, result, 'Query executed successfully');
} catch (error) {
console.error(error);
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import swaggerJsDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import swaggerOptions from './config/swagger';
import userRoutes from './routes/user-routes';
import authRoutes from './routes/auth-routes';
import ddlRoutes from './routes/ddl-routes';
Expand Down Expand Up @@ -37,10 +40,14 @@ apiRouter.use('/apikey', apikeyRoutes);

app.use('/api', apiRouter);

const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));

app.get('/health', (req: Request, res: Response) => {
res.send('Hello, TypeScript with Express!');
});

app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
console.log(`Swagger Docs available at http://localhost:${PORT}/api-docs`);
});
22 changes: 22 additions & 0 deletions src/operations/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { sequelize } from '../config/database';
import { QueryTypes } from 'sequelize';

export class QueryExecutor {
static async execute(query: string) {
if (!query || typeof query !== 'string') {
throw new Error('Query is required and must be a string');
}

const forbiddenPatterns = [
/DROP\s+TABLE/i,
/ALTER\s+/i,
/DELETE\s+FROM\s+[^\s]+(\s*;|$)/i,
];
if (forbiddenPatterns.some((pattern) => pattern.test(query))) {
throw new Error('Query contains forbidden operations');
}

const result = await sequelize.query(query, { type: QueryTypes.RAW });
return result;
}
}
86 changes: 86 additions & 0 deletions src/routes/apikey-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,93 @@ import { getApiKey, regenerateApiKey } from '../controllers/apikey-controller';

const router = Router();

/**
* @swagger
* tags:
* name: API Key
* description: API for managing user API key
*/

/**
* @swagger
* /apikey:
* get:
* summary: Get API key
* tags: [API Key]
* security:
* - BearerAuth: []
* responses:
* 200:
* description: Successfully retrieved API key
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: User details retrieved
* apikey:
* type: string
* example: 'example-api-key'
* 401:
* description: Unauthorized (Invalid or missing token)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Unauthorized
*/
router.get('/', authMiddleware, getApiKey);

/**
* @swagger
* /apikey:
* put:
* summary: Regenerate API key
* tags: [API Key]
* security:
* - BearerAuth: []
* responses:
* 200:
* description: Successfully regenerated API key
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: User details retrieved
* apikey:
* type: string
* example: 'new-example-api-key'
* 401:
* description: Unauthorized (Invalid or missing token)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Unauthorized
*/
router.put('/', authMiddleware, regenerateApiKey);

export default router;
134 changes: 134 additions & 0 deletions src/routes/auth-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,141 @@ import { registerUser, loginUser } from '../controllers/user-controller';

const router = Router();

/**
* @swagger
* tags:
* name: Authentication
* description: API for user authentication
*/

/**
* @swagger
* /auth/register:
* post:
* summary: Register a new user
* tags: [Authentication]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* properties:
* name:
* type: string
* example: Full Name
* email:
* type: string
* format: email
* example: user@example.com
* password:
* type: string
* format: password
* example: password123
* responses:
* 200:
* description: User registered successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: User registered successfully
* data:
* type: object
* properties:
* id:
* type: string
* example: "123e4567-e89b-12d3-a456-426614174000"
* 400:
* description: Bad request (Missing fields or email already registered)
* content:
* application/json:
* examples:
* missing_fields:
* summary: Missing required fields
* value:
* success: false
* message: "Name, email, and password are required"
* email_registered:
* summary: Email already exists
* value:
* success: false
* message: "Email is already registered"
* 500:
* description: Internal server error
*/
router.post('/register', registerUser);

/**
* @swagger
* /auth/login:
* post:
* summary: Login a user
* tags: [Authentication]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* format: email
* example: user@example.com
* password:
* type: string
* format: password
* example: password123
* responses:
* 200:
* description: Login successful, returns a JWT token
* 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..."
* 401:
* description: Unauthorized
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Unauthorized
* 500:
* description: Internal server error
*/
router.post('/login', loginUser);

export default router;
Loading