first commit
This commit is contained in:
commit
605358e651
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
docs/
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage/
|
||||
|
||||
# production
|
||||
build/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Custom
|
||||
.vstags
|
||||
**.pem
|
||||
**.crt
|
||||
**.bak
|
||||
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 150,
|
||||
"proseWrap": "always",
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false
|
||||
}
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM node:lts-alpine
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install pm2 -g
|
||||
|
||||
CMD pm2 start process.yml && tail -f /dev/null
|
||||
|
||||
EXPOSE 3001
|
||||
13
eslint.config.js
Normal file
13
eslint.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
};
|
||||
5448
package-lock.json
generated
Normal file
5448
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "smash-or-pass-rest-api",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node build/server.js",
|
||||
"dev": "nodemon --watch 'src/**/*.ts' --exec npx ts-node src/server.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"prettify": "prettier --write src/**/*.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"joi": "^17.13.3",
|
||||
"mongodb": "^6.17.0",
|
||||
"mongoose": "^8.16.1",
|
||||
"multer": "^2.0.1",
|
||||
"npm": "^11.4.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^24.0.3",
|
||||
"eslint": "^9.29.0",
|
||||
"install": "^0.13.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "^3.6.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
4
process.yml
Normal file
4
process.yml
Normal file
@ -0,0 +1,4 @@
|
||||
apps:
|
||||
- script: build/server.js
|
||||
instances: 4
|
||||
exec_mode: cluster
|
||||
33
src/config/config.ts
Normal file
33
src/config/config.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import dotenv from 'dotenv';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const DEVELOPMENT = process.env.NODE_ENV === 'development';
|
||||
export const TEST = process.env.NODE_ENV === 'test';
|
||||
|
||||
export const MONGO_URL = process.env.MONGO_URL || 'localhost:27017';
|
||||
export const MONGO_USER = process.env.MONGO_USER || 'server';
|
||||
export const MONGO_PASSWORD = process.env.MONGO_PASSWORD || 'your_password';
|
||||
export const MONGO_DATABASE = process.env.MONGO_DATABASE || 'smash-or-pass';
|
||||
export const MONGO_OPTIONS: mongoose.ConnectOptions = {
|
||||
retryWrites: true,
|
||||
w: 'majority',
|
||||
};
|
||||
|
||||
export const SERVER_HOSTNAME = process.env.SERVER_HOSTNAME || 'localhost';
|
||||
export const SERVER_PORT = process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3000;
|
||||
|
||||
export const mongo = {
|
||||
MONGO_URL,
|
||||
MONGO_USER,
|
||||
MONGO_PASSWORD,
|
||||
MONGO_DATABASE,
|
||||
MONGO_OPTIONS,
|
||||
MONGO_CONNECTION: `mongodb://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_URL}/${MONGO_DATABASE}`,
|
||||
};
|
||||
|
||||
export const serverConfig = {
|
||||
SERVER_HOSTNAME,
|
||||
SERVER_PORT,
|
||||
};
|
||||
129
src/config/logging.ts
Normal file
129
src/config/logging.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { TEST } from './config';
|
||||
|
||||
const colours = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
underscore: '\x1b[4m',
|
||||
blink: '\x1b[5m',
|
||||
reverse: '\x1b[7m',
|
||||
hidden: '\x1b[8m',
|
||||
|
||||
fg: {
|
||||
black: '\x1b[30m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m',
|
||||
crimson: '\x1b[38m',
|
||||
},
|
||||
bg: {
|
||||
black: '\x1b[40m',
|
||||
red: '\x1b[41m',
|
||||
green: '\x1b[42m',
|
||||
yellow: '\x1b[43m',
|
||||
blue: '\x1b[44m',
|
||||
magenta: '\x1b[45m',
|
||||
cyan: '\x1b[46m',
|
||||
white: '\x1b[47m',
|
||||
crimson: '\x1b[48m',
|
||||
},
|
||||
};
|
||||
|
||||
export function getCallingFunction(error: Error) {
|
||||
try {
|
||||
const stack = error.stack;
|
||||
|
||||
if (stack === undefined) return '--';
|
||||
|
||||
const line = stack.split('\n')[2];
|
||||
const regex = /^.*at\s([a-zA-Z]+).*$/;
|
||||
const groups = line.match(regex);
|
||||
|
||||
if (groups === null) return '--';
|
||||
|
||||
if (groups.length < 2) return '--';
|
||||
|
||||
return groups[1];
|
||||
} catch {
|
||||
return '--';
|
||||
}
|
||||
}
|
||||
|
||||
export function log(message?: any, ...optionalParams: any[]) {
|
||||
if (!TEST) console.log(`[${new Date().toLocaleString()}]`, colours.fg.magenta, '[SERVER-LOG] ', colours.reset, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function info(message?: any, ...optionalParams: any[]) {
|
||||
if (!TEST)
|
||||
console.info(
|
||||
`[${new Date().toLocaleString()}]`,
|
||||
colours.fg.cyan,
|
||||
'[INFO]',
|
||||
colours.reset,
|
||||
colours.bg.green,
|
||||
`[${getCallingFunction(new Error())}]`,
|
||||
colours.reset,
|
||||
message,
|
||||
...optionalParams,
|
||||
);
|
||||
}
|
||||
|
||||
export function warn(message?: any, ...optionalParams: any[]) {
|
||||
if (!TEST)
|
||||
console.warn(
|
||||
`[${new Date().toLocaleString()}]`,
|
||||
colours.fg.yellow,
|
||||
'[WARN]',
|
||||
colours.reset,
|
||||
colours.bg.green,
|
||||
`[${getCallingFunction(new Error())}]`,
|
||||
colours.reset,
|
||||
message,
|
||||
...optionalParams,
|
||||
);
|
||||
}
|
||||
|
||||
export function error(message?: any, ...optionalParams: any[]) {
|
||||
if (!TEST)
|
||||
console.error(
|
||||
`[${new Date().toLocaleString()}]`,
|
||||
colours.fg.red,
|
||||
'[ERROR]',
|
||||
colours.reset,
|
||||
colours.bg.green,
|
||||
`[${getCallingFunction(new Error())}]`,
|
||||
colours.reset,
|
||||
message,
|
||||
...optionalParams,
|
||||
);
|
||||
}
|
||||
|
||||
const logging = {
|
||||
log,
|
||||
info,
|
||||
warn,
|
||||
error,
|
||||
warning: warn,
|
||||
getCallingFunction,
|
||||
};
|
||||
|
||||
/** Create the global definition */
|
||||
declare global {
|
||||
var logging: {
|
||||
log: (message?: any, ...optionalParams: any[]) => void;
|
||||
info: (message?: any, ...optionalParams: any[]) => void;
|
||||
warn: (message?: any, ...optionalParams: any[]) => void;
|
||||
warning: (message?: any, ...optionalParams: any[]) => void;
|
||||
error: (message?: any, ...optionalParams: any[]) => void;
|
||||
getCallingFunction: (error: Error) => string;
|
||||
};
|
||||
}
|
||||
|
||||
/** Link the local and global variable */
|
||||
globalThis.logging = logging;
|
||||
|
||||
export default logging;
|
||||
74
src/controllers/visualAssetController.ts
Normal file
74
src/controllers/visualAssetController.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import multer, { Multer } from 'multer';
|
||||
import { Route } from '../decorators/route';
|
||||
import { Controller } from '../decorators/controller';
|
||||
import { MongoGetAll } from '../decorators/mongoose/getAll';
|
||||
import { VisualAssetModel } from '../models/VisualAsset';
|
||||
import { MongoCreate } from '../decorators/mongoose/create';
|
||||
import { MongoGet } from '../decorators/mongoose/get';
|
||||
import { MongoCollectionGet } from '../decorators/mongoose/collectionGet';
|
||||
import { MongoQuery } from '../decorators/mongoose/query';
|
||||
import { MongoDeleteCollection } from '../decorators/mongoose/deleteCollection';
|
||||
|
||||
// MongoDB connection details
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
});
|
||||
|
||||
@Controller('/api/visualAssets')
|
||||
class VisualAssetController {
|
||||
@Route('get', '/')
|
||||
@MongoGetAll(VisualAssetModel)
|
||||
getAll(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(200).json(req.mongoGetAll);
|
||||
}
|
||||
|
||||
@Route('get', '/:id')
|
||||
@MongoGet(VisualAssetModel)
|
||||
get(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(200).json(req.mongoGet);
|
||||
}
|
||||
|
||||
@Route('get', '/collection/:collectionName')
|
||||
@MongoCollectionGet(VisualAssetModel)
|
||||
getByCollection(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(200).json(req.mongoCollectionGet);
|
||||
}
|
||||
|
||||
@Route('post', '/', upload.single('image'))
|
||||
@MongoCreate(VisualAssetModel)
|
||||
create(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(201).json(req.mongoCreate);
|
||||
}
|
||||
|
||||
@Route('post', '/query')
|
||||
@MongoQuery(VisualAssetModel)
|
||||
query(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(200).json(req.mongoQuery);
|
||||
}
|
||||
|
||||
@Route('delete', '/collection/:collectionName')
|
||||
@MongoDeleteCollection(VisualAssetModel)
|
||||
delete(req: Request, res: Response, next: NextFunction): void {
|
||||
res.status(204).json({});
|
||||
}
|
||||
}
|
||||
|
||||
export default VisualAssetController;
|
||||
|
||||
/* export const uploadVisualAssets = [
|
||||
upload.array('image'),
|
||||
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||
try {
|
||||
const { name, collection } = req.body;
|
||||
const files = req.files as Express.Multer.File[];
|
||||
|
||||
const names = Array.isArray(name) ? name : [name];
|
||||
const collections = Array.isArray(collection) ? collection : [collection];
|
||||
} catch (error) {
|
||||
console.error('Error uploading visual assets:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
] */
|
||||
5
src/decorators/controller.ts
Normal file
5
src/decorators/controller.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export function Controller(baseRoute: string = '') {
|
||||
return (target: any) => {
|
||||
Reflect.defineMetadata('baseRoute', baseRoute, target);
|
||||
};
|
||||
}
|
||||
33
src/decorators/mongoose/collectionGet.ts
Normal file
33
src/decorators/mongoose/collectionGet.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
export function MongoCollectionGet(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
logging.log(`Fetching collection: ${req.params.collectionName}`);
|
||||
const documents = await model.find({ collection: req.params.collectionName });
|
||||
const result = [];
|
||||
for (const document of documents) {
|
||||
const obj = document.toObject();
|
||||
if (document.image && document.image.data && document.image.contentType) {
|
||||
const base64 = document.image.data.toString('base64');
|
||||
obj.imageSrc = `data:${document.image.contentType};base64,${base64}`;
|
||||
}
|
||||
delete obj.image;
|
||||
result.push(obj);
|
||||
}
|
||||
req.mongoCollectionGet = result;
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
36
src/decorators/mongoose/create.ts
Normal file
36
src/decorators/mongoose/create.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import mongoose, { Model } from 'mongoose';
|
||||
|
||||
export function MongoCreate(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
let document = new model({
|
||||
_id: new mongoose.Types.ObjectId(),
|
||||
...req.body,
|
||||
});
|
||||
|
||||
if (req.file) {
|
||||
document.image = {
|
||||
data: req.file.buffer,
|
||||
contentType: req.file.mimetype,
|
||||
};
|
||||
}
|
||||
|
||||
await document.save();
|
||||
|
||||
req.mongoCreate = document;
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
24
src/decorators/mongoose/deleteCollection.ts
Normal file
24
src/decorators/mongoose/deleteCollection.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
export function MongoDeleteCollection(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const document = await model.findOneAndDelete({ collection: req.params.collectionName });
|
||||
if (!document) {
|
||||
return res.status(404).json({ message: 'Document not found' });
|
||||
}
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
36
src/decorators/mongoose/get.ts
Normal file
36
src/decorators/mongoose/get.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
export function MongoGet(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const document = await model.findById(req.params.id);
|
||||
if (document) {
|
||||
if (document.image && document.image.data && document.image.contentType) {
|
||||
const base64 = document.image.data.toString('base64');
|
||||
const obj = document.toObject();
|
||||
delete obj.image;
|
||||
req.mongoGet = {
|
||||
...obj,
|
||||
imageSrc: `data:${document.image.contentType};base64,${base64}`,
|
||||
};
|
||||
} else {
|
||||
req.mongoGet = document;
|
||||
}
|
||||
} else {
|
||||
return res.status(404).json({ message: 'Document not found' });
|
||||
}
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
32
src/decorators/mongoose/getAll.ts
Normal file
32
src/decorators/mongoose/getAll.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
export function MongoGetAll(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
let documents = await model.find();
|
||||
const result = [];
|
||||
for (const document of documents) {
|
||||
const obj = document.toObject();
|
||||
if (document.image && document.image.data && document.image.contentType) {
|
||||
const base64 = document.image.data.toString('base64');
|
||||
obj.imageSrc = `data:${document.image.contentType};base64,${base64}`;
|
||||
}
|
||||
delete obj.image;
|
||||
result.push(obj);
|
||||
}
|
||||
req.mongoGetAll = result;
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
22
src/decorators/mongoose/query.ts
Normal file
22
src/decorators/mongoose/query.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
export function MongoQuery(model: Model<any>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const documents = await model.find({ ...req.body });
|
||||
req.mongoQuery = documents;
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
17
src/decorators/route.ts
Normal file
17
src/decorators/route.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Express, RequestHandler } from 'express';
|
||||
import { RouteHandler } from '../library/routes';
|
||||
|
||||
export function Route(method: keyof Express, path: string = '', ...middleware: RequestHandler[]) {
|
||||
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
||||
const routePath = path;
|
||||
const routeHandlers: RouteHandler = Reflect.getMetadata('routeHandlers', target) || new Map();
|
||||
|
||||
if (!routeHandlers.has(method)) {
|
||||
routeHandlers.set(method, new Map());
|
||||
}
|
||||
|
||||
routeHandlers.get(method)?.set(routePath, [...middleware, descriptor.value]);
|
||||
|
||||
Reflect.defineMetadata('routeHandlers', routeHandlers, target);
|
||||
};
|
||||
}
|
||||
22
src/decorators/validate.ts
Normal file
22
src/decorators/validate.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import Joi from 'joi';
|
||||
|
||||
export function Validate<T = any>(schema: Joi.ObjectSchema<T>) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
await schema.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
logging.error(error);
|
||||
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
return originalMethod.call(this, req, res, next);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
3
src/library/routes.ts
Normal file
3
src/library/routes.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Express, RequestHandler } from 'express';
|
||||
|
||||
export type RouteHandler = Map<keyof Express, Map<string, RequestHandler[]>>;
|
||||
18
src/middlewares/corsHandler.ts
Normal file
18
src/middlewares/corsHandler.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export function corsHandler(req: Request, res: Response, next: NextFunction): any {
|
||||
res.header('Access-Control-Allow-Origin', req.header('origin'));
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||
res.header('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
|
||||
return res.status(200).json({});
|
||||
}
|
||||
|
||||
if (req.path.includes('/favicon.ico')) {
|
||||
return res.status(204).end();
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
26
src/middlewares/declareHandler.ts
Normal file
26
src/middlewares/declareHandler.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
mongoGet: Document | undefined;
|
||||
mongoCollectionGet: Document[];
|
||||
mongoGetAll: Document[];
|
||||
mongoCreate: Document | undefined;
|
||||
mongoUpdate: Document | undefined;
|
||||
mongoQuery: Document[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function declareHandler(req: Request, res: Response, next: NextFunction) {
|
||||
req.mongoCreate = undefined;
|
||||
req.mongoGet = undefined;
|
||||
req.mongoGetAll = [];
|
||||
req.mongoUpdate = undefined;
|
||||
req.mongoQuery = [];
|
||||
req.mongoCollectionGet = [];
|
||||
|
||||
next();
|
||||
}
|
||||
12
src/middlewares/errorHandler.ts
Normal file
12
src/middlewares/errorHandler.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export interface AppError extends Error {
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export const errorHandler = (err: AppError, req: Request, res: Response, next: NextFunction) => {
|
||||
logging.error(err);
|
||||
res.status(err.status || 500).json({
|
||||
message: err.message || 'Internal Server Error',
|
||||
});
|
||||
};
|
||||
11
src/middlewares/loggingHandler.ts
Normal file
11
src/middlewares/loggingHandler.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export function loggingHandler(req: Request, res: Response, next: NextFunction) {
|
||||
logging.info(`Incomming - METHOD: [${req.method}] - URL: [${req.url}] - IP: [${req.socket.remoteAddress}]`);
|
||||
|
||||
res.on('finish', () => {
|
||||
logging.info(`Result - METHOD: [${req.method}] - URL: [${req.url}] - IP: [${req.socket.remoteAddress}] - STATUS: [${res.statusCode}]`);
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
12
src/middlewares/routeNotFound.ts
Normal file
12
src/middlewares/routeNotFound.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export function routeNotFound(req: Request, res: Response, next: NextFunction) {
|
||||
const error = new Error('Not found');
|
||||
logging.warning(error);
|
||||
|
||||
res.status(404).json({
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
13
src/models/VisualAsset.ts
Normal file
13
src/models/VisualAsset.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import mongoose, { Schema } from 'mongoose';
|
||||
export const VisualAsset = new Schema(
|
||||
{
|
||||
collection: { type: String, required: true },
|
||||
name: { type: String, required: true },
|
||||
image: { data: Buffer, contentType: String },
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const VisualAssetModel = mongoose.model('VisualAsset', VisualAsset);
|
||||
31
src/modules/routes.ts
Normal file
31
src/modules/routes.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Express, RequestHandler } from 'express';
|
||||
|
||||
export function defineRoutes(controllers: any, app: Express) {
|
||||
for (let i = 0; i < controllers.length; i++) {
|
||||
const controller = new controllers[i]();
|
||||
const routeHandlers: Map<keyof Express, Map<string, RequestHandler[]>> = Reflect.getMetadata('routeHandlers', controller);
|
||||
const controllerPath: String = Reflect.getMetadata('baseRoute', controller.constructor);
|
||||
const methods = Array.from(routeHandlers.keys());
|
||||
|
||||
logging.log('--------------------------------------------------');
|
||||
|
||||
for (let j = 0; j < methods.length; j++) {
|
||||
const method = methods[j];
|
||||
const routes = routeHandlers.get(method as keyof Express);
|
||||
|
||||
if (routes) {
|
||||
const routeNames = Array.from(routes.keys());
|
||||
for (let k = 0; k < routeNames.length; k++) {
|
||||
const handlers = routes.get(routeNames[k]);
|
||||
|
||||
if (handlers) {
|
||||
app[method as keyof Express](controllerPath + routeNames[k], handlers);
|
||||
logging.log('Loading route:', method, controllerPath + routeNames[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logging.log('--------------------------------------------------');
|
||||
}
|
||||
}
|
||||
68
src/server.ts
Normal file
68
src/server.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import http from 'http';
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import './config/logging';
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { mongo, serverConfig } from './config/config';
|
||||
import { declareHandler } from './middlewares/declareHandler';
|
||||
import { loggingHandler } from './middlewares/loggingHandler';
|
||||
import { corsHandler } from './middlewares/corsHandler';
|
||||
import { errorHandler } from './middlewares/errorHandler';
|
||||
import VisualAssetController from './controllers/visualAssetController';
|
||||
import { defineRoutes } from './modules/routes';
|
||||
import { routeNotFound } from './middlewares/routeNotFound';
|
||||
|
||||
export const app = express();
|
||||
export let httpServer: ReturnType<typeof http.createServer>;
|
||||
|
||||
export const Main = async () => {
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Initializing API');
|
||||
logging.log('----------------------------------------');
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
try {
|
||||
const conn = await mongoose.connect(mongo.MONGO_CONNECTION, mongo.MONGO_OPTIONS);
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Database Connected Successfully. Version:', conn.version);
|
||||
logging.log('----------------------------------------');
|
||||
} catch (error) {
|
||||
logging.log('----------------------------------------');
|
||||
logging.error('Database Connection Failed:\n', error);
|
||||
logging.log('----------------------------------------');
|
||||
}
|
||||
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Logging & Configuration');
|
||||
logging.log('----------------------------------------');
|
||||
app.use(declareHandler);
|
||||
app.use(loggingHandler);
|
||||
app.use(corsHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Define Controller Routing');
|
||||
logging.log('----------------------------------------');
|
||||
defineRoutes([VisualAssetController], app);
|
||||
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Define Routing Error');
|
||||
logging.log('----------------------------------------');
|
||||
app.use(routeNotFound);
|
||||
|
||||
logging.log('----------------------------------------');
|
||||
logging.log('Starting Server');
|
||||
logging.log('----------------------------------------');
|
||||
httpServer = http.createServer(app);
|
||||
httpServer.listen(serverConfig.SERVER_PORT, () => {
|
||||
logging.log('----------------------------------------');
|
||||
logging.log(`Server started on ${serverConfig.SERVER_HOSTNAME}:${serverConfig.SERVER_PORT}`);
|
||||
logging.log('----------------------------------------');
|
||||
});
|
||||
};
|
||||
|
||||
export const Shutdown = (callback: any) => httpServer && httpServer.close(callback);
|
||||
|
||||
Main();
|
||||
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["test"]
|
||||
}
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "build",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["reflect-metadata"],
|
||||
"moduleResolution": "Node16",
|
||||
"module": "Node16"
|
||||
},
|
||||
"exclude": ["node_modules/"],
|
||||
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user