diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts new file mode 100644 index 00000000..43753c10 --- /dev/null +++ b/cypress/plugins/index.ts @@ -0,0 +1,14 @@ +import ms from 'smtp-tester'; + +module.exports = (on, config) => { + // starts the SMTP server at localhost:7777 + const port = 7777; + const mailServer = ms.init(port); + console.log('mail server at port %d', port); + + // process all emails + mailServer.bind((addr, id, email) => { + console.log('--- email ---'); + console.log(addr, id, email); + }); +}; diff --git a/package.json b/package.json index 110a5ae4..947d04d5 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "rxjs": "^7.8.1", + "smtp-tester": "^2.1.0", "start-server-and-test": "^2.0.10", "typeorm": "^0.3.20", "typeorm-extension": "^3.6.3", diff --git a/page/src/auth.service.ts b/page/src/auth.service.ts index 71928836..d84c2c7c 100644 --- a/page/src/auth.service.ts +++ b/page/src/auth.service.ts @@ -1,11 +1,13 @@ import axios from 'axios'; import { + funcs, IEmployeeHook, IEmployeeSignUp, IEnterpriseAssign, IEntityId, IEventInfo, IFacultyAssign, + IResponse, IUserAuthentication, IUserInfo, IUserRecieve, @@ -16,67 +18,100 @@ const API_URL = '/api/v1'; export const alert = reactive({ message: '', type: 'none' }), state = reactive({ user: null, token: null }); + interface AuthState { user: null | IUserInfo | string; token: null | IUserRecieve; } -export async function authRequest( +export async function action( type: 'login' | 'signup' | 'logout' | 'change-password' | 'request-signature', - user?: Required, -) { - const response = await axios.post(`${API_URL}/${type}`, user); - state.user = response.data.user; - saveTokens(response.data.session); - return response.data.user; -} - -export async function hookRequest(signature: string, password: string) { - const response = await axios.post(`${API_URL}/change-password/${signature}`, { - password, - }); - return response.data; -} - -export async function assignEvent(input: IEventInfo) { - const response = await axios.post(`${API_URL}/event/assign`, input); - return response.data; -} - -export async function updateEvent(input: IEventInfo & IEntityId) { - const response = await axios.post(`${API_URL}/event/update`, input); - return response.data; -} - -export async function assignEnterprise(input: IEnterpriseAssign) { - const response = await axios.post(`${API_URL}/enterprise/assign`, input); - return response.data; -} - -export async function assignFaculty(input: IFacultyAssign) { - const response = await axios.post(`${API_URL}/faculty/assign`, input); - return response.data; -} - -export async function assignEnterpriseUser(input: IEmployeeSignUp) { - const response = await axios.post(`${API_URL}/employee/signup`, input); - return response.data; -} - -export async function requestConsole() { - const response = await axios.post(`${API_URL}/console`); - return response.data; -} + input: Required, +): Promise; +export async function action( + type: 'EventUpdate', + input: IEventInfo & IEntityId, +): Promise; +export async function action( + type: 'EventAssign', + input: IEventInfo, +): Promise; +export async function action( + type: 'EnterpriseAssign', + input: IEnterpriseAssign, +): Promise; +export async function action( + type: 'FacultyAssign', + input: IFacultyAssign, +): Promise; +export async function action( + type: 'EmployeeSignUp', + input: IEmployeeSignUp, +): Promise; +export async function action( + type: 'EmployeeHook', + input: IEmployeeHook, +): Promise; +export async function action( + type: + | 'login' + | 'signup' + | 'logout' + | 'change-password' + | 'request-signature' + | 'EmployeeHook' + | 'EventAssign' + | 'EventUpdate' + | 'EnterpriseAssign' + | 'FacultyAssign' + | 'EmployeeSignUp', + input: + | IEmployeeHook + | IEmployeeSignUp + | IEventInfo + | (IEventInfo & IEntityId) + | IEnterpriseAssign + | IFacultyAssign + | Required, +): Promise { + let url = API_URL; + + switch (type) { + case 'EventAssign': + url += '/event/assign'; + break; + + case 'EventUpdate': + url += '/event/update'; + break; + + case 'EnterpriseAssign': + url += '/enterprise/assign'; + break; + + case 'FacultyAssign': + url += '/faculty/assign'; + break; + + case 'EmployeeSignUp': + url += '/employee/signup'; + break; + + case 'EmployeeHook': + url += '/employee/hook'; + break; + + default: + url += '/' + type; + break; + } -export async function requestFromEmployee(input: IEmployeeHook) { - const response = await axios.post(`${API_URL}/employee/hook`, input); - return response.data; -} + const { token } = (await axios.get(`${API_URL}/csrf-token`)).data, + { data } = await axios.post(url, input, { + headers: { 'csrf-token': token }, + }); -function saveTokens(input: IUserRecieve) { - state.token = input; - localStorage.setItem('acsTkn', state.token!.accessToken); - localStorage.setItem('rfsTkn', state.token!.refreshToken); + return data; } export type IObject = @@ -93,16 +128,13 @@ export interface IAlert { object?: IObject; } -export async function apiErrorHandler( - func: Promise, -) { +export async function apiErrorHandler(response: Promise) { alert.message = ''; alert.type = 'processing'; try { - const response = await func; - switch (response.message) { - case 'Sent_Signature_Email': + switch ((await response).message) { + case funcs.err('Success', 'Signature', 'Sent'): alert.message = 'An email has sent to your email address, please check inbox and spam'; alert.type = 'error'; diff --git a/page/src/router/index.ts b/page/src/router/index.ts index d45673ea..d85b291d 100644 --- a/page/src/router/index.ts +++ b/page/src/router/index.ts @@ -1,13 +1,12 @@ import { createRouter, createWebHistory } from 'vue-router'; import LoginView from '@/views/LoginView.vue'; import NotFoundView from '@/views/NotFoundView.vue'; -import HookView from '@/views/HookView.vue'; +import ChangePasswordView from '@/views/ChangePassword.vue'; import EnterpriseAssignView from '@/views/EnterpriseAssignView.vue'; import EmployeeSignUpView from '@/views/EmployeeSignUpView.vue'; import FacultyAssignView from '@/views/FacultyAssignView.vue'; import EventAssignView from '@/views/EventAssignView.vue'; import EventUpdateView from '@/views/EventUpdateView.vue'; -import AdminRequestSignatureView from '@/views/AdminRequestSignatureView.vue'; import FrontPageView from '@/views/FrontPageView.vue'; const router = createRouter({ @@ -16,11 +15,10 @@ const router = createRouter({ { path: '/login', component: LoginView }, { path: '/enterprise/assign', component: EnterpriseAssignView }, { path: '/employee/signup', component: EmployeeSignUpView }, - { path: '/hook/:signature', component: HookView }, + { path: '/change-password/:signature', component: ChangePasswordView }, { path: '/faculty/assign', component: FacultyAssignView }, { path: '/event/assign', component: EventAssignView }, { path: '/event/update', component: EventUpdateView }, - { path: '/request-signature', component: AdminRequestSignatureView }, { path: '/', component: FrontPageView }, { path: '/:pathMatch(.*)*', component: NotFoundView }, ], diff --git a/page/src/views/AdminRequestSignatureView.vue b/page/src/views/AdminRequestSignatureView.vue deleted file mode 100644 index b0dc0e16..00000000 --- a/page/src/views/AdminRequestSignatureView.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/page/src/views/HookView.vue b/page/src/views/ChangePassword.vue similarity index 72% rename from page/src/views/HookView.vue rename to page/src/views/ChangePassword.vue index 547d00bd..dedd8986 100644 --- a/page/src/views/HookView.vue +++ b/page/src/views/ChangePassword.vue @@ -1,7 +1,8 @@ + diff --git a/page/src/views/EmployeeSignUpView.vue b/page/src/views/EmployeeSignUpView.vue index 33d774f6..56e45b28 100644 --- a/page/src/views/EmployeeSignUpView.vue +++ b/page/src/views/EmployeeSignUpView.vue @@ -53,7 +53,7 @@ v-model="input.signature" :alert="alert" object="signature" - :sub-btn-click="request" + :sub-btn-click="signatureRequest" > Request signature @@ -61,12 +61,7 @@ diff --git a/page/src/views/EventAssignView.vue b/page/src/views/EventAssignView.vue index 2b9113fa..cb236249 100644 --- a/page/src/views/EventAssignView.vue +++ b/page/src/views/EventAssignView.vue @@ -57,7 +57,7 @@ diff --git a/page/src/views/FacultyAssignView.vue b/page/src/views/FacultyAssignView.vue index a8417492..09368ffb 100644 --- a/page/src/views/FacultyAssignView.vue +++ b/page/src/views/FacultyAssignView.vue @@ -34,28 +34,11 @@ object="password" v-model="input.password" /> - - Request signature from console - diff --git a/page/src/views/FrontPageView.vue b/page/src/views/FrontPageView.vue index 6c7b9b76..1ba47993 100644 --- a/page/src/views/FrontPageView.vue +++ b/page/src/views/FrontPageView.vue @@ -1,9 +1,3 @@ - + - + diff --git a/page/src/views/LoginView.vue b/page/src/views/LoginView.vue index af2d42bd..1cdced36 100644 --- a/page/src/views/LoginView.vue +++ b/page/src/views/LoginView.vue @@ -29,7 +29,7 @@ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9bf3457..72a360ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,6 +182,9 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.1 + smtp-tester: + specifier: ^2.1.0 + version: 2.1.0 start-server-and-test: specifier: ^2.0.10 version: 2.0.10 @@ -3748,6 +3751,9 @@ packages: '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/mailparser@3.4.5': + resolution: {integrity: sha512-EPERBp7fLeFZh7tS2X36MF7jawUx3Y6/0rXciZah3CTYgwLi3e0kpGUJ6FOmUabgzis/U1g+3/JzrVWbWIOGjg==} + '@types/markdown-it@14.1.2': resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} @@ -3828,6 +3834,9 @@ packages: '@types/sizzle@2.3.9': resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} + '@types/smtp-server@3.5.10': + resolution: {integrity: sha512-i3Jx7sJ2qF52vjaOf3HguulXlWRFf6BSfsRLsIdmytDyVGv7KkhSs+gR9BXJnJWg1Ljkh/56Fh1Xqwa6u6X7zw==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -4347,6 +4356,10 @@ packages: bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + base32.js@0.1.0: + resolution: {integrity: sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==} + engines: {node: '>=0.12.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -6417,6 +6430,9 @@ packages: resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} engines: {node: '>= 10'} + ipv6-normalize@1.0.1: + resolution: {integrity: sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA==} + is-absolute@1.0.0: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} @@ -7674,6 +7690,10 @@ packages: resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==} engines: {node: '>=6.0.0'} + nodemailer@6.9.15: + resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==} + engines: {node: '>=6.0.0'} + nodemailer@6.9.16: resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} engines: {node: '>=6.0.0'} @@ -9104,6 +9124,14 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + smtp-server@3.13.6: + resolution: {integrity: sha512-dqbSPKn3PCq3Gp5hxBM99u7PET7cQSAWrauhtArJbc+zrf5xNEOjm9+Ob3lySySrRoIEvNE0dz+w2H/xWFJNRw==} + engines: {node: '>=12.0.0'} + + smtp-tester@2.1.0: + resolution: {integrity: sha512-HfOBdHkdwoBO+Qb06H8ShVEc08nnvDbtYWzdT5iQMIeInBdLKu17XOhuaC79uM24zPApRfN3JAg627TIy3y/Ww==} + engines: {node: '>=14.0.0'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -14446,7 +14474,6 @@ snapshots: dependencies: domhandler: 5.0.3 selderee: 0.11.0 - optional: true '@semantic-release/changelog@6.0.3(semantic-release@24.2.2(typescript@5.7.3))': dependencies: @@ -15383,6 +15410,11 @@ snapshots: '@types/long@4.0.2': {} + '@types/mailparser@3.4.5': + dependencies: + '@types/node': 22.13.1 + iconv-lite: 0.6.3 + '@types/markdown-it@14.1.2': dependencies: '@types/linkify-it': 5.0.0 @@ -15469,6 +15501,11 @@ snapshots: '@types/sizzle@2.3.9': {} + '@types/smtp-server@3.5.10': + dependencies: + '@types/node': 22.13.1 + '@types/nodemailer': 6.4.17 + '@types/stack-utils@2.0.3': {} '@types/ua-parser-js@0.7.39': {} @@ -16220,6 +16257,8 @@ snapshots: bare-events@2.5.4: optional: true + base32.js@0.1.0: {} + base64-js@1.5.1: {} basic-auth@2.0.1: @@ -17296,8 +17335,7 @@ snapshots: encodeurl@2.0.0: {} - encoding-japanese@2.2.0: - optional: true + encoding-japanese@2.2.0: {} end-of-stream@1.4.4: dependencies: @@ -18506,8 +18544,7 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.2.0: - optional: true + he@1.2.0: {} header-case@2.0.4: dependencies: @@ -18564,7 +18601,6 @@ snapshots: dom-serializer: 2.0.0 htmlparser2: 8.0.2 selderee: 0.11.0 - optional: true htmlparser2@5.0.1: dependencies: @@ -18682,7 +18718,6 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.1.13: {} @@ -18801,6 +18836,8 @@ snapshots: ipaddr.js@2.2.0: {} + ipv6-normalize@1.0.1: {} + is-absolute@1.0.0: dependencies: is-relative: 1.0.0 @@ -19578,8 +19615,7 @@ snapshots: lazy-ass@1.6.0: {} - leac@0.6.0: - optional: true + leac@0.6.0: {} leven@3.1.0: {} @@ -19588,8 +19624,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libbase64@1.3.0: - optional: true + libbase64@1.3.0: {} libmime@5.3.6: dependencies: @@ -19597,12 +19632,10 @@ snapshots: iconv-lite: 0.6.3 libbase64: 1.3.0 libqp: 2.1.1 - optional: true libphonenumber-js@1.11.19: {} - libqp@2.1.1: - optional: true + libqp@2.1.1: {} light-my-request@5.14.0: dependencies: @@ -19820,14 +19853,12 @@ snapshots: nodemailer: 6.9.16 punycode.js: 2.3.1 tlds: 1.255.0 - optional: true mailsplit@5.4.2: dependencies: libbase64: 1.3.0 libmime: 5.3.6 libqp: 2.1.1 - optional: true make-dir@2.1.0: dependencies: @@ -20439,8 +20470,9 @@ snapshots: nodemailer@6.10.0: {} - nodemailer@6.9.16: - optional: true + nodemailer@6.9.15: {} + + nodemailer@6.9.16: {} nopt@8.1.0: dependencies: @@ -20747,7 +20779,6 @@ snapshots: dependencies: leac: 0.6.0 peberminta: 0.9.0 - optional: true parseurl@1.3.3: {} @@ -20824,8 +20855,7 @@ snapshots: pause@0.0.1: {} - peberminta@0.9.0: - optional: true + peberminta@0.9.0: {} peek-readable@5.4.2: optional: true @@ -21841,7 +21871,6 @@ snapshots: selderee@0.11.0: dependencies: parseley: 0.12.1 - optional: true semantic-release@24.2.2(typescript@5.7.3): dependencies: @@ -22095,6 +22124,20 @@ snapshots: smob@1.5.0: {} + smtp-server@3.13.6: + dependencies: + base32.js: 0.1.0 + ipv6-normalize: 1.0.1 + nodemailer: 6.9.15 + punycode.js: 2.3.1 + + smtp-tester@2.1.0: + dependencies: + '@types/mailparser': 3.4.5 + '@types/smtp-server': 3.5.10 + mailparser: 3.7.2 + smtp-server: 3.13.6 + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -22548,8 +22591,7 @@ snapshots: dependencies: tslib: 2.8.1 - tlds@1.255.0: - optional: true + tlds@1.255.0: {} tldts-core@6.1.77: {} diff --git a/src/app/app.middleware.ts b/src/app/app.middleware.ts index 2c46edb6..d60e67a1 100644 --- a/src/app/app.middleware.ts +++ b/src/app/app.middleware.ts @@ -31,10 +31,7 @@ export class AppMiddleware extends SecurityService { */ auth(req: FastifyRequest, res: FastifyReply, done: DoneFuncWithErrOrRes) { const isRefresh = this.rfsgrd.test(req.url), - accessKey = this.decrypt( - req.session.get('accessKey'), - req.ips?.join(';') || req.ip, - ); + accessKey = this.decrypt(req.session.get('accessKey'), req.ip); let access: string = '', refresh: string = ''; diff --git a/src/app/utils/controller.utils.ts b/src/app/utils/controller.utils.ts index 570927a5..700d8e07 100644 --- a/src/app/utils/controller.utils.ts +++ b/src/app/utils/controller.utils.ts @@ -43,7 +43,7 @@ export class BaseController { if (!user) throw new ServerException('Invalid', 'Email', ''); return this.svc.mail.send(email, 'Change password?', 'forgetPassword', { name: user.name, - url: `${hostname}/hook/${s}`, + url: `${hostname}/change-password/${s}`, }); }); diff --git a/src/app/utils/server.utils.ts b/src/app/utils/server.utils.ts index 5d70ce1b..b1f5f62f 100644 --- a/src/app/utils/server.utils.ts +++ b/src/app/utils/server.utils.ts @@ -24,6 +24,10 @@ import fastifyCompression from '@fastify/compress'; import { constants } from 'zlib'; import { IRefreshResult } from 'auth/guards'; import { JwtService } from '@nestjs/jwt'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import pc from 'picocolors'; +import { Colors } from 'picocolors/types'; +import { ErrorType, ErrorObject, ErrorAction } from './utils'; /** * Modified fastify interfaces @@ -206,3 +210,95 @@ export class InitServerClass implements OnModuleInit { ); } } + +declare global { + /** + * Server exception class + */ + class ServerException extends HttpException { + constructor( + type: ErrorType, + object: ErrorObject, + action: ErrorAction, + err?: any, + ); + } +} + +const color = (args: ColorLogOptions) => + (args.bg ? pc['bg' + args.bg.capitalize] : String)( + (args.font ? pc[args.font] : String)(args.msg), + ), + errorStatus = (error: any) => + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + +/** + * Server exception class + */ +class ServerException extends HttpException { + constructor( + type: ErrorType, + object: ErrorObject, + action: ErrorAction, + private err: ErrorExtender = new Error() as ErrorExtender, + ) { + super( + (6).string + '_' + type + '_' + object + (action ? '_' : '') + action, + +(err.statusCode || 400), + ); + } + + terminalLogging() { + const { cause, message, stack, statusCode } = this.err, + title = `${'-'.repeat(6)}${this.message}-${statusCode || 400}${'-'.repeat(6)}`; + + console.error( + color({ bg: 'red', msg: title }) + + '\n' + + color({ + font: 'yellow', + msg: `\tCause: ${cause || 'unknown'}\n\tMessage: ${message || 'N/A'}\n\tStack: ${stack}`, + }) + + '\n' + + color({ bg: 'red', msg: '-'.repeat(title.length) }), + ); + } +} + +/** + * Colored logging options + */ +interface ColorLogOptions { + msg: string; + bg?: keyof KeepBgKeys | ''; + font?: keyof Omit, 'isColorSupported'> | ''; +} + +/** + * Server error extender + */ +type ErrorExtender = Error & { statusCode: string | number }; + +/** + * Removing properties have prefix bg + */ +type RemoveBgKeys = { + // eslint-disable-next-line tsEslint/no-unused-vars + [K in keyof T as K extends `bg${infer _}` ? never : K]: T[K]; +}; + +/** + * Keep properties have prefix bg + */ +type KeepBgKeys = { + [K in keyof T as K extends `bg${infer Rest}` + ? Uncapitalize + : never]: T[K]; +}; + +/** + * Assign server functions + */ +Object.assign(global, { ServerException, color, errorStatus }); diff --git a/src/app/utils/utils.ts b/src/app/utils/utils.ts index 44edce03..9f7ac174 100644 --- a/src/app/utils/utils.ts +++ b/src/app/utils/utils.ts @@ -1,9 +1,3 @@ -/* eslint-disable tsEslint/no-unused-vars */ - -import { HttpException, HttpStatus } from '@nestjs/common'; -import pc from 'picocolors'; -import { Colors } from 'picocolors/types'; - /** * Casting object to interface */ @@ -271,18 +265,6 @@ declare global { */ function delay(ms: number): Promise; - /** - * Server exception - */ - class ServerException extends HttpException { - constructor( - type: ErrorType, - object: ErrorObject, - action: ErrorAction, - extend?: Error, - ); - } - /** * Error code generator * @param {ErrorType} type - type of error @@ -296,13 +278,6 @@ declare global { action: ErrorAction, ): string; - /** - * Console log with color - * @param {ColorLogOptions} args - functions arguments - * @return {string} - */ - function color(args: ColorLogOptions): string; - /** * Get http exception status code * @param {any} error - catched error @@ -319,12 +294,17 @@ declare global { /** * Server error type */ -type ErrorType = 'Invalid' | 'Success' | 'Fatal' | 'Forbidden' | 'Unauthorized'; +export type ErrorType = + | 'Invalid' + | 'Success' + | 'Fatal' + | 'Forbidden' + | 'Unauthorized'; /** * Server error object type */ -type ErrorObject = +export type ErrorObject = | 'ID' | 'Hash' | 'CsrfCookie' @@ -349,7 +329,7 @@ type ErrorObject = /** * Server error action type */ -type ErrorAction = +export type ErrorAction = | '' | 'Read' | 'Sent' @@ -361,88 +341,14 @@ type ErrorAction = | 'LogOut' | 'Access'; -/** - * Server error extender - */ -type ErrorExtender = Error & { statusCode: string | number }; - -/** - * Server exception class - */ -class ServerException extends HttpException { - constructor( - type: ErrorType, - object: ErrorObject, - action: ErrorAction, - private err: ErrorExtender = new Error() as ErrorExtender, - ) { - super( - (6).string + '_' + type + '_' + object + (action ? '_' : '') + action, - +(err.statusCode || 400), - ); - } - - terminalLogging() { - const { cause, message, stack, statusCode } = this.err, - title = `${'-'.repeat(6)}${this.message}-${statusCode || 400}${'-'.repeat(6)}`; - - console.error( - color({ bg: 'red', msg: title }) + - '\n' + - color({ - font: 'yellow', - msg: `\tCause: ${cause || 'unknown'}\n\tMessage: ${message || 'N/A'}\n\tStack: ${stack}`, - }) + - '\n' + - color({ bg: 'red', msg: '-'.repeat(title.length) }), - ); - } -} - -/** - * Removing properties have prefix bg - */ -type RemoveBgKeys = { - [K in keyof T as K extends `bg${infer _}` ? never : K]: T[K]; -}; - -/** - * Keep properties have prefix bg - */ -type KeepBgKeys = { - [K in keyof T as K extends `bg${infer Rest}` - ? Uncapitalize - : never]: T[K]; -}; - -/** - * Colored logging options - */ -interface ColorLogOptions { - msg: string; - bg?: keyof KeepBgKeys | ''; - font?: keyof Omit, 'isColorSupported'> | ''; -} - // Global functions -try { - (global as any).ServerException = ServerException; - global.color = (args: ColorLogOptions) => - (args.bg ? pc['bg' + args.bg.capitalize] : String)( - (args.font ? pc[args.font] : String)(args.msg), - ); - global.errorStatus = (error: any) => - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - global.currentTime = () => Math.floor(new Date().getTime() / 1000); - global.err = (type: ErrorType, object: ErrorObject, action: ErrorAction) => - type + '_' + object + (action ? '_' : '') + action; - global.disableDescribe = ( - _name: string, - _func: () => void | Promise, - ) => {}; - global.curFile = (file: string, cut = 2) => +export const funcs = { + currentTime: () => Math.floor(new Date().getTime() / 1000), + err: (type: ErrorType, object: ErrorObject, action: ErrorAction) => + type + '_' + object + (action ? '_' : '') + action, + // eslint-disable-next-line tsEslint/no-unused-vars + disableDescribe: (_name: string, _func: () => void | Promise) => {}, + curFile: (file: string, cut = 2) => file .split(/\/|\\/) .lastElement.split('.') @@ -450,18 +356,19 @@ try { .slice(0, cut) .join('') + '_' + - (5).string; - global.array = (length: number, initValue: any = '') => + (5).string, + array: (length: number, initValue: any = '') => Array(length) .join() .split(',') - .map(() => initValue); - global.delay = async (ms: number) => { + .map(() => initValue), + delay: async (ms: number) => { await new Promise((resolve) => setTimeout(() => resolve(), ms)).then( () => console.log('fired'), ); - }; -} catch {} + }, +}; +Object.assign(globalThis, funcs); // String.prototype Object.defineProperty(String.prototype, 'toBase64Url', { get: function () { diff --git a/src/build.ts b/src/build.ts index 042c4ca4..2cc6b623 100644 --- a/src/build.ts +++ b/src/build.ts @@ -59,13 +59,17 @@ for (const file of IKeysFiles) { IKeysOut.saveSync(); const modelsProject = new Project(), - modelsFiles = modelsProject.addSourceFilesAtPaths(['src/**/*model.ts']), + modelsFiles = modelsProject.addSourceFilesAtPaths([ + 'src/**/*.model.ts', + 'src/build/models.ts', + 'src/app/utils/utils.ts', + ]), modelsOut = modelsProject.createSourceFile('./src/build/types.ts', '', { overwrite: true, }); for (const file of modelsFiles) { modelsOut.addExportDeclaration({ - moduleSpecifier: `../${file.getFilePath().split('src')[1].slice(1, -3)}`, + moduleSpecifier: `../${file.getFilePath().split('src')[1].slice(1, -3)}.js`, }); } modelsOut.saveSync(); diff --git a/types/package.json b/types/package.json index 95154649..28d6587a 100644 --- a/types/package.json +++ b/types/package.json @@ -5,5 +5,6 @@ "license": "AGPL-3.0-only", "author": "Nguyễn Việt Anh ", "types": "index.d.ts", - "main": "build/types.js" + "main": "build/types.js", + "type": "module" }