init commit
This commit is contained in:
commit
d8446080cc
18
.eslintrc.js
Normal file
18
.eslintrc.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
},
|
||||
rules: {
|
||||
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
||||
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any":"off",
|
||||
"@typescript-eslint/interface-name-prefix":"off"
|
||||
},
|
||||
};
|
||||
5
.estlintignore
Normal file
5
.estlintignore
Normal file
@ -0,0 +1,5 @@
|
||||
.webpack/*
|
||||
|
||||
schema.d.ts
|
||||
validation.ts
|
||||
redoc-static.html
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
|
||||
.build
|
||||
.webpack
|
||||
|
||||
*/**/.env
|
||||
*/**/postman.json
|
||||
*/**/schema.d.ts
|
||||
*/**/*.d.ts
|
||||
|
||||
src/config/.env.json
|
||||
|
||||
.serverless/
|
||||
|
||||
config/.env.json
|
||||
|
||||
handler.ts
|
||||
.slslogs.txt
|
||||
.vscode
|
||||
8
.prettierrc.js
Normal file
8
.prettierrc.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 150,
|
||||
tabWidth: 4,
|
||||
arrowParens: "always"
|
||||
};
|
||||
23
__tests/accounts.playground.gql
Normal file
23
__tests/accounts.playground.gql
Normal file
@ -0,0 +1,23 @@
|
||||
mutation getCode {
|
||||
signInEmailCodeRequest(input: { email: "tom@makeit-studio.com" }) {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
mutation signInWithCode {
|
||||
signInEmailCodeConfirm(input: { email: "tom@makeit-studio.com", code: 731560 }) {
|
||||
idToken
|
||||
}
|
||||
}
|
||||
|
||||
query me {
|
||||
me {
|
||||
_id
|
||||
}
|
||||
}
|
||||
|
||||
mutation completeRegistration {
|
||||
completeRegistration(input: { firstName: "Tom", lastName: "Roelants", organisation: "Makeit", position: "Dev" }) {
|
||||
_id
|
||||
}
|
||||
}
|
||||
36
__tests/admin/config.ts
Normal file
36
__tests/admin/config.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Thunder } from './helpers/graphql-zeus';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const url = 'http://localhost:4001';
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6IjVmOTcxMmEwODczMTcyMGQ2NmZkNGEyYTU5MmU0ZGZjMmI1ZGU1OTUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vc29mdHdhcmUtdGVhbS1zdGFnaW5nIiwiYXVkIjoic29mdHdhcmUtdGVhbS1zdGFnaW5nIiwiYXV0aF90aW1lIjoxNjEwNzE1ODA4LCJ1c2VyX2lkIjoiTDNpQ3RkZDRzb2djYVJ1Y1RXbVVJNjM0aWo1MyIsInN1YiI6IkwzaUN0ZGQ0c29nY2FSdWNUV21VSTYzNGlqNTMiLCJpYXQiOjE2MTA3MTU4MDgsImV4cCI6MTYxMDcxOTQwOCwiZW1haWwiOiJzc3llZC5tYWtlaXRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsic3N5ZWQubWFrZWl0QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6ImN1c3RvbSJ9fQ.o3HXVp5lrIk_bzaTqb-aA7bpRJLb-Jp7lPz7_O7PQdsOaw5-L066HFtp5I-meS2CdpvUXy1REvAntGEeTNFukBzOkcRjBYagaxwUd_KpZOOoru5wpvTW8jJXlzmQXmDbHm2zfmzSCxsmrdZz62p_mKKJgGw5QTZB_LxUqTo6v0rhboXOowTzvAaHf1iwSU84JfrZwKU4xd0031RZPFbi6NDgHprYIDx7L5Fsl6bQhXDEXELDe4RNYQ5b0CKXa8rdJE9rBuroGJAJhB42_iwDwCa07FwBLIJzXefKqI97s15s23RzVT5o81EtIDOlOI6nBhucsad4boxacT6VBuS_vw';
|
||||
|
||||
export const thunder = Thunder(async (query) => {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (token) headers.authorization = token;
|
||||
|
||||
const response = await fetch(url, {
|
||||
body: JSON.stringify({ query }),
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
});
|
||||
if (!response.ok) {
|
||||
return new Promise((resolve, reject) => {
|
||||
response
|
||||
.text()
|
||||
.then((text) => {
|
||||
try {
|
||||
reject(JSON.parse(text));
|
||||
} catch (err) {
|
||||
reject(text);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
const json = await response.json();
|
||||
return json.data;
|
||||
});
|
||||
4164
__tests/admin/helpers/graphql-zeus.ts
Normal file
4164
__tests/admin/helpers/graphql-zeus.ts
Normal file
File diff suppressed because it is too large
Load Diff
0
__tests/admin/index.ts
Normal file
0
__tests/admin/index.ts
Normal file
7
__tests/app/config.ts
Normal file
7
__tests/app/config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Thunder } from './helpers/graphql-zeus';
|
||||
import { testRunQuery } from '../../lib/seed/__tests/helper';
|
||||
|
||||
const url = 'http://localhost:4000';
|
||||
const token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjRlMDBlOGZlNWYyYzg4Y2YwYzcwNDRmMzA3ZjdlNzM5Nzg4ZTRmMWUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vd29ya2luZ2ZsZXgtcHJvZHVjdGlvbiIsImF1ZCI6IndvcmtpbmdmbGV4LXByb2R1Y3Rpb24iLCJhdXRoX3RpbWUiOjE2MTYxNzE2MTcsInVzZXJfaWQiOiI4TW1EdmZHbWV0Ylc5aVE5S0Iybm55TFRsV3gyIiwic3ViIjoiOE1tRHZmR21ldGJXOWlROUtCMm5ueUxUbFd4MiIsImlhdCI6MTYxNjE3MTYxNywiZXhwIjoxNjE2MTc1MjE3LCJlbWFpbCI6Im5pY29sYXMuYS5iZXJuaWVyK3dvcmtAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsibmljb2xhcy5hLmJlcm5pZXIrd29ya0BnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJjdXN0b20ifX0.JQvWIyuMmh2tWtf6I1ACWadhyC7LbwvBdaqE0wtXy5YX2dj_cdrKyV1H35nmJ4xRXAoTfMIdarnfPqZaCcCDrAMVey4Jt2BU9dQ0COoQ-qz5KGzeFbeUU-MDSx3sP7sEm_qsdcgPvBHPAj2WnBjrUh58Je1JRVORcXbn9oopJ0v-U2sSn1H43Ycv5qDGF9eqPHaz3ex0NmVum77JRPIKGOKPopCDKrJgMG1tlUiSsr2Qo0YgmxuzY5SrIvVImXmyclNMNrEiRsemTACBScX9seDl_vKosbgIPFaTFqtJksI8NeKxBQFCKz78DwZpZPHxwlN7IEP-ilzIbbppFoHFIQ'
|
||||
|
||||
export const thunder = Thunder(testRunQuery(url, token));
|
||||
6824
__tests/app/helpers/graphql-zeus.ts
Normal file
6824
__tests/app/helpers/graphql-zeus.ts
Normal file
File diff suppressed because it is too large
Load Diff
3093
__tests/app/helpers/zeus/const.ts
Normal file
3093
__tests/app/helpers/zeus/const.ts
Normal file
File diff suppressed because it is too large
Load Diff
3420
__tests/app/helpers/zeus/index.ts
Normal file
3420
__tests/app/helpers/zeus/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
16
__tests/app/index.ts
Normal file
16
__tests/app/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { finaliseCheckoutWithStripe, reserveOneBooking } from './src/bookings';
|
||||
import { getOneWorkspace, searchWorkspace } from './src/workspace';
|
||||
|
||||
/*
|
||||
Workspace
|
||||
*/
|
||||
|
||||
// searchWorkspace();
|
||||
// getOneWorkspace();
|
||||
|
||||
/*
|
||||
Bookings
|
||||
*/
|
||||
|
||||
reserveOneBooking();
|
||||
// finaliseCheckoutWithStripe();
|
||||
72
__tests/app/src/bookings.ts
Normal file
72
__tests/app/src/bookings.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { thunder } from '../config';
|
||||
import { BookingsRessourceEnum, DurationTypeEnum } from '../helpers/graphql-zeus';
|
||||
|
||||
export const reserveOneBooking = async () => {
|
||||
const reserveOneBooking = await thunder.mutation({
|
||||
reserveOneBooking: [
|
||||
{
|
||||
input: {
|
||||
durationType: DurationTypeEnum.slot,
|
||||
slot: {
|
||||
startDate: '2021-03-18T23:00:00.000Z',
|
||||
endDate: '2021-03-19T23:00:00.000Z',
|
||||
startTime: '12:00',
|
||||
endTime: '14:30',
|
||||
},
|
||||
ressourceId: '6187782d-0445-4c0d-8c31-6c0c48a95312',
|
||||
ressourceModel: BookingsRessourceEnum.workspaces,
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: true,
|
||||
'finalPrice':true,
|
||||
subTotalPrice:true,
|
||||
"vatPrice":true,
|
||||
paymentIntent: {
|
||||
stripePaymentIntentData: {
|
||||
client_secret: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export const finaliseCheckoutWithStripe = async () => {
|
||||
return await thunder.mutation({
|
||||
finaliseCheckoutWithStripe: [
|
||||
{
|
||||
input: {
|
||||
billingInfo: {
|
||||
firstName: 'Jay',
|
||||
lastName: 'Son',
|
||||
email: '',
|
||||
address: {
|
||||
city: 'BX',
|
||||
country: 'Belgium',
|
||||
},
|
||||
},
|
||||
contactInfo: {
|
||||
firstName: 'Jay',
|
||||
lastName: 'Son',
|
||||
email: '',
|
||||
address: {
|
||||
city: 'BX',
|
||||
country: 'Belgium',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: true,
|
||||
paymentIntent: {
|
||||
stripePaymentIntentData: {
|
||||
client_secret: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
49
__tests/app/src/workspace.ts
Normal file
49
__tests/app/src/workspace.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { thunder } from '../config';
|
||||
import { BookingsRessourceEnum, DurationTypeEnum } from '../helpers/graphql-zeus';
|
||||
|
||||
export const searchWorkspace = async () => {
|
||||
const searchWorkspace = await thunder.query({
|
||||
workspacesSearchMany: [
|
||||
{
|
||||
dateFrom: '2021-03-18T00:00:00',
|
||||
dateTo: '2021-03-20T00:00:00',
|
||||
hoursFrom: '10:00',
|
||||
hoursTo: '12:00',
|
||||
},
|
||||
{
|
||||
_id: true,
|
||||
title: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log(searchWorkspace);
|
||||
return searchWorkspace;
|
||||
};
|
||||
|
||||
export const getOneWorkspace = async () => {
|
||||
return await thunder.query({
|
||||
workspacesGetOne: [
|
||||
{
|
||||
id: '6187782d-0445-4c0d-8c31-6c0c48a95312',
|
||||
},
|
||||
{
|
||||
_id: true,
|
||||
title: true,
|
||||
getOwner: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
getBookings: [
|
||||
{},
|
||||
{
|
||||
dates: {
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
11
__tests__/booking.gql
Normal file
11
__tests__/booking.gql
Normal file
@ -0,0 +1,11 @@
|
||||
mutation createBooking {
|
||||
createOneBooking(input:{ressourceModel:workspaces,ressourceId:"718aa94e-f13d-4ee3-9610-c12c98750c08",dates:{startDate:"2020-11-01T23:15:17.044+0000",endDate:"2020-11-02T23:15:17.044+0000"},capacity:10}){_id,ownerId,owner{email}}
|
||||
}
|
||||
|
||||
query bookingsGetMany {
|
||||
bookingsGetMany{_id, workspace{_id,title}}
|
||||
}
|
||||
|
||||
query bookingsGetOne {
|
||||
bookingsGetOne(id:"2a3c6b3d-e8e5-4a4d-9c10-6e438ac22e10"){_id, workspace{_id,title}}
|
||||
}
|
||||
7
__tests__/signin.gql
Normal file
7
__tests__/signin.gql
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
mutation resetPassword {
|
||||
resetPassword(input: { email: "sanawar@makeit-studio.com" }) {
|
||||
message
|
||||
}
|
||||
}
|
||||
2
__tests__/signup.gql
Normal file
2
__tests__/signup.gql
Normal file
@ -0,0 +1,2 @@
|
||||
query me {me{paymentInfo{stripeInfo{customerId},paymentMethods{sPayMethodId,default,nameOnCard},billingInfos{firstName,lastName,vatNumber}}}}
|
||||
query login {login(creds:{email:"nicolas.a.bernier@gmail.com",password:"aaaaaa"}){localId,idToken}}
|
||||
66
__tests__/workspace.gql
Normal file
66
__tests__/workspace.gql
Normal file
@ -0,0 +1,66 @@
|
||||
query workspacesSearchMany {
|
||||
workspacesSearchMany(
|
||||
workspaceType: open
|
||||
priceMin: 5
|
||||
priceMax: 15
|
||||
dateFrom: "2020-09-25T15:58:40.803+0000"
|
||||
dateTo: "2020-12-20T15:58:40.803+0000"
|
||||
hoursFrom: "08:00"
|
||||
hoursTo: "12:00"
|
||||
) {
|
||||
_id
|
||||
title {
|
||||
en
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query workspaceGetOne {
|
||||
workspacesGetOne(id: "5061682a-11df-48a8-9894-9ebc577c63d5") {
|
||||
_id
|
||||
title {
|
||||
en
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query workspacesGetMine {
|
||||
workspacesGetMine {
|
||||
_id
|
||||
title {
|
||||
en
|
||||
}
|
||||
}
|
||||
}
|
||||
mutation workspacesAddOne {
|
||||
workspacesAddOne(
|
||||
input: {
|
||||
place: {
|
||||
placeId: "ChIJrZYyJf_Rw0cR8I227YjcDEQ"
|
||||
loc: { type: "Point", coordinates: [4.3879295, 50.6819832] }
|
||||
formattedAddress: "Avenue de la Paix 36, 1420 Braine-l'Alleud, Belgium"
|
||||
}
|
||||
workspaceType: open
|
||||
pricingPerHour: 25
|
||||
currency: eur
|
||||
maxCapacity: 4
|
||||
minStay: 1
|
||||
maxStay: 2
|
||||
title: { fr: "Le rouge petillant", en: "The Red Velvet" }
|
||||
availability: {
|
||||
dateFrom: "2020-09-24T15:58:40.803+0000"
|
||||
dateTo: "2020-12-24T15:58:40.803+0000"
|
||||
weekend: true
|
||||
hours: [{ from: "08:00", to: "20:00" }]
|
||||
exceptions: [{ dateFrom: "2020-10-24T15:58:40.803+0000", dateTo: "2020-10-31T15:58:40.803+0000" }]
|
||||
}
|
||||
equipmentIds: []
|
||||
featureIds: []
|
||||
}
|
||||
) {
|
||||
_id
|
||||
title {
|
||||
en
|
||||
}
|
||||
}
|
||||
}
|
||||
24
config/config.ts
Normal file
24
config/config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { seedErrorConfig } from '@seed/interfaces/components.errors';
|
||||
import { engineBookingErrorConfig } from '@services/module-booking/components.errors';
|
||||
import { enginePaymentErrorConfig } from '@services/module-payments/components/components.errors';
|
||||
|
||||
export enum NotificationEnum {
|
||||
// emailCodeSignin = "emailCodeSignin",
|
||||
// magicLink = "magicLink",
|
||||
adminAccountAddOne = "adminAccountAddOne",
|
||||
resetPassword = "resetPassword",
|
||||
bookingUserConfirm = "bookingUserConfirm",
|
||||
bookingProConfirm = "bookingProConfirm",
|
||||
bookingUserCancel = "bookingUserCancel",
|
||||
bookingProCancel = "bookingProCancel",
|
||||
welcomeUser = "welcomeUser",
|
||||
}
|
||||
|
||||
export const ErrorsConfig = {
|
||||
...seedErrorConfig,
|
||||
...enginePaymentErrorConfig,
|
||||
...engineBookingErrorConfig,
|
||||
|
||||
// Auth & Registration
|
||||
4000: 'You already completed your profile',
|
||||
};
|
||||
19
config/domains.yaml
Normal file
19
config/domains.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
## Instal ##
|
||||
## run : ./node_modules/.bin/sls plugin install --name serverless-domain-manager --config ./lib/seed/serverless-domains.yaml
|
||||
## run : SLS_DEBUG=* ./node_modules/.bin/sls create_domain --config ./lib/seed/serverless-domains.yaml --aws-profile awsProfile
|
||||
|
||||
## SLS_DEBUG=* ./node_modules/.bin/sls delete_domain --config ./lib/seed/serverless-domains.yaml --aws-profile awsProfile
|
||||
domains:
|
||||
production: api.work-in-flex.com
|
||||
staging: staging-api.work-in-flex.com
|
||||
dev: dev-api.work-in-flex.com
|
||||
dev-v2: dev-v2-api.work-in-flex.com
|
||||
customDomain:
|
||||
basePath: '${self:service}'
|
||||
domainName: ${self:custom.domains.${self:custom.stage}}
|
||||
stage: '${self:custom.stage}'
|
||||
createRoute53Record: true
|
||||
certificateName: work-in-flex.com
|
||||
certificateArn: arn:aws:acm:us-east-1:555646219416:certificate/07e83105-1554-4e3d-b968-0a34d77911ad
|
||||
endpointType: 'edge'
|
||||
securityPolicy: tls_1_2
|
||||
32
lib/__cronjobs/serverless-cron.yaml
Normal file
32
lib/__cronjobs/serverless-cron.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: cronjob
|
||||
package:
|
||||
individually: false
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
environment:
|
||||
AWS_compute: ${self:service}-${self:provider.stage}-compute
|
||||
iamRoleStatements:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: '*'
|
||||
plugins:
|
||||
- serverless-webpack
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
webpack:
|
||||
webpackConfig: './lib/seed/webpack.config.js' # Name of webpack configuration file
|
||||
includeModules:
|
||||
forceExclude:
|
||||
- aws-sdk
|
||||
- puppeteer
|
||||
|
||||
functions:
|
||||
removeDraftBookings:
|
||||
handler: lib/__cronjobs/handler.removeDraftBookings
|
||||
timeout: 300 # optional, in seconds, default is 6
|
||||
events:
|
||||
- schedule: rate(1 minute)
|
||||
29
lib/__hooks/index.ts
Normal file
29
lib/__hooks/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export class AsyncHooksService {
|
||||
/* [MODULES - SHOP] */
|
||||
|
||||
public async afterCheckout(): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
|
||||
public async afterOrderCancel(data,ctx): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
|
||||
public async afterOrdersMarkOneAsPaid(data,ctx): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
|
||||
public async afterOrdersMarkOneAsUnPaid(data,ctx): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
|
||||
public async afterOrdersReimbursed(data,ctx): Promise<any> {
|
||||
console.log('[NO ACTION] - afterOrdersReimbursed');
|
||||
}
|
||||
|
||||
/* [MODULES - EVENTS] */
|
||||
|
||||
public async afterCheckoutEvent(): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
}
|
||||
19
lib/seed/.gitignore
vendored
Normal file
19
lib/seed/.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
|
||||
.build
|
||||
.webpack
|
||||
|
||||
*/**/.env
|
||||
*/**/postman.json
|
||||
*/**/schema.d.ts
|
||||
*/**/*.d.ts
|
||||
|
||||
src/config/.env.json
|
||||
|
||||
.serverless/
|
||||
|
||||
config/.env.json
|
||||
|
||||
serverless.yaml
|
||||
73
lib/seed/README.md
Normal file
73
lib/seed/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Make-it Seed V5
|
||||
|
||||
[](https://makeit-studio.com)
|
||||
|
||||

|
||||
|
||||
### GrapQL carefull
|
||||
|
||||
- Fields resolvers must await the response (cannot return a promise)
|
||||
- Classical errors
|
||||
-- Cannot determine GraphQL output type for 'xxx' of 'YOUR_CLASS' class. Does the value used as its TS type or explicit type is decorated with a proper decorator or is it a proper output value?
|
||||
--- You forgot to put @ObjectType or @InputType in your class definition. Or your forgot to precise on the decorator what type of field it was @Field(() => YOUR_CLASS)
|
||||
|
||||
### Serverless carefull
|
||||
|
||||
- Test your serverless file npx sls print --config ./devOps/serverless-upload.yaml
|
||||
|
||||
### Installation
|
||||
|
||||
In order to make the seed run and setup your project you need to
|
||||
|
||||
- copy the account service in your application folder (see example folder)
|
||||
- copy the \_\_loaders file in your application folder (see example folder)
|
||||
- copy the \_\_resolvers.ts file in your application folder (see example folder)
|
||||
|
||||
- copy the .env file in your config folder (see example folder)
|
||||
- put the correct admin email
|
||||
|
||||
### Domains
|
||||
|
||||
- run : ./node_modules/.bin/sls plugin install --name serverless-domain-manager --config ./lib/seed/serverless-domains.yaml
|
||||
- run : SLS_DEBUG=\* ./node_modules/.bin/sls create_domain --config ./lib/seed/serverless-domains.yaml --aws-profile awsProfile
|
||||
|
||||
- to delete : SLS_DEBUG=\* ./node_modules/.bin/sls delete_domain --config ./lib/seed/serverless-domains.yaml --aws-profile awsProfile
|
||||
|
||||
### VSCODE CONFIG
|
||||
|
||||
npm i -g eslint eslint-config-prettier eslint-plugin-prettier prettier
|
||||
|
||||
### If you need REST APIS
|
||||
|
||||
npm install --save express serverless-http
|
||||
|
||||
Check examples/app-rest.ts
|
||||
|
||||
# SOME ENV CONFIG
|
||||
|
||||
TWILIO_FROM_NUMBER
|
||||
TWILIO_ACCOUNT_SID
|
||||
TWILIO_AUTH_TOKEN
|
||||
|
||||
# How to write tests
|
||||
|
||||
npm i -g graphql-zeus
|
||||
Have your dev running
|
||||
zeus http://localhost:4000 ./**tests/app/helpers --ts
|
||||
zeus http://localhost:4001 ./**tests/admin/helpers --ts
|
||||
|
||||
# Search Engine
|
||||
|
||||
--- Free form text
|
||||
(1) Create text index on searchT field
|
||||
|
||||
# Cronjobs
|
||||
|
||||
copy examples/**cronjobs to lib/**cronjobs
|
||||
|
||||
Add this to your package.json
|
||||
"cronjobs": [
|
||||
"./lib/__cronjobs",
|
||||
"serverless-cron.yaml",
|
||||
"handler.ts"
|
||||
]
|
||||
46
lib/seed/__loaders.ts
Normal file
46
lib/seed/__loaders.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import CategoryModel from '@services/module-cms/functions/categories/category.model';
|
||||
|
||||
import ListModel from '@services/module-cms/functions/lists/list.model';
|
||||
import PageModel from '@services/module-cms/functions/pages/pages.model';
|
||||
|
||||
import AccountModel from '@src/accounts/account.model';
|
||||
import DataLoader from 'dataloader';
|
||||
import StreamEngineModel from './engine/utils/streams/stream.model';
|
||||
import { buildEngineLoader, buildLoader } from './services/database/LoaderService';
|
||||
import NotificationModel from './services/notifications/notifications.model';
|
||||
|
||||
/*
|
||||
|
||||
!!! Do not use, copy on parent only !
|
||||
|
||||
*/
|
||||
const seedModelLoaders = {
|
||||
'stream.changes': new StreamEngineModel(),
|
||||
streams: new StreamEngineModel(),
|
||||
notifications: new NotificationModel(),
|
||||
accounts: new AccountModel(),
|
||||
pages: new PageModel(),
|
||||
};
|
||||
class SeedLoaders {
|
||||
/* CMS */
|
||||
|
||||
listLoader: DataLoader<string, ListModel, string>;
|
||||
categoryLoader: DataLoader<string, CategoryModel, string>;
|
||||
// tagsLoader: DataLoader<string, TagSchema, string>;
|
||||
|
||||
// answerLoader: DataLoader<string, AnswerEngineSchema, string>;
|
||||
|
||||
/* ACCOUNT */
|
||||
|
||||
accountLoader: DataLoader<string, AccountModel, string>;
|
||||
|
||||
public constructor() {
|
||||
this.listLoader = buildLoader<ListModel>(new ListModel());
|
||||
this.categoryLoader = buildLoader<CategoryModel>(new CategoryModel());
|
||||
// this.tagsLoader = buildEngineLoader<TagModel, TagSchema>(new TagModel());
|
||||
|
||||
// this.answerLoader = buildEngineLoader<AnswerEngineModel, AnswerEngineSchema>(new AnswerEngineModel());
|
||||
|
||||
this.accountLoader = buildLoader<AccountModel>(new AccountModel());
|
||||
}
|
||||
}
|
||||
33
lib/seed/__tests/helper.ts
Normal file
33
lib/seed/__tests/helper.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { clog } from '../helpers/Utils';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
export function testRunQuery(url: string, token: string): (query: string, variables?: Record<string, any>) => Promise<any> {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
return async (query, variables) => {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (token && (token as any) != '') headers.authorization = token;
|
||||
|
||||
const r = JSON.stringify({ query, variables });
|
||||
clog({ query, variables });
|
||||
|
||||
const response = await fetch(url, {
|
||||
body: r,
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
});
|
||||
|
||||
try {
|
||||
const text = await response.text();
|
||||
const res = JSON.parse(text);
|
||||
if (res.errors) clog(res.errors[0]);
|
||||
clog(res.data);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
66
lib/seed/app-admin.ts
Normal file
66
lib/seed/app-admin.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import 'reflect-metadata';
|
||||
import { authMiddleware, errorMiddleware, ctxMiddleware, complexityMiddleware } from '@seed/graphql/Middleware';
|
||||
|
||||
import Firebase from '@seed/services/auth/FirebaseService';
|
||||
|
||||
import { AdminResolvers } from '@src/__indexes/__resolvers.index.admin';
|
||||
import { SettingsCache } from './graphql/Settings';
|
||||
import { buildSchema } from 'type-graphql';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { ApolloServer } = require('apollo-server');
|
||||
|
||||
const PORT = process.env.PORT || 4001;
|
||||
|
||||
const bootstrap = async (): Promise<any> => {
|
||||
process.env.NODE_ENV = 'local';
|
||||
|
||||
const settingsI = SettingsCache.getInstance();
|
||||
await settingsI.refreshCache();
|
||||
|
||||
try {
|
||||
const schema = await buildSchema({
|
||||
resolvers: AdminResolvers as any,
|
||||
authChecker: authMiddleware,
|
||||
});
|
||||
|
||||
const server = new ApolloServer({
|
||||
// typeDefs,
|
||||
// resolvers,
|
||||
schema,
|
||||
formatError: errorMiddleware,
|
||||
formatResponse: (response): any => {
|
||||
return response;
|
||||
},
|
||||
context: ctxMiddleware,
|
||||
tracing: false,
|
||||
plugins: [
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
requestDidStart: () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
didResolveOperation({ request, document }) {
|
||||
return complexityMiddleware(schema, request.variables, document);
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
const token = await Firebase.getInstance().createTokenId(process.env.TEST_ADMIN_EMAIL ? process.env.TEST_ADMIN_EMAIL : '');
|
||||
console.log(token);
|
||||
} catch (error) {
|
||||
console.log('no token available');
|
||||
}
|
||||
|
||||
server.listen(PORT).then(({ url }) => {
|
||||
console.log(`🚀 Server ready at ${url}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('[GRAPH ERROR]', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
bootstrap();
|
||||
72
lib/seed/app.ts
Normal file
72
lib/seed/app.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import 'reflect-metadata';
|
||||
import { buildSchema, NonEmptyArray } from 'type-graphql';
|
||||
|
||||
import { authMiddleware, errorMiddleware, ctxMiddleware, complexityMiddleware } from '@seed/graphql/Middleware';
|
||||
|
||||
import Firebase from '@seed/services/auth/FirebaseService';
|
||||
|
||||
import { AppResolvers } from '@src/__indexes/__resolvers.index';
|
||||
import { SettingsCache } from './graphql/Settings';
|
||||
import { runBeforeAll } from '@src/__indexes/__bootstrap';
|
||||
import { nullToUndefined } from './helpers/Utils';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { ApolloServer } = require('apollo-server');
|
||||
|
||||
const PORT = process.env.PORT || 4000;
|
||||
|
||||
const bootstrap = async (): Promise<any> => {
|
||||
process.env.NODE_ENV = 'local';
|
||||
|
||||
const settingsI = SettingsCache.getInstance();
|
||||
await settingsI.refreshCache();
|
||||
|
||||
try {
|
||||
const schema = await buildSchema({
|
||||
resolvers: (AppResolvers as unknown) as NonEmptyArray<Function>,
|
||||
authChecker: authMiddleware,
|
||||
validate: false, // disable automatic validation or pass the default config object
|
||||
});
|
||||
|
||||
const server = new ApolloServer({
|
||||
// typeDefs,
|
||||
// resolvers,
|
||||
schema,
|
||||
formatError: errorMiddleware,
|
||||
formatResponse: (response): any => {
|
||||
return nullToUndefined(response);
|
||||
},
|
||||
context: ctxMiddleware,
|
||||
tracing: false,
|
||||
plugins: [
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
requestDidStart: () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
didResolveOperation({ request, document }) {
|
||||
return complexityMiddleware(schema, request.variables, document);
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
const token = await Firebase.getInstance().createTokenId(process.env.TEST_USER_EMAIL ? process.env.TEST_USER_EMAIL : '');
|
||||
console.log(`Token for ${process.env.TEST_USER_EMAIL}`, token.idToken);
|
||||
} catch (error) {
|
||||
console.log('no token available');
|
||||
}
|
||||
|
||||
await runBeforeAll();
|
||||
|
||||
server.listen(PORT).then(({ url }) => {
|
||||
console.log(`🚀 Server ready at ${url}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('[GRAPH ERROR]', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
bootstrap();
|
||||
117
lib/seed/devops/deploy-domains.js
Normal file
117
lib/seed/devops/deploy-domains.js
Normal file
@ -0,0 +1,117 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const execa = require('execa');
|
||||
const Listr = require('listr');
|
||||
const package = require('../../../package.json');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
if (!package.devops) throw `No devops configuration found in package.json`;
|
||||
if (!package.devops.services) throw `No devops services found in package.json`;
|
||||
if (!package.devops.awsProfile) throw `No devops awsProfile found in package.json`;
|
||||
if (!package.devops.awsRegion) throw 'No devops awsRegion found in package.json';
|
||||
|
||||
// Config input
|
||||
const awsProfile = package.devops.awsProfile;
|
||||
const awsRegion = package.devops.awsRegion;
|
||||
|
||||
// Arguments checks
|
||||
|
||||
const deployBase = function() {
|
||||
return new Listr(
|
||||
[
|
||||
{
|
||||
title: 'Deploy domains for DEV',
|
||||
task: async () => {
|
||||
const subprocess = execa(
|
||||
'npx',
|
||||
[
|
||||
'./node_modules/.bin/sls',
|
||||
'create_domain',
|
||||
'--config',
|
||||
'./lib/seed/devops/serverless-domains.yaml',
|
||||
'--stage',
|
||||
'dev',
|
||||
'--aws-profile',
|
||||
awsProfile,
|
||||
'--region',
|
||||
awsRegion,
|
||||
],
|
||||
{ env: { 'SLS_DEBUG=*': '*' } },
|
||||
);
|
||||
|
||||
subprocess.stdout.pipe(fs.createWriteStream('.slslogs.txt', { flags: 'a' }));
|
||||
|
||||
const { stdout } = await subprocess;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Deploy domains for STAGING',
|
||||
task: async () => {
|
||||
const subprocess = execa(
|
||||
'npx',
|
||||
[
|
||||
'./node_modules/.bin/sls',
|
||||
'create_domain',
|
||||
'--config',
|
||||
'./lib/seed/devops/serverless-domains.yaml',
|
||||
'--stage',
|
||||
'staging',
|
||||
'--aws-profile',
|
||||
awsProfile,
|
||||
'--region',
|
||||
awsRegion,
|
||||
],
|
||||
{ env: { 'SLS_DEBUG=*': '*' } },
|
||||
);
|
||||
|
||||
subprocess.stdout.pipe(fs.createWriteStream('.slslogs.txt', { flags: 'a' }));
|
||||
|
||||
const { stdout } = await subprocess;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Deploy domains for PRODUCTION',
|
||||
task: async () => {
|
||||
const subprocess = execa(
|
||||
'npx',
|
||||
[
|
||||
'./node_modules/.bin/sls',
|
||||
'create_domain',
|
||||
'--config',
|
||||
'./lib/seed/devops/serverless-domains.yaml',
|
||||
'--stage',
|
||||
'production',
|
||||
'--aws-profile',
|
||||
awsProfile,
|
||||
'--region',
|
||||
awsRegion,
|
||||
],
|
||||
{ env: { 'SLS_DEBUG=*': '*' } },
|
||||
);
|
||||
|
||||
subprocess.stdout.pipe(fs.createWriteStream('.slslogs.txt', { flags: 'a' }));
|
||||
|
||||
const { stdout } = await subprocess;
|
||||
},
|
||||
},
|
||||
],
|
||||
{ concurrent: false },
|
||||
);
|
||||
};
|
||||
|
||||
const tasksList = [];
|
||||
|
||||
tasksList.push({
|
||||
title: 'Deploying Domains',
|
||||
task: () => {
|
||||
return deployBase();
|
||||
},
|
||||
});
|
||||
|
||||
const tasks = new Listr(tasksList, { concurrent: false });
|
||||
|
||||
tasks.run().catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
98
lib/seed/devops/deploy-v2.js
Normal file
98
lib/seed/devops/deploy-v2.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const execa = require('execa');
|
||||
const Listr = require('listr');
|
||||
const package = require('../../../package.json');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
if (!package.devops) throw `No devops configuration found in package.json`;
|
||||
if (!package.devops.services) throw `No devops services found in package.json`;
|
||||
if (!package.devops.awsProfile) throw `No devops awsProfile found in package.json`;
|
||||
if (!package.devops.awsRegion) throw 'No devops awsRegion found in package.json';
|
||||
|
||||
// Config input
|
||||
const paths = package.devops.services;
|
||||
const awsProfile = package.devops.awsProfile;
|
||||
const awsRegion = package.devops.awsRegion;
|
||||
|
||||
// Arguments checks
|
||||
|
||||
const stage = process.argv[2] || 'dev';
|
||||
const sName = process.argv[3] || 'all';
|
||||
|
||||
const deployBase = function(basePath) {
|
||||
const baseDirectory = basePath[0];
|
||||
const serverlessFile = baseDirectory + '/' + basePath[1];
|
||||
|
||||
let handlerFile;
|
||||
if (basePath[2]) handlerFile = baseDirectory + '/' + basePath[2];
|
||||
else handlerFile = baseDirectory + '/' + 'handler.ts';
|
||||
|
||||
return new Listr(
|
||||
[
|
||||
{
|
||||
title: 'Remove handler file',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
task: () => execa('rm', ['-f', 'handler.ts']).then((result) => {}),
|
||||
},
|
||||
{
|
||||
title: 'Copy handler file',
|
||||
task: () => {
|
||||
console.log('handler file here', handlerFile);
|
||||
execa('cp', [handlerFile, 'handler.ts']).then((result) => {});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Deploy serverless file',
|
||||
task: async () => {
|
||||
console.log('serverless file here', serverlessFile);
|
||||
|
||||
const subprocess = execa(
|
||||
'npx',
|
||||
['sls', 'deploy', '--config', serverlessFile, '--stage', stage, '--aws-profile', awsProfile, '--region', awsRegion],
|
||||
{ env: { 'SLS_DEBUG=*': '*' } },
|
||||
);
|
||||
|
||||
console.log('deploying logs here', '.slslogs.txt');
|
||||
subprocess.stdout.pipe(fs.createWriteStream('.slslogs.txt', { flags: 'a' }));
|
||||
|
||||
const { stdout } = await subprocess;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remove handler file',
|
||||
task: () => execa('rm', ['-f', 'handler.ts']).then((result) => {}),
|
||||
},
|
||||
],
|
||||
{ concurrent: false },
|
||||
);
|
||||
};
|
||||
|
||||
let functions = Object.keys(paths);
|
||||
|
||||
if (sName != 'all') {
|
||||
functions = sName.split(',');
|
||||
for (let index = 0; index < functions.length; index++) {
|
||||
if (!paths.hasOwnProperty(functions[index])) throw `Bad functions name for ${functions[index]}`;
|
||||
}
|
||||
}
|
||||
|
||||
const tasksList = [];
|
||||
|
||||
for (let index = 0; index < functions.length; index++) {
|
||||
const element = functions[index];
|
||||
tasksList.push({
|
||||
title: 'Deploying' + ' ' + element,
|
||||
task: () => {
|
||||
return deployBase(paths[element]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const tasks = new Listr(tasksList, { concurrent: false });
|
||||
|
||||
tasks.run().catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
103
lib/seed/devops/deploy.js
Normal file
103
lib/seed/devops/deploy.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const execa = require('execa');
|
||||
const Listr = require('listr');
|
||||
const package = require('../../../package.json');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
if (!package.devops) throw `No devops configuration found in package.json`;
|
||||
if (!package.devops.services) throw `No devops services found in package.json`;
|
||||
if (!package.devops.awsProfile) throw `No devops awsProfile found in package.json`;
|
||||
if (!package.devops.awsRegion) throw 'No devops awsRegion found in package.json';
|
||||
|
||||
// Config input
|
||||
const paths = package.devops.services;
|
||||
const awsProfile = package.devops.awsProfile;
|
||||
const awsRegion = package.devops.awsRegion;
|
||||
|
||||
// Arguments checks
|
||||
|
||||
const stage = process.argv[2] || 'dev';
|
||||
const sName = process.argv[3] || 'all';
|
||||
|
||||
const deployBase = function(basePath) {
|
||||
const baseDirectory = basePath[0];
|
||||
const serverlessFile = baseDirectory + '/' + basePath[1];
|
||||
|
||||
let handlerFile;
|
||||
if (basePath[2]) handlerFile = baseDirectory + '/' + basePath[2];
|
||||
else handlerFile = baseDirectory + '/' + 'handler.ts';
|
||||
|
||||
return new Listr(
|
||||
[
|
||||
{
|
||||
title: 'Remove handler file',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
task: () => execa('rm', ['-f', 'handler.ts']).then((result) => {}),
|
||||
},
|
||||
{
|
||||
title: 'Copy handler file',
|
||||
task: () => {
|
||||
console.log('handler file here', handlerFile);
|
||||
execa('cp', [handlerFile, 'handler.ts']).then((result) => {});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Copy serverless file',
|
||||
task: () => {
|
||||
console.log('serverless file here', serverlessFile);
|
||||
execa('cp', [serverlessFile, 'serverless.yaml']).then((result) => {});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Deploy serverless file',
|
||||
task: async () => {
|
||||
const subprocess = execa(
|
||||
'npx',
|
||||
['sls', 'deploy', /*'--config', serverlessFile,*/ '--stage', stage, '--aws-profile', awsProfile, '--region', awsRegion],
|
||||
{ env: { 'SLS_DEBUG=*': '*' } },
|
||||
);
|
||||
|
||||
console.log('deploying logs here', '.slslogs.txt');
|
||||
subprocess.stdout.pipe(fs.createWriteStream('.slslogs.txt', { flags: 'a' }));
|
||||
|
||||
const { stdout } = await subprocess;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remove handler file',
|
||||
task: () => execa('rm', ['-f', 'handler.ts']).then((result) => {}),
|
||||
},
|
||||
],
|
||||
{ concurrent: false },
|
||||
);
|
||||
};
|
||||
|
||||
let functions = Object.keys(paths);
|
||||
|
||||
if (sName != 'all') {
|
||||
functions = sName.split(',');
|
||||
for (let index = 0; index < functions.length; index++) {
|
||||
if (!paths.hasOwnProperty(functions[index])) throw `Bad functions name for ${functions[index]}`;
|
||||
}
|
||||
}
|
||||
|
||||
const tasksList = [];
|
||||
|
||||
for (let index = 0; index < functions.length; index++) {
|
||||
const element = functions[index];
|
||||
tasksList.push({
|
||||
title: 'Deploying' + ' ' + element,
|
||||
task: () => {
|
||||
return deployBase(paths[element]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const tasks = new Listr(tasksList, { concurrent: false });
|
||||
|
||||
tasks.run().catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
10
lib/seed/devops/handler-admin.ts
Normal file
10
lib/seed/devops/handler-admin.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import 'reflect-metadata';
|
||||
import { ServerInstance } from '@seed/graphql/Server';
|
||||
|
||||
import { AdminResolvers } from '@src/__indexes/__resolvers.index.admin';
|
||||
|
||||
export async function adminHandler(event: any, context: any): Promise<any> {
|
||||
const apolloInstance = await ServerInstance.getInstance(AdminResolvers);
|
||||
const response = await apolloInstance.run(event, context);
|
||||
return response;
|
||||
}
|
||||
9
lib/seed/devops/handler-app.ts
Normal file
9
lib/seed/devops/handler-app.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import 'reflect-metadata';
|
||||
import { ServerInstance } from '@seed/graphql/Server';
|
||||
import { AppResolvers } from '@src/__indexes/__resolvers.index';
|
||||
|
||||
export async function appHandler(event: any, context: any): Promise<any> {
|
||||
const apolloInstance = await ServerInstance.getInstance(AppResolvers);
|
||||
const response = await apolloInstance.run(event, context);
|
||||
return response;
|
||||
}
|
||||
153
lib/seed/devops/handler-hooks.ts
Normal file
153
lib/seed/devops/handler-hooks.ts
Normal file
@ -0,0 +1,153 @@
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { modelsLoaders } from '@src/__indexes/__loaders';
|
||||
import ChangeStreamModel from '@lib/seed/services/change-stream/change-stream.model';
|
||||
import { StreamOperationType, PostHookStatus } from '@lib/seed/services/change-stream/change-stream.components';
|
||||
import { AsyncHooksService } from '@lib/__hooks';
|
||||
import { createApolloContext } from '@seed/graphql/Middleware';
|
||||
import { SettingsCache } from '@seed/graphql/Settings';
|
||||
|
||||
export const hookHandler = async (event: any): Promise<void> => {
|
||||
const settingsI = SettingsCache.getInstance();
|
||||
await settingsI.refreshCache();
|
||||
|
||||
process.env.isHook = 'true';
|
||||
|
||||
const { streamId, notificationRessourceId } = event;
|
||||
|
||||
if (streamId) {
|
||||
await streamHandler(streamId);
|
||||
} else if (notificationRessourceId) {
|
||||
await notificationHandler(notificationRessourceId);
|
||||
} else {
|
||||
console.log('[ASYNC - ERROR]', 'was called with no info');
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
async function notificationHandler(streamId: string) {
|
||||
const stream = await (await new ChangeStreamModel().db()).findOneAndUpdate(
|
||||
{ _id: streamId, hookStatus: PostHookStatus.new },
|
||||
{ $set: { hookStatus: PostHookStatus.inProcess } },
|
||||
);
|
||||
|
||||
if (stream && stream.value) {
|
||||
const streamData = stream.value;
|
||||
console.log('[ASYNC - STREAM] Stream', stream.value.collection, stream.value.operation);
|
||||
|
||||
try {
|
||||
if (streamData.operation == StreamOperationType.hook) {
|
||||
const HookService = new AsyncHooksService();
|
||||
|
||||
if (!HookService[streamData.collection])
|
||||
throw `[ASYNC - STREAM] ERROR - No AsyncHooksService for this ${streamData.collection}. Are you sure you put it in the AsyncHooksService ?`;
|
||||
|
||||
if (streamData.insertedValues && streamData.insertedValues.length > 0) {
|
||||
const paramsArray = streamData.insertedValues as any[];
|
||||
|
||||
for (let index = 0; index < paramsArray.length; index++) {
|
||||
// Deal with CTX
|
||||
if (paramsArray[index].ctx) {
|
||||
paramsArray[index] = await createApolloContext(paramsArray[index].ctx.user._id, paramsArray[index].ctx.organisationId);
|
||||
}
|
||||
}
|
||||
await HookService[streamData.collection](...paramsArray);
|
||||
} else await HookService[streamData.collection]();
|
||||
} else {
|
||||
if (!modelsLoaders[streamData.collection])
|
||||
throw `[ASYNC - STREAM] ERROR - No ModelLoaders for this ${streamData.collection}. Are you sure you put it in the ModelLoaders ?`;
|
||||
|
||||
console.log('streamData', streamData);
|
||||
const model = modelsLoaders[streamData.collection];
|
||||
const modelData = await model.getOne({ _id: streamData.documentKey }, null);
|
||||
console.log('model', model);
|
||||
|
||||
switch (streamData.operation) {
|
||||
case StreamOperationType.insert:
|
||||
await model.afterCreate();
|
||||
break;
|
||||
case StreamOperationType.update:
|
||||
await model.afterUpdate();
|
||||
break;
|
||||
case StreamOperationType.delete:
|
||||
await model.afterDelete();
|
||||
break;
|
||||
default:
|
||||
throw `[ASYNC - STREAM] ERROR - No operation ${streamData.operation} for this ${streamData.collection}`;
|
||||
}
|
||||
}
|
||||
await (await new ChangeStreamModel().db()).updateOne({ _id: streamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
console.log('[ASYNC - STREAM] COMPLETE', { _id: streamId });
|
||||
} catch (error) {
|
||||
console.error('[ASYNC - STREAM] ERROR', error);
|
||||
await (await new ChangeStreamModel().db()).updateOne({ _id: streamId }, { $set: { hookStatus: PostHookStatus.error } });
|
||||
}
|
||||
} else {
|
||||
console.error('[ASYNC - STREAM] ERROR', 'No new stream found', streamId);
|
||||
}
|
||||
}
|
||||
|
||||
async function streamHandler(streamId: string) {
|
||||
const stream = await (await new ChangeStreamModel().db()).findOneAndUpdate(
|
||||
{ _id: streamId, hookStatus: PostHookStatus.new },
|
||||
{ $set: { hookStatus: PostHookStatus.inProcess } },
|
||||
);
|
||||
|
||||
if (stream && stream.value) {
|
||||
const streamData = stream.value;
|
||||
console.log('[ASYNC - STREAM] Stream', stream.value.collection, stream.value.operation);
|
||||
|
||||
try {
|
||||
if (streamData.operation == StreamOperationType.hook) {
|
||||
const HookService = new AsyncHooksService();
|
||||
|
||||
if (!HookService[streamData.collection])
|
||||
throw `[ASYNC - STREAM] ERROR - No AsyncHooksService for this ${streamData.collection}. Are you sure you put it in the AsyncHooksService ?`;
|
||||
|
||||
if (streamData.insertedValues && streamData.insertedValues.length > 0) {
|
||||
const paramsArray = streamData.insertedValues as any[];
|
||||
|
||||
for (let index = 0; index < paramsArray.length; index++) {
|
||||
// Deal with CTX
|
||||
if (paramsArray[index].ctx) {
|
||||
paramsArray[index] = await createApolloContext(paramsArray[index].ctx.user._id, paramsArray[index].ctx.organisationId);
|
||||
}
|
||||
}
|
||||
await HookService[streamData.collection](...paramsArray);
|
||||
} else await HookService[streamData.collection]();
|
||||
} else {
|
||||
if (!modelsLoaders[streamData.collection])
|
||||
throw `[ASYNC - STREAM] ERROR - No ModelLoaders for this ${streamData.collection}. Are you sure you put it in the ModelLoaders ?`;
|
||||
|
||||
console.log('streamData', streamData);
|
||||
const model = modelsLoaders[streamData.collection];
|
||||
const modelData = await model.getOne({ _id: streamData.documentKey }, null);
|
||||
console.log('model', model);
|
||||
|
||||
switch (streamData.operation) {
|
||||
case StreamOperationType.insert:
|
||||
await model.afterCreate();
|
||||
break;
|
||||
case StreamOperationType.update:
|
||||
await model.afterUpdate();
|
||||
break;
|
||||
case StreamOperationType.delete:
|
||||
await model.afterDelete();
|
||||
break;
|
||||
default:
|
||||
throw `[ASYNC - STREAM] ERROR - No operation ${streamData.operation} for this ${streamData.collection}`;
|
||||
}
|
||||
}
|
||||
await (await new ChangeStreamModel().db()).updateOne({ _id: streamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
console.error('[ASYNC - STREAM] COMPLETE', { _id: streamId });
|
||||
} catch (error) {
|
||||
console.error('[ASYNC - STREAM] ERROR', error);
|
||||
await (await new ChangeStreamModel().db()).updateOne({ _id: streamId }, { $set: { hookStatus: PostHookStatus.error } });
|
||||
}
|
||||
} else {
|
||||
console.error('[ASYNC - STREAM] ERROR', 'No new stream found', streamId);
|
||||
}
|
||||
}
|
||||
55
lib/seed/devops/serverless-admin.yaml
Normal file
55
lib/seed/devops/serverless-admin.yaml
Normal file
@ -0,0 +1,55 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: admin
|
||||
package:
|
||||
individually: true
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
environment:
|
||||
AWS_Hooks: hooks-${self:provider.stage}-hookHandler
|
||||
S3_BUCKET: ${self:app}-appdata
|
||||
iamRoleStatements:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: '*'
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- s3:*
|
||||
Resource: arn:aws:s3:::${self:provider.environment.S3_BUCKET}/*
|
||||
plugins:
|
||||
- serverless-webpack
|
||||
- serverless-domain-manager
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
domains: ${file(./config/domains.yaml):domains}
|
||||
customDomain: ${file(./config/domains.yaml):customDomain}
|
||||
webpack:
|
||||
webpackConfig: './lib/seed/webpack.config.js' # Name of webpack configuration file
|
||||
includeModules:
|
||||
forceExclude:
|
||||
- aws-sdk
|
||||
- puppeteer
|
||||
functions:
|
||||
admin:
|
||||
handler: handler.adminHandler
|
||||
timeout: 60
|
||||
events:
|
||||
- http:
|
||||
path: /
|
||||
method: post
|
||||
cors:
|
||||
origin: '*'
|
||||
headers:
|
||||
- Content-Type
|
||||
- Authorization
|
||||
- X-Api-Key
|
||||
- X-Amz-Security-Token
|
||||
- X-Amz-User-Agent
|
||||
- organisationid
|
||||
|
||||
- http:
|
||||
path: /
|
||||
method: get
|
||||
cors: true
|
||||
60
lib/seed/devops/serverless-app.yaml
Normal file
60
lib/seed/devops/serverless-app.yaml
Normal file
@ -0,0 +1,60 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: app
|
||||
package:
|
||||
individually: true
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
environment:
|
||||
AWS_Hooks: hooks-${self:provider.stage}-hookHandler
|
||||
S3_BUCKET: ${self:app}-appdata
|
||||
iamRoleStatements:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: '*'
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- s3:*
|
||||
Resource: arn:aws:s3:::${self:provider.environment.S3_BUCKET}/*
|
||||
|
||||
plugins:
|
||||
- serverless-webpack
|
||||
- serverless-domain-manager
|
||||
- serverless-offline
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
domains: ${file(./config/domains.yaml):domains}
|
||||
customDomain: ${file(./config/domains.yaml):customDomain}
|
||||
serverless-offline:
|
||||
httpPort: 4000
|
||||
lambdaPort: 4010
|
||||
webpack:
|
||||
webpackConfig: './lib/seed/webpack.config.js' # Name of webpack configuration file
|
||||
includeModules:
|
||||
forceExclude:
|
||||
- aws-sdk
|
||||
- puppeteer
|
||||
functions:
|
||||
app:
|
||||
handler: handler.appHandler
|
||||
timeout: 60
|
||||
events:
|
||||
- http:
|
||||
path: /
|
||||
method: post
|
||||
cors:
|
||||
origin: '*'
|
||||
headers:
|
||||
- Content-Type
|
||||
- Authorization
|
||||
- X-Authorization
|
||||
- X-Api-Key
|
||||
- X-Amz-Security-Token
|
||||
- X-Amz-User-Agent
|
||||
- organisationid
|
||||
- http:
|
||||
path: /
|
||||
method: get
|
||||
cors: true
|
||||
12
lib/seed/devops/serverless-domains.yaml
Normal file
12
lib/seed/devops/serverless-domains.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: domains
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
plugins:
|
||||
- serverless-domain-manager
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
domains: ${file(./config/domains.yaml):domains}
|
||||
customDomain: ${file(./config/domains.yaml):customDomain}
|
||||
37
lib/seed/devops/serverless-hooks.yaml
Normal file
37
lib/seed/devops/serverless-hooks.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: hooks
|
||||
package:
|
||||
individually: true
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
environment:
|
||||
AWS_Hooks: hooks-${self:provider.stage}-hookHandler
|
||||
S3_BUCKET: ${self:app}-appdata
|
||||
iamRoleStatements:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: '*'
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- s3:*
|
||||
Resource: arn:aws:s3:::${self:provider.environment.S3_BUCKET}/*
|
||||
|
||||
plugins:
|
||||
- serverless-webpack
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
webpack:
|
||||
webpackConfig: './lib/seed/webpack.config.js' # Name of webpack configuration file
|
||||
includeModules:
|
||||
forceExclude:
|
||||
- aws-sdk
|
||||
- puppeteer
|
||||
functions:
|
||||
hookHandler:
|
||||
handler: handler.hookHandler
|
||||
memorySize: 1024 # optional, in MB, default is 1024
|
||||
timeout: 600 # optional, in seconds, default is 6
|
||||
async: true
|
||||
161
lib/seed/engine/EngineAccessService.ts
Normal file
161
lib/seed/engine/EngineAccessService.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import AccountModel from '@src/accounts/account.model';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class EngineAccessService {
|
||||
static addPermissions<U, T>(ressource: any, type: ('r' | 'w' | 'd')[], ids: (string | string)[]): void {
|
||||
for (let index = 0; index < type.length; index++) {
|
||||
const t = type[index];
|
||||
ressource.permissions[t] = ressource.permissions[t].concat(ids);
|
||||
}
|
||||
}
|
||||
|
||||
static addPermissionToQuery(account: AccountModel | null, query: 'get' | 'update' | 'delete', params: any): any {
|
||||
let types: any[] = ['public'];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin, no need to add the query filters
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return params;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
|
||||
// if (account.organisationIds) {
|
||||
// params.organisationId = { $in: account.organisationIds };
|
||||
// }
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
|
||||
types.push(account._id);
|
||||
types = types.concat(account.types);
|
||||
}
|
||||
|
||||
if (!params.$and) params.$and = [];
|
||||
|
||||
switch (query) {
|
||||
default:
|
||||
case 'get':
|
||||
params.$and.push({ 'permissions.r': { $in: types } });
|
||||
break;
|
||||
case 'update':
|
||||
params.$and.push({ 'permissions.w': { $in: types } });
|
||||
break;
|
||||
case 'delete':
|
||||
params.$and.push({ 'permissions.d': { $in: types } });
|
||||
break;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
static checkPermissions<U, T>(ressource: any, account: AccountModel | null, type: 'c' | 'r' | 'w' | 'd'): boolean {
|
||||
// Adding the public by default
|
||||
let perm: any[] = [AccountTypeEnum.public];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return true;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
if (
|
||||
ressource.organisationId &&
|
||||
account.organisationIds &&
|
||||
!account.organisationIds.includes(ressource.organisationId) &&
|
||||
!ressource.permissions.r.includes(AccountTypeEnum.public)
|
||||
)
|
||||
throw newError(2100, {
|
||||
allowed: ressource.organisationId,
|
||||
you: account.organisationIds,
|
||||
});
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
perm.push(account._id);
|
||||
perm = perm.concat(account.types);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify on the ressource level
|
||||
*/
|
||||
|
||||
const permissions = ressource.permissions[type];
|
||||
|
||||
// if there is no permission on the ressource, return true
|
||||
if (!permissions) return true;
|
||||
|
||||
let hasPerm = false;
|
||||
|
||||
// Verifying if it matches
|
||||
for (let index = 0; index < perm.length; index++) {
|
||||
const element = perm[index];
|
||||
if (permissions.includes(element)) {
|
||||
hasPerm = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPerm)
|
||||
throw newError(2000, {
|
||||
allowed: permissions,
|
||||
you: { _id: account?._id, types: account?.types },
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static checkOrganisationPermissions<U, T>(ressource: any, organisationId: string): boolean {
|
||||
if (organisationId == ressource.organisationId || organisationId == ressource._id) return true;
|
||||
throw newError(2100, { allowedOrgId: ressource.organisationId, youOrgId: organisationId });
|
||||
}
|
||||
|
||||
static addOrganisationToQuery(account: AccountModel | null, query: 'get' | 'update' | 'delete', params: any): any {
|
||||
let types: any[] = ['public'];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin, no need to add the query filters
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return params;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
if (account.organisationIds) {
|
||||
params.organisationId = { $in: account.organisationIds };
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
|
||||
types.push(account._id);
|
||||
types = types.concat(account.types);
|
||||
}
|
||||
|
||||
switch (query) {
|
||||
default:
|
||||
case 'get':
|
||||
params = { ...params, $or: [{ 'permissions.r': { $in: _.uniq(types) } }] };
|
||||
break;
|
||||
case 'update':
|
||||
params = { ...params, $or: [{ 'permissions.w': { $in: _.uniq(types) } }] };
|
||||
break;
|
||||
case 'delete':
|
||||
params = { ...params, $or: [{ 'permissions.d': { $in: _.uniq(types) } }] };
|
||||
break;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
277
lib/seed/engine/EngineModel.ts
Normal file
277
lib/seed/engine/EngineModel.ts
Normal file
@ -0,0 +1,277 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ObjectType } from 'type-graphql';
|
||||
import { classToPlain, Exclude, plainToClassFromExist } from 'class-transformer';
|
||||
|
||||
import { Collection, ObjectId } from 'mongodb';
|
||||
import DB from '@seed/services/database/DBService';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
|
||||
import { IEngineSchema, MetaBy } from './EngineSchema';
|
||||
|
||||
import { GetArgs } from '@seed/graphql/Request';
|
||||
|
||||
import {
|
||||
bulk,
|
||||
deleteOne,
|
||||
getAll,
|
||||
getCount,
|
||||
getMany,
|
||||
getOne,
|
||||
getQuery,
|
||||
saveMany,
|
||||
saveOne,
|
||||
updateOne,
|
||||
updateOneCustom,
|
||||
updateOneSubField,
|
||||
} from './utils/crud.utils';
|
||||
import { getCountGeneric, getManyGeneric, getManyGenericWithArgs, getOneGeneric } from './utils/service.utils';
|
||||
|
||||
import {
|
||||
BulkInput,
|
||||
DeleteOneInput,
|
||||
GetAllInput,
|
||||
GetCountInput,
|
||||
GetManyInput,
|
||||
GetOneInput,
|
||||
GetQueryInput,
|
||||
SaveManyInput,
|
||||
SaveOneInput,
|
||||
UpdateOneCustomInput,
|
||||
UpdateOneInput,
|
||||
UpdateOneSubField,
|
||||
} from './utils/__interface';
|
||||
import { EnginePathComponent } from '@seed/interfaces/components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { nullToUndefined } from '@seed/helpers/Utils';
|
||||
|
||||
@ObjectType()
|
||||
export abstract class EngineModel<SchemaDBInterface, SchemaDB, Schema> implements IEngineSchema {
|
||||
@Exclude()
|
||||
public collectionName: ModelCollectionEnum | string;
|
||||
@Exclude()
|
||||
public permissions: Permission;
|
||||
|
||||
@Exclude()
|
||||
defaultSort: string;
|
||||
|
||||
dbData: SchemaDB;
|
||||
|
||||
searchT?: string;
|
||||
|
||||
_id: string;
|
||||
organisationId?: string | undefined;
|
||||
paths?: EnginePathComponent[];
|
||||
by?: MetaBy | undefined;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
tagsIds?: string[];
|
||||
|
||||
public constructor(init: {
|
||||
collectionName: ModelCollectionEnum | string;
|
||||
permissions: Permission;
|
||||
dataInit: SchemaDB & Partial<IEngineSchema>;
|
||||
defaultSort?: string;
|
||||
}) {
|
||||
this.collectionName = init.collectionName;
|
||||
this.permissions = init.permissions;
|
||||
|
||||
this.dbData = init.dataInit;
|
||||
|
||||
this.permissions = {
|
||||
c: init.permissions.c,
|
||||
r: init.permissions.r,
|
||||
w: init.permissions.w,
|
||||
d: init.permissions.d,
|
||||
};
|
||||
|
||||
this.createdAt = new Date();
|
||||
this.updatedAt = new Date();
|
||||
|
||||
this.defaultSort = init.defaultSort || 'createdAt desc';
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Model functions
|
||||
*
|
||||
*/
|
||||
|
||||
public get(): Schema {
|
||||
const data: any = this.dbData;
|
||||
|
||||
if (this._id) data._id = this._id;
|
||||
if (this.permissions) data.permissions = this.permissions;
|
||||
|
||||
if (this.organisationId) data.organisationId = this.organisationId;
|
||||
if (this.by) data.by = this.by;
|
||||
|
||||
if (this.createdAt) data.createdAt = this.createdAt;
|
||||
if (this.updatedAt) data.updatedAt = this.updatedAt;
|
||||
|
||||
return this.plainToClass(data) as Schema;
|
||||
}
|
||||
|
||||
public set(doc: any): void {
|
||||
const newData = {
|
||||
...classToPlain(this.dbData), // Get old data
|
||||
...doc, // Replace with new data
|
||||
};
|
||||
|
||||
this.dbData = _.assign(this.dbData, newData);
|
||||
|
||||
if (doc._id) this._id = doc._id;
|
||||
|
||||
if (doc.permissions) {
|
||||
this.permissions = {
|
||||
c: _.uniq(_.concat(doc.permissions.c)),
|
||||
r: _.uniq(_.concat(doc.permissions.r)),
|
||||
w: _.uniq(_.concat(doc.permissions.w)),
|
||||
d: _.uniq(_.concat(doc.permissions.d)),
|
||||
};
|
||||
}
|
||||
|
||||
if (doc.organisationId) this.organisationId = doc.organisationId;
|
||||
|
||||
if (doc.paths) this.paths = doc.paths;
|
||||
|
||||
if (doc.createdAt) this.createdAt = doc.createdAt;
|
||||
if (doc.updatedAt) this.updatedAt = doc.updatedAt;
|
||||
}
|
||||
|
||||
async beforeCreate?(ctx?: ApolloContext | null): Promise<void>;
|
||||
async beforeUpdate?(ctx?: ApolloContext | null): Promise<void>;
|
||||
async beforeDelete?(ctx?: ApolloContext | null): Promise<void>;
|
||||
|
||||
async afterCreate?(changeStream: any): Promise<void>;
|
||||
async afterUpdate?(changeStream: any): Promise<void>;
|
||||
async afterDelete?(changeStream: any): Promise<void>;
|
||||
|
||||
/*
|
||||
*
|
||||
* Search Functions & Helpers
|
||||
*
|
||||
*/
|
||||
|
||||
searchEngine?(): any;
|
||||
searchText?(): any;
|
||||
/*
|
||||
*
|
||||
* Transform Functions
|
||||
*
|
||||
*/
|
||||
|
||||
abstract plainToClass(plain: any): Schema | Schema[];
|
||||
|
||||
/*
|
||||
*
|
||||
* DB Functions
|
||||
*
|
||||
*/
|
||||
|
||||
public async db(): Promise<Collection<SchemaDBInterface & IEngineSchema>> {
|
||||
return await (await DB.getInstance()).db.collection<SchemaDBInterface & IEngineSchema>(this.collectionName);
|
||||
}
|
||||
|
||||
generatePaths?(): EnginePathComponent[];
|
||||
|
||||
public getPaths(): EnginePathComponent[] {
|
||||
return this.generatePaths ? this.generatePaths() : this.paths ? this.paths : [];
|
||||
}
|
||||
|
||||
public getFullPath(): EnginePathComponent[] {
|
||||
const myPath = this.getPaths();
|
||||
myPath.push({
|
||||
ressourceModel: this.collectionName,
|
||||
ressourceId: this._id,
|
||||
});
|
||||
|
||||
return myPath;
|
||||
}
|
||||
|
||||
// ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
// ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
// ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
// ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
// ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
// ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
|
||||
public async getQuery(input: GetQueryInput<SchemaDBInterface>): Promise<any> {
|
||||
return getQuery(this, input);
|
||||
}
|
||||
public async getMany(input: GetManyInput<SchemaDBInterface>): Promise<Schema[]> {
|
||||
return getMany(this, input);
|
||||
}
|
||||
public async getCount(input: GetCountInput<SchemaDBInterface>): Promise<number> {
|
||||
return getCount(this, input);
|
||||
}
|
||||
public async getAll(input: GetAllInput<SchemaDBInterface>): Promise<Schema[]> {
|
||||
return getAll(this, input);
|
||||
}
|
||||
public async getOne(input: GetOneInput<SchemaDBInterface>): Promise<Schema> {
|
||||
let newInput;
|
||||
|
||||
if (!input.query) {
|
||||
newInput = { query: input };
|
||||
} else newInput = input;
|
||||
|
||||
return getOne(this, newInput);
|
||||
}
|
||||
|
||||
// ███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗
|
||||
// ████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║
|
||||
// ██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║██║ ██║██╔██╗ ██║
|
||||
// ██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║
|
||||
// ██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║
|
||||
// ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
||||
|
||||
public async saveOne(input: SaveOneInput<SchemaDBInterface>): Promise<Schema> {
|
||||
return saveOne(this, input);
|
||||
}
|
||||
public async updateOne(input: UpdateOneInput<SchemaDBInterface>): Promise<Schema> {
|
||||
return updateOne(this, input);
|
||||
}
|
||||
public async updateOneCustom(input: UpdateOneCustomInput<SchemaDBInterface>): Promise<Schema> {
|
||||
return updateOneCustom(this, input);
|
||||
}
|
||||
public async updateOneSubField(input: UpdateOneSubField<SchemaDBInterface>): Promise<Schema> {
|
||||
return updateOneSubField(this, input);
|
||||
}
|
||||
|
||||
public async deleteOne(input: DeleteOneInput<SchemaDBInterface>): Promise<Schema> {
|
||||
return deleteOne(this, input);
|
||||
}
|
||||
|
||||
public async saveMany(input: SaveManyInput<SchemaDBInterface>): Promise<Schema[]> {
|
||||
return saveMany(this, input);
|
||||
}
|
||||
|
||||
public async bulk(input: BulkInput<SchemaDBInterface, SchemaDB, Schema>): Promise<void> {
|
||||
return bulk(this, input);
|
||||
}
|
||||
|
||||
// ██████╗ ███████╗███╗ ██╗███████╗██████╗ ██╗ ██████╗███████╗
|
||||
// ██╔════╝ ██╔════╝████╗ ██║██╔════╝██╔══██╗██║██╔════╝██╔════╝
|
||||
// ██║ ███╗█████╗ ██╔██╗ ██║█████╗ ██████╔╝██║██║ ███████╗
|
||||
// ██║ ██║██╔══╝ ██║╚██╗██║██╔══╝ ██╔══██╗██║██║ ╚════██║
|
||||
// ╚██████╔╝███████╗██║ ╚████║███████╗██║ ██║██║╚██████╗███████║
|
||||
// ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝╚══════
|
||||
|
||||
public async getOneGeneric(id: string, ctx: ApolloContext): Promise<Schema> {
|
||||
return getOneGeneric(this, id, ctx);
|
||||
}
|
||||
public async getManyGeneric(query: any, pagination: GetArgs | undefined, ctx: ApolloContext | null): Promise<Schema[]> {
|
||||
return getManyGeneric(this, query, pagination, ctx);
|
||||
}
|
||||
|
||||
public async getCountGeneric(baseArguments: any, ctx: ApolloContext): Promise<number> {
|
||||
return getCountGeneric(this, baseArguments, ctx);
|
||||
}
|
||||
|
||||
public async getManyGenericWithArgs(baseArguments: any, ctx: ApolloContext | null): Promise<Schema[]> {
|
||||
return getManyGenericWithArgs(this, baseArguments, ctx);
|
||||
}
|
||||
}
|
||||
58
lib/seed/engine/EngineSchema.ts
Normal file
58
lib/seed/engine/EngineSchema.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { EnginePathComponent } from '@seed/interfaces/components';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { Field, ID, ObjectType, Authorized, InterfaceType } from 'type-graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class MetaBy {
|
||||
@Field(() => ID, { nullable: true })
|
||||
createdBy?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
updatedBy?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
deletedBy?: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class MetaPermissions {
|
||||
@Field(() => [String])
|
||||
r: string[];
|
||||
|
||||
@Authorized(AccountTypeEnum.admin)
|
||||
@Field(() => [String])
|
||||
w: string[];
|
||||
|
||||
@Authorized(AccountTypeEnum.admin)
|
||||
@Field(() => [String])
|
||||
d: string[];
|
||||
}
|
||||
|
||||
@InterfaceType()
|
||||
export abstract class IEngineSchema {
|
||||
@Field(() => ID)
|
||||
_id: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
organisationId?: string;
|
||||
|
||||
@Field(() => [EnginePathComponent], { nullable: true })
|
||||
paths?: EnginePathComponent[];
|
||||
|
||||
searchT?: string;
|
||||
|
||||
@Field(() => MetaBy, { nullable: true })
|
||||
by?: MetaBy;
|
||||
|
||||
@Field(() => MetaPermissions, { nullable: true })
|
||||
permissions: MetaPermissions;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
tagsIds?: string[];
|
||||
}
|
||||
40
lib/seed/engine/decorators/db.guard.ts
Normal file
40
lib/seed/engine/decorators/db.guard.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import DB from '@seed/services/database/DBService';
|
||||
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
|
||||
@ValidatorConstraint({ async: true })
|
||||
export class IsRefExistConstraint implements ValidatorConstraintInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
async validate(ressourceId: any, args: ValidationArguments & any) {
|
||||
// Validate only when in the DB object
|
||||
if (args.targetName.includes('DB')) {
|
||||
const obj = args.object as any;
|
||||
// Check what kind of id check
|
||||
if (obj.ressourceId && obj.ressourceModel) {
|
||||
|
||||
const res = await (await DB.getInstance()).db.collection(ModelCollectionEnum[obj.ressourceModel]).findOne({ _id: obj.ressourceId });
|
||||
if (!res) throw newError(4040, obj);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
args.hasValidated = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function IsRefExist(validationOptions?: ValidationOptions) {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
return function(object: Record<string, any>, propertyName: string) {
|
||||
registerDecorator({
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
constraints: [],
|
||||
validator: IsRefExistConstraint,
|
||||
});
|
||||
};
|
||||
}
|
||||
0
lib/seed/engine/decorators/pre.hooks.ts
Normal file
0
lib/seed/engine/decorators/pre.hooks.ts
Normal file
114
lib/seed/engine/genericResolvers/BaseEngineResolver.ts
Normal file
114
lib/seed/engine/genericResolvers/BaseEngineResolver.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { Query, Arg, Resolver, Ctx, Args, Mutation, ClassType } from 'type-graphql';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { EngineMiddleware, EngineMiddlewareInput } from '@seed/graphql/MiddlewareV2';
|
||||
import { EngineModel } from '../EngineModel';
|
||||
|
||||
export const createEngineQueryResolver = <U extends ClassType<any>, T extends ClassType<any>, ARGS extends ClassType<any>>(init: {
|
||||
domain: string;
|
||||
schemaName: U;
|
||||
modelName: T;
|
||||
argsType: ARGS;
|
||||
engineMiddleware: EngineMiddlewareInput;
|
||||
}): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseQueryResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => init.schemaName, { name: `${init.domain}GetOne` })
|
||||
@EngineMiddleware(init.engineMiddleware)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
const result = await model.getOneGeneric(id, ctx);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Query(() => [init.schemaName], { name: `${init.domain}GetMany` })
|
||||
@EngineMiddleware(init.engineMiddleware)
|
||||
async getMany(@Args(() => init.argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
return model.getManyGenericWithArgs(args, ctx);
|
||||
}
|
||||
|
||||
@Query(() => Number, { name: `${init.domain}GetCount` })
|
||||
@EngineMiddleware(init.engineMiddleware)
|
||||
async getCount(@Args(() => init.argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<number> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
return model.getCountGeneric(args, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseQueryResolver;
|
||||
};
|
||||
|
||||
export const createEngineMutationResolver = <
|
||||
U extends ClassType<any>,
|
||||
T extends ClassType<any>,
|
||||
NEW extends ClassType<any>,
|
||||
EDIT extends ClassType<any>
|
||||
>(init: {
|
||||
domain: string;
|
||||
schemaName: U;
|
||||
modelName: T;
|
||||
newInput: NEW;
|
||||
editInput: EDIT;
|
||||
engineMiddleware: EngineMiddlewareInput;
|
||||
validate?: boolean;
|
||||
}): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseMutationResolver {
|
||||
/*
|
||||
███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
||||
████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
||||
██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
||||
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
||||
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||
*/
|
||||
@Mutation(() => init.schemaName, { name: `${init.domain}AddOne` })
|
||||
@EngineMiddleware({ ...init.engineMiddleware, validations: [{ schema: init.newInput }] })
|
||||
async addOne(@Arg('input', () => init.newInput) input: NEW, @Ctx() ctx: ApolloContext): Promise<U> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
return await await model.saveOne({ newData: input, ctx });
|
||||
}
|
||||
|
||||
@Mutation(() => init.schemaName, { name: `${init.domain}EditOne` })
|
||||
@EngineMiddleware({ ...init.engineMiddleware, validations: [{ schema: init.editInput }] })
|
||||
async editOne(@Arg('id') id: string, @Arg('input', () => init.editInput) input: EDIT, @Ctx() ctx: ApolloContext): Promise<U> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
return await await model.updateOne({
|
||||
query: { _id: id },
|
||||
newData: input,
|
||||
ctx: ctx,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => init.schemaName, { name: `${init.domain}DeleteOne` })
|
||||
@EngineMiddleware({ ...init.engineMiddleware })
|
||||
async deleteOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<U> {
|
||||
const model = new init.modelName() as EngineModel<any, any, any>;
|
||||
return await await model.deleteOne({
|
||||
query: { _id: id },
|
||||
ctx: ctx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return BaseMutationResolver;
|
||||
};
|
||||
76
lib/seed/engine/utils/__interface.ts
Normal file
76
lib/seed/engine/utils/__interface.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { GetArgs } from '@seed/graphql/Request';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { FilterQuery, UpdateQuery } from 'mongodb';
|
||||
import { EngineModel } from '../EngineModel';
|
||||
import { IEngineSchema } from '../EngineSchema';
|
||||
|
||||
export interface GetQueryInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface GetManyInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
pagination?: GetArgs;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
export interface GetAllInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
export interface GetCountInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface GetOneInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface SaveOneInput<SchemaDBInterface> {
|
||||
newData?: SchemaDBInterface & Partial<IEngineSchema>;
|
||||
additionnalData?: Partial<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
upsert?: boolean;
|
||||
}
|
||||
|
||||
export interface SaveManyInput<SchemaDBInterface> {
|
||||
models: EngineModel<SchemaDBInterface, any, any>[];
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface BulkInput<SchemaDBInterface, SchemaDB, Schema> {
|
||||
inserts?: EngineModel<SchemaDBInterface, SchemaDB, Schema>[];
|
||||
updates?: {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
newData?: Partial<SchemaDBInterface & IEngineSchema>;
|
||||
updateRequest?: UpdateQuery<SchemaDBInterface & IEngineSchema>;
|
||||
}[];
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface UpdateOneInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
newData: Partial<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface UpdateOneCustomInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
updateRequest: UpdateQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface UpdateOneSubField<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
fieldName: keyof SchemaDBInterface;
|
||||
subPath?: string;
|
||||
fieldValue: any;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
|
||||
export interface DeleteOneInput<SchemaDBInterface> {
|
||||
query: FilterQuery<SchemaDBInterface & IEngineSchema>;
|
||||
ctx?: ApolloContext | null;
|
||||
}
|
||||
721
lib/seed/engine/utils/crud.utils.ts
Normal file
721
lib/seed/engine/utils/crud.utils.ts
Normal file
@ -0,0 +1,721 @@
|
||||
import { parsePaginationOptions } from '@lib/seed/helpers/Request';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { validateOrReject } from 'class-validator';
|
||||
import { EngineAccessService } from '../EngineAccessService';
|
||||
import { EngineModel } from '../EngineModel';
|
||||
import { IEngineSchema } from '../EngineSchema';
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import {
|
||||
BulkInput,
|
||||
DeleteOneInput,
|
||||
GetAllInput,
|
||||
GetCountInput,
|
||||
GetManyInput,
|
||||
GetOneInput,
|
||||
GetQueryInput,
|
||||
SaveManyInput,
|
||||
SaveOneInput,
|
||||
UpdateOneCustomInput,
|
||||
UpdateOneInput,
|
||||
UpdateOneSubField,
|
||||
} from './__interface';
|
||||
import DB from '@lib/seed/services/database/DBService';
|
||||
import { createChangeStream } from './streams/stream.service';
|
||||
import { StreamOperationType } from './streams/schemas/stream.components';
|
||||
import { BulkWriteOperation, ObjectId } from 'mongodb';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
import { clog } from '@seed/helpers/Utils';
|
||||
|
||||
export function getQuery<SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: GetQueryInput<SchemaDBInterface>,
|
||||
): any {
|
||||
let finalQuery: any;
|
||||
const { query, ctx } = input;
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
// We add the permission (on r field) to make sure that kind of user has access to the ressource
|
||||
if (!ctx.ctx.noPermissionCheck) finalQuery = EngineAccessService.addPermissionToQuery(account, 'get', { ...query });
|
||||
|
||||
// We check if it comes from an organisation
|
||||
// Check of the user's correct authorisation on that organisation comes from the middleware
|
||||
if (!ctx.ctx.noOrganisationCheck) {
|
||||
if (organisationId) {
|
||||
if (model.collectionName == 'accounts') finalQuery['organisationIds'] = organisationId;
|
||||
else finalQuery['organisationId'] = { $eq: organisationId };
|
||||
} else {
|
||||
// If admin & public query -> do not add model condition and let search every records)
|
||||
if (account && account.types && !account.types.includes(AccountTypeEnum.public) && !account.types.includes(AccountTypeEnum.admin)) {
|
||||
finalQuery['organisationId'] = { $exists: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalQuery = { ...query };
|
||||
}
|
||||
|
||||
return finalQuery;
|
||||
}
|
||||
|
||||
export const getMany = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: GetManyInput<SchemaDBInterface>,
|
||||
): Promise<Schema[]> => {
|
||||
let { pagination } = input;
|
||||
|
||||
const finalQuery = getQuery(model, input);
|
||||
|
||||
if (!pagination) pagination = { limit: 100, skip: 0 };
|
||||
if (!pagination.sort) pagination.sort = model.defaultSort;
|
||||
const paginationOption = parsePaginationOptions(pagination);
|
||||
|
||||
try {
|
||||
console.log('search request', JSON.stringify(finalQuery, null, 2));
|
||||
const dbResults = await (await model.db()).find(finalQuery, paginationOption).toArray();
|
||||
return model.plainToClass(dbResults) as any;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getCount = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: GetCountInput<SchemaDBInterface>,
|
||||
): Promise<number> => {
|
||||
const finalQuery = getQuery(model, input);
|
||||
|
||||
try {
|
||||
const result = (await (await model.db()).countDocuments(finalQuery)) as any;
|
||||
return result;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAll = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: GetAllInput<SchemaDBInterface>,
|
||||
): Promise<any> => {
|
||||
const finalQuery = getQuery(model, input);
|
||||
try {
|
||||
clog(finalQuery);
|
||||
const res = await (await model.db()).find(finalQuery).toArray();
|
||||
return model.plainToClass(res);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getOne = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: GetOneInput<SchemaDBInterface>,
|
||||
): Promise<Schema> => {
|
||||
const { query, ctx } = input;
|
||||
|
||||
let dataToReturn;
|
||||
if (query._id) {
|
||||
const _id = query._id as any;
|
||||
if (_id.match(/^[0-9a-fA-F]{24}$/)) {
|
||||
query._id = new ObjectId(_id) as any;
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
const doc = await (await model.db()).findOne(query);
|
||||
if (!doc) throw await newError(4041, { id: query._id, model: model.collectionName });
|
||||
|
||||
dataToReturn = model.plainToClass(doc) as any;
|
||||
model.set(dataToReturn);
|
||||
|
||||
if (!ctx.ctx.noOrganisationCheck) {
|
||||
if (organisationId) {
|
||||
if (model.collectionName != 'accounts') EngineAccessService.checkOrganisationPermissions(model, organisationId);
|
||||
}
|
||||
}
|
||||
if (!ctx.ctx.noPermissionCheck) EngineAccessService.checkPermissions(dataToReturn, account, 'r');
|
||||
} else {
|
||||
const doc = await (await model.db()).findOne(query);
|
||||
if (!doc) throw await newError(4041, { id: query._id, model: model.collectionName });
|
||||
|
||||
dataToReturn = model.plainToClass(doc) as any;
|
||||
model.set(dataToReturn);
|
||||
}
|
||||
|
||||
return model.get();
|
||||
};
|
||||
|
||||
export const saveOne = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: SaveOneInput<SchemaDBInterface>,
|
||||
): Promise<Schema> => {
|
||||
const { newData, additionnalData, upsert } = input;
|
||||
let ctx = input.ctx;
|
||||
|
||||
if (newData) model.set(newData);
|
||||
if (additionnalData) model.set(additionnalData);
|
||||
|
||||
// Set the path
|
||||
model.set({ paths: model.getPaths() });
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
EngineAccessService.addPermissions(model, ['r', 'w', 'd'], [account._id]);
|
||||
|
||||
// TODO : Check create permissions
|
||||
|
||||
if (organisationId) {
|
||||
model.set({ organisationId });
|
||||
}
|
||||
|
||||
if (model.by) model.by.createdBy = account._id;
|
||||
else {
|
||||
model.by = {
|
||||
createdBy: account._id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// if (!ctx) ctx = await createApolloContext();
|
||||
|
||||
try {
|
||||
if (model.beforeCreate) await model.beforeCreate(ctx);
|
||||
await validateOrReject(model.dbData as any);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const dataToSave = model.get() as any;
|
||||
if (model.searchText) dataToSave.searchT = model.searchText();
|
||||
|
||||
if (!model._id) model._id = uuid();
|
||||
|
||||
delete dataToSave._id;
|
||||
|
||||
try {
|
||||
const result = await (await model.db()).findOneAndUpdate(
|
||||
{ _id: model._id } as any,
|
||||
{
|
||||
$set: dataToSave,
|
||||
$setOnInsert: {
|
||||
_id: model._id,
|
||||
},
|
||||
} as any,
|
||||
{ upsert: upsert ? upsert : true, returnOriginal: false },
|
||||
);
|
||||
|
||||
if (result.ok == 0) throw await newError(1000, result);
|
||||
|
||||
await createChangeStream(model, StreamOperationType.insert);
|
||||
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw await newError(1000, error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOne = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: UpdateOneInput<SchemaDBInterface>,
|
||||
): Promise<Schema> => {
|
||||
try {
|
||||
const { query, newData, ctx } = input;
|
||||
|
||||
if (!model._id) await model.getOne(input);
|
||||
if (newData) model.set(newData);
|
||||
|
||||
model.set({ paths: model.getPaths() });
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
// Check if doc exists & correct permissions
|
||||
|
||||
if (model.by) model.by.updatedBy = account._id;
|
||||
else {
|
||||
model.by = {
|
||||
updatedBy: account._id,
|
||||
};
|
||||
}
|
||||
|
||||
EngineAccessService.checkPermissions(model, account, 'w');
|
||||
}
|
||||
|
||||
if (model.beforeUpdate) await model.beforeUpdate(ctx);
|
||||
|
||||
const dataToSave = model.get() as any;
|
||||
if (model.searchText) dataToSave.searchT = model.searchText();
|
||||
|
||||
const result = await (await model.db()).findOneAndUpdate(query, { $set: dataToSave }, { upsert: false, returnOriginal: false });
|
||||
if (result.ok != 1) throw await newError(1000, result);
|
||||
|
||||
model.set(result.value);
|
||||
|
||||
await createChangeStream(model, StreamOperationType.update);
|
||||
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateOneCustom = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: UpdateOneCustomInput<SchemaDBInterface>,
|
||||
): Promise<Schema> => {
|
||||
try {
|
||||
const { query, updateRequest, ctx } = input;
|
||||
if (!model._id) await model.getOne(input);
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
// Check if doc exists
|
||||
// dataBeforeChange = model.get();
|
||||
|
||||
EngineAccessService.checkPermissions(model, account, 'w');
|
||||
|
||||
if (model.by) model.by.updatedBy = account._id;
|
||||
else {
|
||||
model.by = {
|
||||
updatedBy: account._id,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate new data that will go into the DB
|
||||
// const validation = await model.validate();
|
||||
// if (!validation.success) return validation;
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
model.updatedAt = new Date();
|
||||
|
||||
const result = await (await model.db()).findOneAndUpdate(query, updateRequest, { upsert: false, returnOriginal: false });
|
||||
if (result.ok != 1) throw await newError(1000, result);
|
||||
|
||||
if (result.value) model.set(result.value);
|
||||
|
||||
await createChangeStream(model, StreamOperationType.update);
|
||||
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOne = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: DeleteOneInput<SchemaDBInterface>,
|
||||
): Promise<Schema> => {
|
||||
const { query, ctx } = input;
|
||||
try {
|
||||
if (!model._id) await model.getOne(input);
|
||||
if (model.beforeDelete) await model.beforeDelete(ctx);
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
// Check if doc exists & correct permissions
|
||||
EngineAccessService.checkPermissions(model, account, 'd');
|
||||
|
||||
if (model.by) model.by.deletedBy = account._id;
|
||||
else {
|
||||
model.by = {
|
||||
deletedBy: account._id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const result = await (await model.db()).findOneAndDelete(query);
|
||||
if (result.ok != 1) throw await newError(1001);
|
||||
|
||||
const deletedModel = '_deleted_' + model.collectionName;
|
||||
await (await DB.getInstance()).db.collection(deletedModel).insertOne(model.get());
|
||||
|
||||
await createChangeStream(model, StreamOperationType.delete);
|
||||
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveMany = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: SaveManyInput<SchemaDBInterface>,
|
||||
): Promise<Schema[]> => {
|
||||
const ctx = input.ctx;
|
||||
const models = input.models;
|
||||
|
||||
const dataToSaves: any[] = [];
|
||||
for (let index = 0; index < models.length; index++) {
|
||||
const element = models[index];
|
||||
|
||||
// Set the path
|
||||
element.set({ paths: element.getPaths() });
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
EngineAccessService.addPermissions(element, ['r', 'w', 'd'], [account._id]);
|
||||
|
||||
// TODO : Check create permissions
|
||||
|
||||
if (organisationId) {
|
||||
element.set({ organisationId });
|
||||
}
|
||||
if (element.by) element.by.createdBy = account._id;
|
||||
else {
|
||||
element.by = {
|
||||
createdBy: account._id,
|
||||
};
|
||||
}
|
||||
try {
|
||||
if (element.beforeCreate) await element.beforeCreate(ctx);
|
||||
await validateOrReject(element.dbData);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const dataToSave = element.get() as any;
|
||||
if (element.searchText) dataToSave.searchT = element.searchText();
|
||||
if (!element._id) dataToSave._id = uuid();
|
||||
|
||||
dataToSaves.push(dataToSave);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await (await model.db()).insertMany(dataToSaves, { ordered: true });
|
||||
|
||||
if (result.result.ok == 0) throw await newError(1000, result);
|
||||
|
||||
// await createChangeStream(model, StreamOperationType.insert);
|
||||
|
||||
return dataToSaves;
|
||||
} catch (error) {
|
||||
throw await newError(1000, error);
|
||||
}
|
||||
};
|
||||
|
||||
export const bulk = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: BulkInput<SchemaDBInterface, SchemaDB, Schema>,
|
||||
): Promise<void> => {
|
||||
const { inserts, updates, ctx } = input;
|
||||
|
||||
const bulkOps: BulkWriteOperation<SchemaDBInterface & IEngineSchema>[] = [];
|
||||
if (inserts) {
|
||||
inserts.forEach((element) => {
|
||||
bulkOps.push({
|
||||
insertOne: {
|
||||
document: element.get() as any,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (updates) {
|
||||
updates.forEach((element) => {
|
||||
if (element.newData)
|
||||
bulkOps.push({
|
||||
updateOne: {
|
||||
filter: element.query,
|
||||
update: { $set: element.newData },
|
||||
},
|
||||
});
|
||||
else if (element.updateRequest)
|
||||
bulkOps.push({
|
||||
updateOne: {
|
||||
filter: element.query,
|
||||
update: element.updateRequest,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const bOpsArray: BulkWriteOperation<SchemaDBInterface & IEngineSchema>[][] = [];
|
||||
if (bulkOps.length > 0) {
|
||||
const chunk = 1000;
|
||||
let i, j;
|
||||
|
||||
for (i = 0, j = bulkOps.length; i < j; i += chunk) {
|
||||
bOpsArray.push(bulkOps.slice(i, i + chunk));
|
||||
}
|
||||
} else bOpsArray.push(bulkOps);
|
||||
|
||||
try {
|
||||
for (let index = 0; index < bOpsArray.length; index++) {
|
||||
const element = bOpsArray[index];
|
||||
const res = await (await model.db()).bulkWrite(element);
|
||||
|
||||
console.log(res);
|
||||
}
|
||||
} catch (error) {
|
||||
throw await newError(1000, error);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
export const updateOneSubField = async <SchemaDBInterface, SchemaDB, Schema>(
|
||||
model: EngineModel<SchemaDBInterface, SchemaDB, Schema>,
|
||||
input: UpdateOneSubField<SchemaDBInterface>,
|
||||
): Promise<any> => {
|
||||
const { fieldName, fieldValue, subPath, query, ctx } = input;
|
||||
|
||||
let updatePath = fieldName as string;
|
||||
if (subPath) updatePath += subPath;
|
||||
try {
|
||||
const filterQ = {
|
||||
...query,
|
||||
};
|
||||
return await model.updateOneCustom({
|
||||
query: filterQ,
|
||||
updateRequest: {
|
||||
$set: {
|
||||
[`${updatePath}`]: fieldValue,
|
||||
} as any,
|
||||
},
|
||||
ctx,
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// public async saveMany(newDataArr: Partial<this>[], ctx: ApolloContext | null): Promise<any[]> {
|
||||
// // Initialize the change stream
|
||||
// const changeStreams: any[] = [];
|
||||
// const afterCreatePromises: any[] = [];
|
||||
|
||||
// const prehooksPromises = newDataArr.map(async (newData) => {
|
||||
// const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||
// if (ctx) {
|
||||
// const account = ctx.ctx.user;
|
||||
// const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
// addPermissions(clone, ['r', 'w', 'd'], [account._id]);
|
||||
|
||||
// if (organisationId) {
|
||||
// newData.organisationId = organisationId;
|
||||
// }
|
||||
|
||||
// newData.createdBy = account._id;
|
||||
// }
|
||||
|
||||
// clone.set(newData);
|
||||
|
||||
// if (!ctx) ctx = await createApolloContext();
|
||||
|
||||
// if (clone.prehook) await clone.prehook();
|
||||
// if (clone.beforeCreate) await clone.beforeCreate(ctx);
|
||||
|
||||
// const { _id, ...dataToSave } = clone.get();
|
||||
// const savedId = _id ? _id : uuid();
|
||||
// clone.set({ _id: savedId });
|
||||
|
||||
// const changeStreamId = uuid();
|
||||
// const changeStreamData = {
|
||||
// _id: changeStreamId,
|
||||
// operation: StreamOperationType.insert,
|
||||
// collection: clone.collectionName,
|
||||
// documentKey: clone._id,
|
||||
// insertedValues: clone.get(),
|
||||
// hookStatus: PostHookStatus.new,
|
||||
// createdAt: new Date(),
|
||||
// updateAt: new Date(),
|
||||
// };
|
||||
|
||||
// changeStreams.push(changeStreamData);
|
||||
|
||||
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// afterCreatePromises.push(async () => {
|
||||
// if (clone.afterCreate) {
|
||||
// const res = await asyncHook(changeStreamData);
|
||||
|
||||
// if (res) {
|
||||
// await clone.afterCreate(changeStreamData);
|
||||
|
||||
// await (await DB.getInstance()).db
|
||||
// .collection('stream.changes')
|
||||
// .updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// return clone.get();
|
||||
// });
|
||||
|
||||
// try {
|
||||
// const dataArr = await Promise.all(prehooksPromises);
|
||||
// await (await this.db()).insertMany(dataArr, { ordered: false });
|
||||
// await (await DB.getInstance()).db.collection('stream.changes').insertMany(changeStreams, { ordered: false });
|
||||
// await promiseAll(afterCreatePromises);
|
||||
// return dataArr;
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// throw await newError('db.error', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
// ██████╗ ███████╗██████╗ ███╗ ███╗██╗███████╗███████╗██╗ ██████╗ ███╗ ██╗███████╗
|
||||
// ██╔══██╗██╔════╝██╔══██╗████╗ ████║██║██╔════╝██╔════╝██║██╔═══██╗████╗ ██║██╔════╝
|
||||
// ██████╔╝█████╗ ██████╔╝██╔████╔██║██║███████╗███████╗██║██║ ██║██╔██╗ ██║███████╗
|
||||
// ██╔═══╝ ██╔══╝ ██╔══██╗██║╚██╔╝██║██║╚════██║╚════██║██║██║ ██║██║╚██╗██║╚════██║
|
||||
// ██║ ███████╗██║ ██║██║ ╚═╝ ██║██║███████║███████║██║╚██████╔╝██║ ╚████║███████║
|
||||
// ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||
|
||||
// public async updateQueryWithPermission(query: FilterQuery<this>, ctx: ApolloContext): Promise<FilterQuery<this>> {
|
||||
// const account = ctx.ctx.user;
|
||||
// const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
// // We add the permission (on r field) to make sure that kind of user has access to the ressource
|
||||
// const finalQuery = addPermissionToQuery(account, 'get', { ...query });
|
||||
|
||||
// // We check if it comes from an organisation
|
||||
// // Check of the user's correct authorisation on that organisation comes from the middleware
|
||||
// if (organisationId) {
|
||||
// finalQuery['organisationId'] = { $eq: organisationId };
|
||||
// } else {
|
||||
// finalQuery['organisationId'] = { $exists: false };
|
||||
// }
|
||||
|
||||
// return finalQuery;
|
||||
// }
|
||||
|
||||
// ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗
|
||||
// ██╔════╝██║ ██║██╔══██╗ ██╔══██╗██╔════╝██╔════╝██╔════╝
|
||||
// ███████╗██║ ██║██████╔╝█████╗██████╔╝█████╗ ███████╗███████╗
|
||||
// ╚════██║██║ ██║██╔══██╗╚════╝██╔══██╗██╔══╝ ╚════██║╚════██║
|
||||
// ███████║╚██████╔╝██████╔╝ ██║ ██║███████╗███████║███████║
|
||||
// ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝
|
||||
|
||||
// public async addOneSubModel<SUB>(
|
||||
// parentQ: { _id: string },
|
||||
// subModelRefField: string,
|
||||
// subModelFieldName: keyof this,
|
||||
// subModel: SUB,
|
||||
// subModelInput: any,
|
||||
// ctx: ApolloContext | null,
|
||||
// ): Promise<SUB> {
|
||||
// try {
|
||||
// subModelInput[subModelRefField] = parentQ._id;
|
||||
// const subM = await subModel.saveOne(subModelInput, ctx);
|
||||
// await this.updateOneCustom(parentQ as any, { $push: { [`${subModelFieldName}`]: subM._id } } as any, null);
|
||||
// return subM;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async editOneSubModel<SUB extends BaseGraphModel>(
|
||||
// subModel: SUB,
|
||||
// subModelId: string,
|
||||
// subModelInput: any,
|
||||
// ctx: ApolloContext | null,
|
||||
// ): Promise<SUB> {
|
||||
// try {
|
||||
// // No need to check if belongs to the correct organisation, the middleware does that
|
||||
// const subM = await subModel.updateOne({ _id: subModelId } as any, subModelInput, ctx);
|
||||
// return subM;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async deleteOneSubModel<SUB extends BaseGraphModel>(
|
||||
// parentQ: FilterQuery<this>,
|
||||
// subModelFieldName: keyof this,
|
||||
// subModel: SUB,
|
||||
// subModelId: string,
|
||||
// ctx: ApolloContext | null,
|
||||
// ): Promise<SUB> {
|
||||
// try {
|
||||
// const subM = await subModel.deleteOne({ _id: subModelId } as any, ctx);
|
||||
// await this.updateOneCustom(parentQ, { $pull: { [`${subModelFieldName}`]: subM._id } } as any, null);
|
||||
// return subM;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public getOneSubRessource(subRessoureFieldName: keyof this | string, subRessoureId: string): any | undefined {
|
||||
// try {
|
||||
// const subress = _.get(this, subRessoureFieldName);
|
||||
// if (subress) {
|
||||
// const ind = _.findIndex(subress, { _id: subRessoureId });
|
||||
// if (ind !== -1) return subress[ind];
|
||||
// }
|
||||
// return;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public getDefaultSubRessource(subRessoureFieldName: keyof this | string): any | undefined {
|
||||
// try {
|
||||
// const subress = _.get(this, subRessoureFieldName);
|
||||
// if (subress) {
|
||||
// const ind = _.findIndex(subress, { default: true });
|
||||
// if (ind !== -1) return subress[ind];
|
||||
// return subress[0];
|
||||
// }
|
||||
// return;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async addOneSubRessource(
|
||||
// parentQ: { _id: string },
|
||||
// subRessoureFieldName: keyof this | string,
|
||||
// subRessoure: any,
|
||||
// ctx: ApolloContext | null,
|
||||
// ): Promise<any> {
|
||||
// try {
|
||||
// if (subRessoure.default && _.get(this, subRessoureFieldName)) {
|
||||
// // Remove all default from others
|
||||
// await this.updateOneCustom(
|
||||
// { _id: this._id } as any,
|
||||
// {
|
||||
// $set: { [`${subRessoureFieldName}.$[].default`]: false } as any,
|
||||
// },
|
||||
// ctx,
|
||||
// );
|
||||
// } else subRessoure.default = false;
|
||||
|
||||
// // Generate ID if not in the model
|
||||
// if (!subRessoure._id) subRessoure._id = uuid();
|
||||
// await this.updateOneCustom(parentQ as any, { $push: { [`${subRessoureFieldName}`]: subRessoure } } as any, ctx);
|
||||
// return subRessoure;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async deleteOneSubRessource(
|
||||
// parentQ: { _id: string },
|
||||
// subRessoureFieldName: keyof this | string,
|
||||
// subRessoureId: string,
|
||||
// ctx: ApolloContext | null,
|
||||
// ): Promise<string> {
|
||||
// try {
|
||||
// if (!this.getOneSubRessource(subRessoureFieldName, subRessoureId)) throw await newError('notFound', '400');
|
||||
|
||||
// const filterQ = {
|
||||
// ...parentQ,
|
||||
// [`${subRessoureFieldName}._id`]: subRessoureId,
|
||||
// };
|
||||
|
||||
// await this.updateOneCustom(filterQ as any, { $pull: { [`${subRessoureFieldName}`]: { _id: subRessoureId } } } as any, ctx);
|
||||
// return subRessoureId;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
118
lib/seed/engine/utils/service.utils.ts
Normal file
118
lib/seed/engine/utils/service.utils.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { GetArgs } from '@lib/seed/graphql/Request';
|
||||
import { ApolloContext } from '@lib/seed/interfaces/context';
|
||||
|
||||
import { baseSearchFunction } from '@lib/seed/services/database/DBRequestService';
|
||||
import { EngineModel } from '../EngineModel';
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
export const getOneGeneric = async (model: any, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
return await (model as EngineModel<any, any, any>).getOne({ query: { _id: id }, ctx });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export const getManyGeneric = async (model: any, query: any, pagination: GetArgs | undefined, ctx: ApolloContext | null): Promise<any> => {
|
||||
return baseSearchFunction({
|
||||
model: model,
|
||||
query: query,
|
||||
pagination: pagination,
|
||||
engine: true,
|
||||
ctx: ctx,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCountGeneric = async (model: any, baseArguments: any, ctx: ApolloContext): Promise<any> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { pagination, ...args } = baseArguments;
|
||||
|
||||
const result = await baseSearchFunction({
|
||||
model: model,
|
||||
query: args,
|
||||
count: true,
|
||||
engine: true,
|
||||
ctx: ctx,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getManyGenericWithArgs = async (model: any, baseArguments: any, ctx: ApolloContext | null): Promise<any> => {
|
||||
try {
|
||||
const { pagination, ...args } = baseArguments;
|
||||
return baseSearchFunction({
|
||||
model: model,
|
||||
query: args,
|
||||
pagination: pagination,
|
||||
engine: true,
|
||||
ctx: ctx,
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// export const editOneGeneric = async <T>(model: T, id: string, body: Partial<T>, ctx: ApolloContext): Promise<T> => {
|
||||
// try {
|
||||
// await model as EngineModel<any,any,any>).updateOne({ _id: id } as any, body, ctx);
|
||||
// return model as EngineModel<any,any,any>).get();
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const deleteOneGeneric = async <T>(model: T, id: string, ctx: ApolloContext): Promise<T> => {
|
||||
// try {
|
||||
// await model as EngineModel<any,any,any>).deleteOne({ _id: id } as any, ctx);
|
||||
// return model as EngineModel<any,any,any>).get();
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const permissionsAddOne = async (model: any, id: string, input: NewPermissionInput, ctx: ApolloContext): Promise<any> => {
|
||||
// try {
|
||||
// await model as EngineModel<any,any,any>).updateOneCustom({ _id: id }, { $addToSet: { [`${input.permissionType}`]: input.permission } }, ctx);
|
||||
// return model as EngineModel<any,any,any>).get();
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const permissionsRemoveOne = async (model: any, id: string, input: NewPermissionInput, ctx: ApolloContext): Promise<any> => {
|
||||
// try {
|
||||
// await model as EngineModel<any,any,any>).updateOneCustom({ _id: id }, { $pull: { [`${input.permissionType}`]: input.permission } }, ctx);
|
||||
// return model as EngineModel<any,any,any>).get();
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const publishOneGeneric = async <T>(model: T, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
// try {
|
||||
// const missingInputs: string[] = [];
|
||||
// // TODO THE CHECK OF THE INPUTS
|
||||
|
||||
// if (missingInputs.length > 0) throw newError('missingInput', '400', missingInputs);
|
||||
|
||||
// return editOneGeneric(model, id, { published: true, publicationDate: new Date() } as any, ctx);
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const unpublishOneGeneric = async <T>(model: T, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
// try {
|
||||
// return editOneGeneric(model, id, { published: false } as any, ctx);
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// };
|
||||
41
lib/seed/engine/utils/streams/schemas/stream.components.ts
Normal file
41
lib/seed/engine/utils/streams/schemas/stream.components.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { registerEnumType } from 'type-graphql';
|
||||
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
export enum StreamOperationType {
|
||||
insert = 'insert',
|
||||
delete = 'delete',
|
||||
replace = 'replace',
|
||||
update = 'update',
|
||||
hook = 'hook',
|
||||
}
|
||||
registerEnumType(StreamOperationType, {
|
||||
name: 'StreamOperationType',
|
||||
});
|
||||
|
||||
export enum PostHookStatus {
|
||||
new = 'new',
|
||||
inProcess = 'inProcess',
|
||||
completed = 'completed',
|
||||
error = 'error',
|
||||
noAction = 'noAction',
|
||||
}
|
||||
registerEnumType(PostHookStatus, {
|
||||
name: 'PostHookStatus',
|
||||
});
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
11
lib/seed/engine/utils/streams/schemas/stream.schema.input.ts
Normal file
11
lib/seed/engine/utils/streams/schemas/stream.schema.input.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { GetManyArgs } from '@seed/graphql/Request';
|
||||
import { Field, ArgsType } from 'type-graphql';
|
||||
|
||||
@ArgsType()
|
||||
export class StreamArgs extends GetManyArgs {
|
||||
@Field({ nullable: true })
|
||||
documentKey?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
hookStatus?: string;
|
||||
}
|
||||
50
lib/seed/engine/utils/streams/schemas/stream.schema.ts
Normal file
50
lib/seed/engine/utils/streams/schemas/stream.schema.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { AsyncHooksService } from '@lib/__hooks';
|
||||
import { IEngineSchema, MetaBy, MetaPermissions } from '@seed/engine/EngineSchema';
|
||||
import { StreamOperationType, PostHookStatus } from '@seed/services/change-stream/change-stream.components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { InputType, ObjectType, Field } from 'type-graphql';
|
||||
|
||||
@ObjectType()
|
||||
@InputType()
|
||||
export class StreamBaseSchema {
|
||||
@Field(() => StreamOperationType)
|
||||
operation: StreamOperationType;
|
||||
|
||||
@Field(() => String)
|
||||
collection: ModelCollectionEnum | keyof AsyncHooksService | string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
documentKey?: string;
|
||||
|
||||
@Field(() => PostHookStatus)
|
||||
hookStatus: PostHookStatus;
|
||||
|
||||
@Field({ nullable: true })
|
||||
logMessage?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
insertedValues?: any;
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
updatedValues?: any;
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
beforeUpdateValues?: any;
|
||||
|
||||
logInfo?: any;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class StreamDBInterfaceSchema extends StreamBaseSchema {}
|
||||
|
||||
@ObjectType()
|
||||
export class StreamDBSchema extends StreamDBInterfaceSchema {}
|
||||
|
||||
@ObjectType({ implements: IEngineSchema })
|
||||
export class StreamSchema extends StreamDBSchema implements IEngineSchema {
|
||||
_id: string;
|
||||
organisationId?: string | undefined;
|
||||
by?: MetaBy | undefined;
|
||||
permissions: MetaPermissions;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
49
lib/seed/engine/utils/streams/stream.model.ts
Normal file
49
lib/seed/engine/utils/streams/stream.model.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { ObjectType } from 'type-graphql';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { StreamDBInterfaceSchema, StreamDBSchema, StreamSchema } from './schemas/stream.schema';
|
||||
import { EngineModel } from '@seed/engine/EngineModel';
|
||||
import { IEngineSchema } from '@seed/engine/EngineSchema';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { CustomSearchEngine } from '@seed/services/database/DBRequestService';
|
||||
import { StreamArgs } from './schemas/stream.schema.input';
|
||||
|
||||
const permissions: Permission = {
|
||||
c: [AccountTypeEnum.admin],
|
||||
r: [AccountTypeEnum.admin],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
};
|
||||
|
||||
@ObjectType()
|
||||
export default class StreamEngineModel extends EngineModel<StreamDBInterfaceSchema, StreamDBSchema, StreamSchema> {
|
||||
public constructor(input?: StreamDBInterfaceSchema & Partial<IEngineSchema>) {
|
||||
const dataInit = plainToClass(StreamDBSchema, input || {});
|
||||
super({
|
||||
// ...init,
|
||||
collectionName: ModelCollectionEnum.streams,
|
||||
permissions: permissions,
|
||||
dataInit,
|
||||
});
|
||||
}
|
||||
|
||||
plainToClass(plain: any): StreamSchema | StreamSchema[] {
|
||||
return plainToClass(StreamSchema, plain);
|
||||
}
|
||||
|
||||
searchEngine(): CustomSearchEngine<StreamArgs> {
|
||||
const sEngine: CustomSearchEngine<StreamArgs> = {
|
||||
documentKey: {
|
||||
operation: '$eq',
|
||||
},
|
||||
hookStatus: {
|
||||
operation: '$eq',
|
||||
},
|
||||
};
|
||||
|
||||
return sEngine;
|
||||
}
|
||||
}
|
||||
0
lib/seed/engine/utils/streams/stream.resolver.ts
Normal file
0
lib/seed/engine/utils/streams/stream.resolver.ts
Normal file
84
lib/seed/engine/utils/streams/stream.service.ts
Normal file
84
lib/seed/engine/utils/streams/stream.service.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { AsyncHooksService } from '@lib/__hooks';
|
||||
import { EngineModel } from '@seed/engine/EngineModel';
|
||||
import { IEngineSchema } from '@seed/engine/EngineSchema';
|
||||
import DB from '@seed/services/database/DBService';
|
||||
import { asyncHook } from '@seed/services/hooks/hooks.decorator';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { StreamOperationType, PostHookStatus } from './schemas/stream.components';
|
||||
import { StreamDBInterfaceSchema } from './schemas/stream.schema';
|
||||
|
||||
export const createChangeStream = async <T extends EngineModel<any, any, any>>(model: T, operation: StreamOperationType): Promise<void> => {
|
||||
let operationFunction;
|
||||
|
||||
const changeStreamId = uuid();
|
||||
const base = {
|
||||
_id: changeStreamId,
|
||||
operation,
|
||||
collection: model.collectionName,
|
||||
documentKey: model._id,
|
||||
hookStatus: PostHookStatus.new,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
switch (operation) {
|
||||
case StreamOperationType.insert:
|
||||
(base as any).insertedValues = model.get();
|
||||
operationFunction = 'afterCreate';
|
||||
break;
|
||||
case StreamOperationType.update:
|
||||
case StreamOperationType.replace:
|
||||
(base as any).updatedValues = model.get();
|
||||
operationFunction = 'afterUpdate';
|
||||
break;
|
||||
case StreamOperationType.delete:
|
||||
(base as any).updatedValues = model.get();
|
||||
operationFunction = 'afterDelete';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const dataToSave: StreamDBInterfaceSchema & IEngineSchema = {
|
||||
...base,
|
||||
permissions: {
|
||||
r: [AccountTypeEnum.admin],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
},
|
||||
};
|
||||
await (await DB.getInstance()).db.collection(ModelCollectionEnum['streams']).insertOne(dataToSave);
|
||||
|
||||
if (operationFunction && model[operationFunction] && (await asyncHook(dataToSave))) {
|
||||
await model[operationFunction]();
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection(ModelCollectionEnum['streams'])
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
};
|
||||
|
||||
export const createAsyncStream = async <T extends EngineModel<any, any, any>>(collection: keyof AsyncHooksService, data: any): Promise<void> => {
|
||||
const changeStreamId = uuid();
|
||||
const base = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.hook,
|
||||
collection,
|
||||
insertedValues: data,
|
||||
hookStatus: PostHookStatus.new,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const dataToSave: StreamDBInterfaceSchema & IEngineSchema = {
|
||||
...base,
|
||||
permissions: {
|
||||
r: [AccountTypeEnum.admin],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
},
|
||||
};
|
||||
await (await DB.getInstance()).db.collection(ModelCollectionEnum['streams']).insertOne(dataToSave);
|
||||
};
|
||||
9
lib/seed/examples/__collections.ts
Normal file
9
lib/seed/examples/__collections.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { registerEnumType } from 'type-graphql';
|
||||
|
||||
export enum ModelCollectionEnum {
|
||||
'streams' = 'streams',
|
||||
accounts = 'accounts',
|
||||
}
|
||||
registerEnumType(ModelCollectionEnum, {
|
||||
name: 'ModelLoadersEnum',
|
||||
});
|
||||
37
lib/seed/examples/__cronjobs/serverless-cron.yaml
Normal file
37
lib/seed/examples/__cronjobs/serverless-cron.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
app: ${file(./package.json):name}
|
||||
service: cronjob
|
||||
package:
|
||||
individually: false
|
||||
provider:
|
||||
name: aws
|
||||
stage: ${opt:stage,'dev'}
|
||||
runtime: nodejs12.x
|
||||
environment:
|
||||
AWS_compute: ${self:service}-${self:provider.stage}-compute
|
||||
iamRoleStatements:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: '*'
|
||||
plugins:
|
||||
- serverless-webpack
|
||||
custom:
|
||||
stage: ${opt:stage, self:provider.stage}
|
||||
webpack:
|
||||
webpackConfig: './lib/seed/webpack.config.js' # Name of webpack configuration file
|
||||
includeModules:
|
||||
forceExclude:
|
||||
- aws-sdk
|
||||
- puppeteer
|
||||
|
||||
functions:
|
||||
treatRemainingDaysCron:
|
||||
handler: lib/__cronjobs/handler.cron1
|
||||
timeout: 300 # optional, in seconds, default is 6
|
||||
events:
|
||||
- schedule: rate(1 day)
|
||||
jobNotificationsCron:
|
||||
handler: lib/__cronjobs/handler.cron2
|
||||
timeout: 300
|
||||
events:
|
||||
- schedule: rate(1 day)
|
||||
8
lib/seed/examples/__hooks/functions/example.hooks.ts
Normal file
8
lib/seed/examples/__hooks/functions/example.hooks.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { promiseAll, sleep } from '@seed/helpers/Utils';
|
||||
|
||||
export const afterCheckoutEvent = async (data: any, ctx: ApolloContext): Promise<void> => {
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
await promiseAll(promises);
|
||||
};
|
||||
35
lib/seed/examples/__hooks/index.ts
Normal file
35
lib/seed/examples/__hooks/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { asyncHookDecorator } from '@seed/services/hooks/hooks.decorator';
|
||||
import { args } from 'chrome-aws-lambda';
|
||||
import { afterCheckoutEvent } from './functions/example.hooks';
|
||||
import { afterOrderCancel, afterOrdersMarkOneAsPaid, afterOrdersMarkOneAsUnPaid } from './functions/orders.hooks';
|
||||
|
||||
@asyncHookDecorator()
|
||||
export class AsyncHooksService {
|
||||
/* [MODULES - SHOP] */
|
||||
|
||||
public async afterCheckout(...args: any): Promise<any> {
|
||||
console.log('[NO ACTION] - afterCheckout');
|
||||
}
|
||||
|
||||
public async afterOrderCancel(...args: Parameters<typeof afterOrderCancel>): Promise<any> {
|
||||
return afterOrderCancel(...args);
|
||||
}
|
||||
|
||||
public async afterOrdersMarkOneAsPaid(...args: Parameters<typeof afterOrderCancel>): Promise<any> {
|
||||
return afterOrdersMarkOneAsPaid(...args);
|
||||
}
|
||||
|
||||
public async afterOrdersMarkOneAsUnPaid(...args: Parameters<typeof afterOrderCancel>): Promise<any> {
|
||||
return afterOrdersMarkOneAsUnPaid(...args);
|
||||
}
|
||||
|
||||
public async afterOrdersReimbursed(...args: Parameters<typeof afterOrderCancel>): Promise<any> {
|
||||
console.log('[NO ACTION] - afterOrdersReimbursed');
|
||||
}
|
||||
|
||||
/* [MODULES - EVENTS] */
|
||||
|
||||
public async afterCheckoutEvent(...args: Parameters<typeof afterCheckoutEvent>): Promise<any> {
|
||||
return afterCheckoutEvent(...args);
|
||||
}
|
||||
}
|
||||
11
lib/seed/examples/app-rest.ts
Normal file
11
lib/seed/examples/app-rest.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import serverless from 'serverless-http';
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
|
||||
app.get('/', function(req, res) {
|
||||
res.send('Hello World!');
|
||||
});
|
||||
|
||||
module.exports.handler = serverless(app);
|
||||
34
lib/seed/examples/config.ts
Normal file
34
lib/seed/examples/config.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import ErrorSettingsModel from '@services/module-cms/functions/error-settings/errors-settings.model';
|
||||
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
|
||||
export enum NotificationEnum {
|
||||
'notification' = 'notification',
|
||||
}
|
||||
|
||||
export const ErrorsConfig: {
|
||||
[key: string]: {
|
||||
message: string;
|
||||
translations?: ErrorSettingsModel;
|
||||
};
|
||||
} = {
|
||||
code: {
|
||||
message: 'message',
|
||||
},
|
||||
};
|
||||
17
lib/seed/examples/domains.yaml
Normal file
17
lib/seed/examples/domains.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
## Instal ##
|
||||
## run : node ./lib/seed/devops/deploy-domains.js
|
||||
## Carefull - Must be us-east-1 for certificate (created beforehand)
|
||||
|
||||
domains:
|
||||
production: XXXX.com
|
||||
staging: staging-XXXX.com
|
||||
dev: dev-XXXX.com
|
||||
customDomain:
|
||||
basePath: '${self:service}'
|
||||
domainName: ${self:custom.domains.${self:custom.stage}}
|
||||
stage: '${self:custom.stage}'
|
||||
createRoute53Record: true
|
||||
certificateName: XXXX
|
||||
certificateArn: arn:aws:acm:us-east-1:555646219416:certificate/07e83105-1554-4e3d-b968-0a34d77911ad
|
||||
endpointType: 'edge'
|
||||
securityPolicy: tls_1_2
|
||||
20
lib/seed/examples/env/__env.accounts.createAdmin.json
vendored
Normal file
20
lib/seed/examples/env/__env.accounts.createAdmin.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"_id" : "23wzbPpt4OWXtqs92dAaNvrue9v1",
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"email" : "admin@xxxx",
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"types" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"firstName" : "Admin",
|
||||
"lastName" : "Project",
|
||||
}
|
||||
170
lib/seed/examples/env/__env.firebase.import.json
vendored
Normal file
170
lib/seed/examples/env/__env.firebase.import.json
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
{
|
||||
"_id" : "b92eebfa-2bd9-4976-9a6d-2fba6bad14fb",
|
||||
"key" : "FIREBASE_project_id",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "f6f8f6d6-25a4-4ce7-a00b-8b0a7188a5cf",
|
||||
"key" : "FIREBASE_private_key_id",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "00698ef9-7dc3-4942-973d-ac5823ca6399",
|
||||
"key" : "FIREBASE_private_key",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "829f390c-ca20-4dec-a679-4f2117fee710",
|
||||
"key" : "FIREBASE_client_email",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "7f86b770-f6ce-4c99-a6ad-7eda6c3eac04",
|
||||
"key" : "FIREBASE_client_id",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "262c6d6f-ff34-46b4-9639-f8baa75186ce",
|
||||
"key" : "FIREBASE_auth_uri",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "b34ec29f-02c1-4699-ab08-83dd920bce7a",
|
||||
"key" : "FIREBASE_token_uri",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "35409f42-f87f-47ae-8505-2328f98a3979",
|
||||
"key" : "FIREBASE_auth_provider_x509_cert_url",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "fbb3f410-6717-49af-8004-75c9f110e7db",
|
||||
"key" : "FIREBASE_client_x509_cert_url",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "34e7ad98-3c5b-4288-802e-57f6f67a2c9f",
|
||||
"key" : "FIREBASE_apiKey",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
169
lib/seed/examples/env/__env.general.import.json
vendored
Normal file
169
lib/seed/examples/env/__env.general.import.json
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
{
|
||||
"_id" : "28ade57d-697b-4cac-9e50-62cd4a8c8e61",
|
||||
"key" : "FILE_allowedMime",
|
||||
"value" :"image/jpeg,image/png,image/gif,application/pdf,image/svg+xml,application/xml,application/zip",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "6f106c8f-df69-403d-b92b-6bba314ce6ef",
|
||||
"key" : "TEST_USER_EMAIL",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "cfc32155-cd1c-4d5e-bfa9-425ef27065ce",
|
||||
"key" : "TEST_ADMIN_EMAIL",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "8c4c28fb-d89d-434c-a962-f4c43dbda68d",
|
||||
"key" : "STRIPE_SKEY",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "dec94533-a457-4c66-9ead-4e754243ec89",
|
||||
"key" : "STRIPE_PUBLIC_KEY",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "870d1674-367a-4937-9d66-d36e889d4953",
|
||||
"key" : "STRIPE_CURRENCY",
|
||||
"value" :"usd",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "5ed19459-1b4b-462b-88fb-5fbb2d698229",
|
||||
"key" : "STRIPE_ORDERDESCRIPTION",
|
||||
"value" :"You order from our webplatform",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "354b4491-497f-4e3f-9569-06863e19fadd",
|
||||
"key" : "MJ_APIKEY_PUBLIC",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},
|
||||
{
|
||||
"_id" : "2e69bc94-28c9-400e-bd77-1714fb4f8300",
|
||||
"key" : "MJ_APIKEY_PRIVATE",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
},{
|
||||
"_id" : "5320e684-77af-4432-b229-60d63825a823",
|
||||
"key" : "GOOGLE_MAP_apiKey",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
],
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
}
|
||||
219
lib/seed/examples/env/__env.mailsettings.import.json
vendored
Normal file
219
lib/seed/examples/env/__env.mailsettings.import.json
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
{
|
||||
"_id" : "04e48fa2-714f-4d7f-ae17-c791ccf6e5bb",
|
||||
"key" : "resetPassword",
|
||||
"templateId" : {
|
||||
"en" : "",
|
||||
"fr" : "",
|
||||
"nl" : ""
|
||||
},
|
||||
"fromEmail" : "no-reply@makeit-studio.com",
|
||||
"replyToEmail" : "software@makeit-studio.com",
|
||||
"fromName" : "Make it | Studio",
|
||||
"subject" : {
|
||||
"en" : "You requested a new password",
|
||||
"fr" : "Vous avez demandé un nouveau mot de passe",
|
||||
"nl" : "You requested a new password"
|
||||
},
|
||||
"body" : {
|
||||
"en" : "<p>Here is the password reset link : ${resetPasswordLink} </p>",
|
||||
"fr" : "<p>Voici le lien pour réinitialiser votre mot de passe : ${resetPasswordLink}</p>",
|
||||
"nl" : "<p>Here is the password reset link : ${resetPasswordLink} </p>"
|
||||
},
|
||||
"availableFields" : [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"resetPasswordLink"
|
||||
],
|
||||
"custom" : false,
|
||||
"type" : "emails",
|
||||
"cci" : [
|
||||
|
||||
],
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
},
|
||||
{
|
||||
"_id" : "0f245c6c-e39a-465f-9374-e2268a0cbf83",
|
||||
"key" : "adminAccountAddOne",
|
||||
"templateId" : {
|
||||
"en" : "",
|
||||
"fr" : "",
|
||||
"nl" : ""
|
||||
},
|
||||
"fromEmail" : "no-reply@makeit-studio.com",
|
||||
"replyToEmail" : "software@makeit-studio.com",
|
||||
"fromName" : "Make it | Studio",
|
||||
"subject" : {
|
||||
"en" : "The administrator of [PROJECT] created an account for you",
|
||||
"fr" : "L'administrateur de [PROJECT] a crée un compte pour vous!",
|
||||
"nl" : "The administrator of [PROJECT] created an account for you"
|
||||
},
|
||||
"body" : {
|
||||
"en" : "<p>Please setup your password by clicking this link : ${resetPasswordLink} </p>",
|
||||
"fr" : "<p>Pouvez-vous configurer un mot de passe en cliquant sur ce lien ? : ${resetPasswordLink} </p>",
|
||||
"nl" : "<p>Please setup your password by clicking this link : ${resetPasswordLink} </p>"
|
||||
},
|
||||
"availableFields" : [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"resetPasswordLink"
|
||||
],
|
||||
"custom" : false,
|
||||
"type" : "emails",
|
||||
"cci" : [
|
||||
|
||||
],
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
},
|
||||
{
|
||||
"_id" : "16496dfc-5935-495b-a352-52efe158d55c",
|
||||
"key" : "organisationsAddOneAccount",
|
||||
"templateId" : {
|
||||
"en" : "",
|
||||
"fr" : "",
|
||||
"nl" : ""
|
||||
},
|
||||
"fromEmail" : "no-reply@makeit-studio.com",
|
||||
"replyToEmail" : "software@makeit-studio.com",
|
||||
"fromName" : "Make it | Studio",
|
||||
"subject" : {
|
||||
"en" : "The administrator of [ORG] created an account for you",
|
||||
"fr" : "L'administrateur de l'organisation [ORG] a crée un compte pour vous!",
|
||||
"nl" : "The administrator of [ORG] created an account for you"
|
||||
},
|
||||
"body" : {
|
||||
"en" : "<p>Please setup your password by clicking this link : ${resetPasswordLink} </p>",
|
||||
"fr" : "<p>Pouvez-vous configurer un mot de passe en cliquant sur ce lien ? : ${resetPasswordLink} </p>",
|
||||
"nl" : "<p>Please setup your password by clicking this link : ${resetPasswordLink} </p>"
|
||||
},
|
||||
"availableFields" : [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"resetPasswordLink"
|
||||
],
|
||||
"custom" : false,
|
||||
"type" : "emails",
|
||||
"cci" : [
|
||||
|
||||
],
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
},
|
||||
{
|
||||
"_id" : "16496dfc-5875-495b-axx2-52efe158d55c",
|
||||
"key" : "emailCodeSignIn",
|
||||
"templateId" : {
|
||||
"en" : "",
|
||||
"fr" : "",
|
||||
"nl" : ""
|
||||
},
|
||||
"fromEmail" : "no-reply@makeit-studio.com",
|
||||
"replyToEmail" : "software@makeit-studio.com",
|
||||
"fromName" : "Make it | Studio",
|
||||
"subject" : {
|
||||
"en" : "Here is your code to login : ${code}",
|
||||
"fr" : "Voici votre code d'accès: ${code}",
|
||||
"nl" : "Here is your code to login : ${code}"
|
||||
},
|
||||
"body" : {
|
||||
"en" : "Here is your code to login : ${code}",
|
||||
"fr" : "Voici votre code d'accès: ${code}",
|
||||
"nl" : "Here is your code to login : ${code}"
|
||||
},
|
||||
"availableFields" : [
|
||||
"firstName",
|
||||
"code"
|
||||
],
|
||||
"custom" : false,
|
||||
"type" : "emails",
|
||||
"cci" : [
|
||||
|
||||
],
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
},
|
||||
{
|
||||
"_id" : "04e48fa2-714f-4d7f-ae17-c791ccf6e5bb",
|
||||
"key" : "magicLink",
|
||||
"templateId" : {
|
||||
"en" : "",
|
||||
"fr" : "",
|
||||
"nl" : ""
|
||||
},
|
||||
"subject" : {
|
||||
"en" : "Your magic link is ready",
|
||||
"fr" : "Votre demande de lien d'accès",
|
||||
"nl" : "Your magic link is ready"
|
||||
},
|
||||
"body" : {
|
||||
"en" : "Here is your link to login : ${magicLink}",
|
||||
"fr" : "Voici votre lien d'accès: ${magicLink}",
|
||||
"nl" : "Here is your link to login : ${magicLink}"
|
||||
},
|
||||
"fromEmail" : "no-reply@makeit-studio.com",
|
||||
"replyToEmail" : "software@makeit-studio.com",
|
||||
"fromName" : "Make it | Studio",
|
||||
"availableFields" : [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"magicLink"
|
||||
],
|
||||
"custom" : false,
|
||||
"type" : "emails",
|
||||
"cci" : [
|
||||
|
||||
],
|
||||
"r" : [
|
||||
"admin"
|
||||
],
|
||||
"w" : [
|
||||
"admin"
|
||||
],
|
||||
"d" : [
|
||||
"admin"
|
||||
],
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
}
|
||||
|
||||
51
lib/seed/examples/env/__env.notifications.import.json
vendored
Normal file
51
lib/seed/examples/env/__env.notifications.import.json
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"_id" : "28ade57d-df69-4cac-9e50-62cd4a8c8e61",
|
||||
"key" : "TWILIO_FROM_NUMBER",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "28ade57d-df69-403d-b92b-6bba314ce6ef",
|
||||
"key" : "TWILIO_ACCOUNT_SID",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id" : "cfc32155-b92b-4d5e-bfa9-425ef27065ce",
|
||||
"key" : "TWILIO_AUTH_TOKEN",
|
||||
"value" :"",
|
||||
"type" : "env",
|
||||
"createdAt" : ISODate(),
|
||||
"updatedAt" : ISODate(),
|
||||
"d" : [
|
||||
"admin",
|
||||
],
|
||||
"r" : [
|
||||
"admin",
|
||||
],
|
||||
"w" : [
|
||||
"admin",
|
||||
]
|
||||
},
|
||||
47
lib/seed/examples/list.import.json
Normal file
47
lib/seed/examples/list.import.json
Normal file
@ -0,0 +1,47 @@
|
||||
mutation listsAddOne {
|
||||
listsAddOne1: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_2.svg",fr:"combined-shape_2.svg",nl:"combined-shape_2.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_2.svg"}}){_id}
|
||||
listsAddOne2: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_3.svg",fr:"combined-shape_3.svg",nl:"combined-shape_3.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_3.svg"}}){_id}
|
||||
listsAddOne3: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_4.svg",fr:"combined-shape_4.svg",nl:"combined-shape_4.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_4.svg"}}){_id}
|
||||
listsAddOne4: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_5.svg",fr:"combined-shape_5.svg",nl:"combined-shape_5.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_5.svg"}}){_id}
|
||||
listsAddOne5: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_6.svg",fr:"combined-shape_6.svg",nl:"combined-shape_6.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_6.svg"}}){_id}
|
||||
listsAddOne6: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_7.svg",fr:"combined-shape_7.svg",nl:"combined-shape_7.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_7.svg"}}){_id}
|
||||
listsAddOne7: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_8.svg",fr:"combined-shape_8.svg",nl:"combined-shape_8.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_8.svg"}}){_id}
|
||||
listsAddOne8: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_9.svg",fr:"combined-shape_9.svg",nl:"combined-shape_9.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_9.svg"}}){_id}
|
||||
listsAddOne9: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_10.svg",fr:"combined-shape_10.svg",nl:"combined-shape_10.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_10.svg"}}){_id}
|
||||
listsAddOne11: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_11.svg",fr:"combined-shape_11.svg",nl:"combined-shape_11.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_11.svg"}}){_id}
|
||||
listsAddOne12: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_12.svg",fr:"combined-shape_12.svg",nl:"combined-shape_12.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_12.svg"}}){_id}
|
||||
listsAddOne13: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_13.svg",fr:"combined-shape_13.svg",nl:"combined-shape_13.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_13.svg"}}){_id}
|
||||
listsAddOne14: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_14.svg",fr:"combined-shape_14.svg",nl:"combined-shape_14.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_14.svg"}}){_id}
|
||||
listsAddOne15: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_15.svg",fr:"combined-shape_15.svg",nl:"combined-shape_15.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_15.svg"}}){_id}
|
||||
listsAddOne16: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_16.svg",fr:"combined-shape_16.svg",nl:"combined-shape_16.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_16.svg"}}){_id}
|
||||
listsAddOne17: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_17.svg",fr:"combined-shape_17.svg",nl:"combined-shape_17.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_17.svg"}}){_id}
|
||||
listsAddOne18: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_18.svg",fr:"combined-shape_18.svg",nl:"combined-shape_18.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_18.svg"}}){_id}
|
||||
listsAddOne19: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_19.svg",fr:"combined-shape_19.svg",nl:"combined-shape_19.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_19.svg"}}){_id}
|
||||
listsAddOne21: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_20.svg",fr:"combined-shape_20.svg",nl:"combined-shape_20.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_20.svg"}}){_id}
|
||||
listsAddOne22: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_21.svg",fr:"combined-shape_21.svg",nl:"combined-shape_21.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_21.svg"}}){_id}
|
||||
listsAddOne23: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_22.svg",fr:"combined-shape_22.svg",nl:"combined-shape_22.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_22.svg"}}){_id}
|
||||
listsAddOne24: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_23.svg",fr:"combined-shape_23.svg",nl:"combined-shape_23.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_23.svg"}}){_id}
|
||||
listsAddOne25: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_24.svg",fr:"combined-shape_24.svg",nl:"combined-shape_24.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_24.svg"}}){_id}
|
||||
listsAddOne26: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_25.svg",fr:"combined-shape_25.svg",nl:"combined-shape_25.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_25.svg"}}){_id}
|
||||
listsAddOne27: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_26.svg",fr:"combined-shape_26.svg",nl:"combined-shape_26.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_26.svg"}}){_id}
|
||||
listsAddOne28: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_27.svg",fr:"combined-shape_27.svg",nl:"combined-shape_27.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_27.svg"}}){_id}
|
||||
listsAddOne29: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_28.svg",fr:"combined-shape_28.svg",nl:"combined-shape_28.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_28.svg"}}){_id}
|
||||
listsAddOne31: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_29.svg",fr:"combined-shape_29.svg",nl:"combined-shape_29.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_29.svg"}}){_id}
|
||||
listsAddOne32: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_30.svg",fr:"combined-shape_30.svg",nl:"combined-shape_30.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_30.svg"}}){_id}
|
||||
listsAddOne33: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape_31.svg",fr:"combined-shape_31.svg",nl:"combined-shape_31.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape_31.svg"}}){_id}
|
||||
listsAddOne34: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-2.svg",fr:"combined-shape-copy-2.svg",nl:"combined-shape-copy-2.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-2.svg"}}){_id}
|
||||
listsAddOne35: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-3.svg",fr:"combined-shape-copy-3.svg",nl:"combined-shape-copy-3.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-3.svg"}}){_id}
|
||||
listsAddOne36: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-4.svg",fr:"combined-shape-copy-4.svg",nl:"combined-shape-copy-4.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-4.svg"}}){_id}
|
||||
listsAddOne37: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-5.svg",fr:"combined-shape-copy-5.svg",nl:"combined-shape-copy-5.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-5.svg"}}){_id}
|
||||
listsAddOne38: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-6.svg",fr:"combined-shape-copy-6.svg",nl:"combined-shape-copy-6.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-6.svg"}}){_id}
|
||||
listsAddOne39: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-7.svg",fr:"combined-shape-copy-7.svg",nl:"combined-shape-copy-7.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-7.svg"}}){_id}
|
||||
listsAddOne41: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape-copy-8.svg",fr:"combined-shape-copy-8.svg",nl:"combined-shape-copy-8.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape-copy-8.svg"}}){_id}
|
||||
listsAddOne42: listsAddOne(input:{ressourceType:equipment,title:{en:"combined-shape.svg",fr:"combined-shape.svg",nl:"combined-shape.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/combined-shape.svg"}}){_id}
|
||||
listsAddOne43: listsAddOne(input:{ressourceType:feature,title:{en:"group.svg",fr:"group.svg",nl:"group.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/group.svg"}}){_id}
|
||||
listsAddOne44: listsAddOne(input:{ressourceType:feature,title:{en:"shape_2.svg",fr:"shape_2.svg",nl:"shape_2.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape_2.svg"}}){_id}
|
||||
listsAddOne45: listsAddOne(input:{ressourceType:feature,title:{en:"shape_3.svg",fr:"shape_3.svg",nl:"shape_3.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape_3.svg"}}){_id}
|
||||
listsAddOne46: listsAddOne(input:{ressourceType:feature,title:{en:"shape_4.svg",fr:"shape_4.svg",nl:"shape_4.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape_4.svg"}}){_id}
|
||||
listsAddOne47: listsAddOne(input:{ressourceType:feature,title:{en:"shape_5.svg",fr:"shape_5.svg",nl:"shape_5.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape_5.svg"}}){_id}
|
||||
listsAddOne48: listsAddOne(input:{ressourceType:feature,title:{en:"shape_6.svg",fr:"shape_6.svg",nl:"shape_6.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape_6.svg"}}){_id}
|
||||
listsAddOne49: listsAddOne(input:{ressourceType:feature,title:{en:"shape.svg",fr:"shape.svg",nl:"shape.svg"},thumbnail:{large:"https://workinflex-uploads-production.s3.eu-central-1.amazonaws.com/shape.svg"}}){_id}
|
||||
}
|
||||
150
lib/seed/graphql/AccessService.ts
Normal file
150
lib/seed/graphql/AccessService.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
|
||||
export const checkPermissions = (ressource: any, account: any | null, type: 'c' | 'r' | 'w' | 'd'): boolean => {
|
||||
// Adding the public by default
|
||||
let perm: any[] = [AccountTypeEnum.public];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return true;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
if (
|
||||
ressource.organisationId &&
|
||||
account.organisationIds &&
|
||||
!account.organisationIds.includes(ressource.organisationId) &&
|
||||
!ressource.r.includes(AccountTypeEnum.public)
|
||||
)
|
||||
throw newError(2100, { allowed: ressource.organisationId, you: account.organisationIds });
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
perm.push(account._id);
|
||||
perm = perm.concat(account.types);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify on the ressource level
|
||||
*/
|
||||
|
||||
const permissions = ressource[type];
|
||||
|
||||
// if there is no permission on the ressource, return true
|
||||
if (!permissions) return true;
|
||||
|
||||
let hasPerm = false;
|
||||
|
||||
// Verifying if it matches
|
||||
for (let index = 0; index < perm.length; index++) {
|
||||
const element = perm[index];
|
||||
if (permissions.includes(element)) {
|
||||
hasPerm = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPerm) throw newError(2000, { allowed: permissions, you: account });
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const checkOrganisationPermissions = (ressource: any, organisationId: string): boolean => {
|
||||
if (organisationId == ressource.organisationId || organisationId == ressource._id) return true;
|
||||
throw newError(2000, { allowedOrgId: ressource.organisationId, youOrgId: organisationId });
|
||||
};
|
||||
|
||||
export const addPermissions = (ressource: any, type: ('r' | 'w' | 'd')[], ids: (string | string)[]): void => {
|
||||
for (let index = 0; index < type.length; index++) {
|
||||
const t = type[index];
|
||||
ressource[t] = ressource[t].concat(ids);
|
||||
}
|
||||
};
|
||||
|
||||
export const addPermissionToQuery = (account: any | null, query: 'get' | 'update' | 'delete', params: any): any => {
|
||||
let types: any[] = ['public'];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin, no need to add the query filters
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return params;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
|
||||
// if (account.organisationIds) {
|
||||
// params.organisationId = { $in: account.organisationIds };
|
||||
// }
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
|
||||
types.push(account._id);
|
||||
types = types.concat(account.types);
|
||||
}
|
||||
|
||||
if (!params.$and) params.$and = [];
|
||||
|
||||
switch (query) {
|
||||
default:
|
||||
case 'get':
|
||||
params.$and.push({ r: { $in: types } });
|
||||
break;
|
||||
case 'update':
|
||||
params.$and.push({ w: { $in: types } });
|
||||
break;
|
||||
case 'delete':
|
||||
params.$and.push({ d: { $in: types } });
|
||||
break;
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
export const addOrganisationToQuery = (account: any | null, query: 'get' | 'update' | 'delete', params: any): any => {
|
||||
let types: any[] = ['public'];
|
||||
|
||||
if (account && account.types) {
|
||||
/*
|
||||
* Verify if admin, no need to add the query filters
|
||||
*/
|
||||
if (account.types.includes(AccountTypeEnum.admin)) return params;
|
||||
|
||||
/*
|
||||
* Verify if organisation type of access
|
||||
*/
|
||||
if (account.organisationIds) {
|
||||
params.organisationId = { $in: account.organisationIds };
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the account id and type
|
||||
*/
|
||||
|
||||
types.push(account._id);
|
||||
types = types.concat(account.types);
|
||||
}
|
||||
|
||||
switch (query) {
|
||||
default:
|
||||
case 'get':
|
||||
params = { ...params, $or: [{ r: { $in: types } }] };
|
||||
break;
|
||||
case 'update':
|
||||
params = { ...params, $or: [{ w: { $in: types } }] };
|
||||
break;
|
||||
case 'delete':
|
||||
params = { ...params, $or: [{ d: { $in: types } }] };
|
||||
break;
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
828
lib/seed/graphql/BaseGraphModel.ts
Normal file
828
lib/seed/graphql/BaseGraphModel.ts
Normal file
@ -0,0 +1,828 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Collection, FilterQuery, UpdateQuery } from 'mongodb';
|
||||
import { classToPlain, Exclude, plainToClassFromExist } from 'class-transformer';
|
||||
import { Field, ID, ObjectType, Authorized } from 'type-graphql';
|
||||
import DB, { createSetRequest } from '@seed/services/database/DBService';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
|
||||
import { parsePaginationOptions } from '@seed/helpers/Request';
|
||||
import { GetArgs, GetManyArgs } from './Request';
|
||||
import { addPermissions, addPermissionToQuery, checkPermissions, checkOrganisationPermissions } from './AccessService';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import _ from 'lodash';
|
||||
import { StreamOperationType, PostHookStatus } from '@seed/services/change-stream/change-stream.components';
|
||||
import { asyncHook } from '@seed/services/hooks/hooks.decorator';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { promiseAll } from '@seed/helpers/Utils';
|
||||
import { EnginePathComponent } from '@seed/interfaces/components';
|
||||
|
||||
export interface BaseInterface {
|
||||
_id: string;
|
||||
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
|
||||
collectionName: string;
|
||||
}
|
||||
@ObjectType()
|
||||
export abstract class BaseGraphModel implements BaseInterface {
|
||||
@Exclude()
|
||||
public collectionName: string;
|
||||
@Exclude()
|
||||
protected permissions: Permission;
|
||||
@Exclude()
|
||||
defaultSort: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Field(() => ID)
|
||||
_id: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
organisationId?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
createdBy?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
updatedBy?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
deletedBy?: string;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => [String])
|
||||
public r: string[];
|
||||
|
||||
@Authorized('admin')
|
||||
@Field(() => [String])
|
||||
public w: string[];
|
||||
|
||||
@Authorized('admin')
|
||||
@Field(() => [String])
|
||||
public d: string[];
|
||||
|
||||
paths: EnginePathComponent[];
|
||||
|
||||
public constructor(init: { collectionName: string; permissions: Permission; defaultSort?: string }) {
|
||||
this.collectionName = init.collectionName;
|
||||
this.permissions = init.permissions;
|
||||
|
||||
this.r = init.permissions.r;
|
||||
this.w = init.permissions.w;
|
||||
this.d = init.permissions.d;
|
||||
|
||||
this.createdAt = new Date();
|
||||
this.updatedAt = new Date();
|
||||
|
||||
this.defaultSort = init.defaultSort || 'createdAt desc';
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Model functions
|
||||
*
|
||||
*/
|
||||
|
||||
public get(): any {
|
||||
return classToPlain(this);
|
||||
}
|
||||
|
||||
public set(doc: any): void {
|
||||
plainToClassFromExist(this, doc);
|
||||
|
||||
if (doc._id) this._id = doc._id;
|
||||
|
||||
if (doc.r) this.r = _.uniq(_.concat(this.r, doc.r));
|
||||
if (doc.w) this.w = _.uniq(_.concat(this.w, doc.w));
|
||||
if (doc.d) this.d = _.uniq(_.concat(this.d, doc.d));
|
||||
|
||||
if (doc.organisationId) this.organisationId = doc.organisationId;
|
||||
|
||||
if (doc.createdAt) this.createdAt = doc.createdAt;
|
||||
if (doc.updatedAt) this.updatedAt = doc.updatedAt;
|
||||
}
|
||||
|
||||
abstract searchOptions(): string[];
|
||||
abstract filterOptions(): string[];
|
||||
|
||||
searchEngine?(): any;
|
||||
|
||||
async prehook?(): Promise<void>;
|
||||
async beforeCreate?(ctx?: ApolloContext | null): Promise<void>;
|
||||
async beforeUpdate?(ctx?: ApolloContext | null): Promise<void>;
|
||||
async beforeDelete?(ctx?: ApolloContext | null): Promise<void>;
|
||||
|
||||
async afterCreate?(changeStream: any): Promise<void>;
|
||||
async afterUpdate?(changeStream: any): Promise<void>;
|
||||
async afterDelete?(changeStream: any): Promise<void>;
|
||||
|
||||
/*
|
||||
*
|
||||
* DB Functions
|
||||
*
|
||||
*/
|
||||
|
||||
public async db(): Promise<Collection<this>> {
|
||||
return await (await DB.getInstance()).db.collection<this>(this.collectionName);
|
||||
}
|
||||
|
||||
public getPath(): EnginePathComponent[] {
|
||||
if (this.paths) {
|
||||
this.paths.push({
|
||||
ressourceModel: this.collectionName,
|
||||
ressourceId: this._id,
|
||||
});
|
||||
|
||||
return this.paths;
|
||||
} else
|
||||
return [
|
||||
{
|
||||
ressourceModel: this.collectionName,
|
||||
ressourceId: this._id,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public getQuery(query: FilterQuery<this>, ctx: ApolloContext | null): any {
|
||||
let finalQuery: any;
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
// We add the permission (on r field) to make sure that kind of user has access to the ressource
|
||||
finalQuery = addPermissionToQuery(account, 'get', { ...query });
|
||||
|
||||
// We check if it comes from an organisation
|
||||
// Check of the user's correct authorisation on that organisation comes from the middleware
|
||||
|
||||
if (!ctx.ctx.noOrganisationCheck) {
|
||||
if (organisationId) {
|
||||
if (this.collectionName == 'accounts') finalQuery['organisationIds'] = organisationId;
|
||||
else finalQuery['organisationId'] = { $eq: organisationId };
|
||||
} else {
|
||||
// If admin & public query -> do not add this condition and let search every records)
|
||||
if (
|
||||
account &&
|
||||
account.types &&
|
||||
!account.types.includes(AccountTypeEnum.public) &&
|
||||
!account.types.includes(AccountTypeEnum.admin)
|
||||
) {
|
||||
finalQuery['organisationId'] = { $exists: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalQuery = { ...query };
|
||||
}
|
||||
|
||||
return finalQuery;
|
||||
}
|
||||
|
||||
public async getMany(query: FilterQuery<this>, pagination: GetArgs | undefined, ctx: ApolloContext | null): Promise<any> {
|
||||
const finalQuery = this.getQuery(query, ctx);
|
||||
|
||||
if (!pagination) pagination = { limit: 100, skip: 0 };
|
||||
if (!pagination.sort) pagination.sort = this.defaultSort;
|
||||
const paginationOption = parsePaginationOptions(pagination);
|
||||
|
||||
try {
|
||||
console.log('search request', JSON.stringify(finalQuery, null, 2));
|
||||
return await (await this.db()).find(finalQuery, paginationOption).toArray();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async getCount(query: FilterQuery<this>, ctx: ApolloContext | null): Promise<number> {
|
||||
const finalQuery = this.getQuery(query, ctx);
|
||||
|
||||
try {
|
||||
const result = (await (await this.db()).countDocuments(finalQuery)) as any;
|
||||
console.log(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async getAll(query: FilterQuery<this>, ctx: ApolloContext | null): Promise<any> {
|
||||
const finalQuery = this.getQuery(query, ctx);
|
||||
try {
|
||||
return await (await this.db()).find(finalQuery).toArray();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async getOne(query: FilterQuery<this>, ctx: ApolloContext | null): Promise<this> {
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
const doc = await (await this.db()).findOne(query);
|
||||
if (!doc) throw newError(404, query);
|
||||
|
||||
this.set(doc);
|
||||
|
||||
if (!ctx.ctx.noOrganisationCheck) {
|
||||
if (organisationId) {
|
||||
if (this.collectionName != 'accounts') checkOrganisationPermissions(this, organisationId);
|
||||
}
|
||||
}
|
||||
|
||||
checkPermissions(this, account, 'r');
|
||||
|
||||
return this;
|
||||
} else {
|
||||
const doc = await (await this.db()).findOne(query);
|
||||
if (!doc) throw newError(404, query);
|
||||
|
||||
this.set(doc);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public async saveOne(newData: Partial<this>, ctx: ApolloContext | null, upsert = true): Promise<this> {
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
addPermissions(this, ['r', 'w', 'd'], [account._id]);
|
||||
|
||||
if (organisationId) {
|
||||
newData.organisationId = organisationId;
|
||||
}
|
||||
|
||||
newData.createdBy = account._id;
|
||||
}
|
||||
|
||||
this.set(newData);
|
||||
|
||||
// if (!ctx) ctx = await createApolloContext();
|
||||
|
||||
try {
|
||||
if (this.prehook) await this.prehook();
|
||||
if (this.beforeCreate) await this.beforeCreate(ctx);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { _id, ...dataToSave } = this.get();
|
||||
const savedId = _id ? _id : uuid();
|
||||
|
||||
try {
|
||||
const result = await (await this.db()).findOneAndUpdate(
|
||||
{ _id: savedId },
|
||||
{
|
||||
$set: dataToSave,
|
||||
$setOnInsert: {
|
||||
_id: savedId,
|
||||
},
|
||||
} as any,
|
||||
{ upsert, returnOriginal: false },
|
||||
);
|
||||
|
||||
if (result.ok == 0) throw newError(1000, result);
|
||||
|
||||
this.set({ _id: savedId });
|
||||
|
||||
const changeStreamId = uuid();
|
||||
const changeStreamData = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.insert,
|
||||
collection: this.collectionName,
|
||||
documentKey: this._id,
|
||||
insertedValues: this.get(),
|
||||
hookStatus: PostHookStatus.new,
|
||||
createdAt: new Date(),
|
||||
updateAt: new Date(),
|
||||
};
|
||||
await (await DB.getInstance()).db.collection('stream.changes').insertOne(changeStreamData);
|
||||
|
||||
if (this.afterCreate && (await asyncHook(changeStreamData))) {
|
||||
await this.afterCreate(changeStreamData);
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection('stream.changes')
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (error) {
|
||||
throw newError(1000, error);
|
||||
}
|
||||
}
|
||||
|
||||
public async saveMany(newDataArr: Partial<this>[], ctx: ApolloContext | null): Promise<any[]> {
|
||||
// Initialize the change stream
|
||||
const changeStreams: any[] = [];
|
||||
const afterCreatePromises: any[] = [];
|
||||
|
||||
const prehooksPromises = newDataArr.map(async (newData) => {
|
||||
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
addPermissions(clone, ['r', 'w', 'd'], [account._id]);
|
||||
|
||||
if (organisationId) {
|
||||
newData.organisationId = organisationId;
|
||||
}
|
||||
|
||||
newData.createdBy = account._id;
|
||||
}
|
||||
|
||||
clone.set(newData);
|
||||
|
||||
// if (!ctx) ctx = await createApolloContext();
|
||||
|
||||
if (clone.prehook) await clone.prehook();
|
||||
if (clone.beforeCreate) await clone.beforeCreate(ctx);
|
||||
|
||||
const { _id, ...dataToSave } = clone.get();
|
||||
const savedId = _id ? _id : uuid();
|
||||
clone.set({ _id: savedId });
|
||||
|
||||
const changeStreamId = uuid();
|
||||
const changeStreamData = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.insert,
|
||||
collection: clone.collectionName,
|
||||
documentKey: clone._id,
|
||||
insertedValues: clone.get(),
|
||||
hookStatus: PostHookStatus.new,
|
||||
createdAt: new Date(),
|
||||
updateAt: new Date(),
|
||||
};
|
||||
|
||||
changeStreams.push(changeStreamData);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
afterCreatePromises.push(async () => {
|
||||
if (clone.afterCreate) {
|
||||
const res = await asyncHook(changeStreamData);
|
||||
|
||||
if (res) {
|
||||
await clone.afterCreate(changeStreamData);
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection('stream.changes')
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return clone.get();
|
||||
});
|
||||
|
||||
try {
|
||||
const dataArr = await Promise.all(prehooksPromises);
|
||||
await (await this.db()).insertMany(dataArr, { ordered: false });
|
||||
await (await DB.getInstance()).db.collection('stream.changes').insertMany(changeStreams, { ordered: false });
|
||||
await promiseAll(afterCreatePromises);
|
||||
return dataArr;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw newError(1000, error);
|
||||
}
|
||||
}
|
||||
|
||||
public async updateOne(query: FilterQuery<this>, newData: Partial<this>, ctx: ApolloContext | null): Promise<this> {
|
||||
try {
|
||||
const dataThatHaveBeenChanged = createSetRequest<this>('', { ...newData, updatedAt: new Date() });
|
||||
let dataBeforeChange: any;
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
// Check if doc exists & correct permissions
|
||||
if (!this._id) await this.getOne(query, ctx);
|
||||
|
||||
dataBeforeChange = this.get();
|
||||
|
||||
dataThatHaveBeenChanged.updatedBy = account._id;
|
||||
|
||||
this.set(dataThatHaveBeenChanged);
|
||||
if (this.beforeUpdate) await this.beforeUpdate(ctx);
|
||||
|
||||
checkPermissions(this, account, 'w');
|
||||
|
||||
const result = await (await this.db()).findOneAndUpdate(
|
||||
query,
|
||||
{ $set: dataThatHaveBeenChanged },
|
||||
{ upsert: false, returnOriginal: false },
|
||||
);
|
||||
if (result.ok != 1) throw newError(1000, result);
|
||||
|
||||
this.set(result.value);
|
||||
} else {
|
||||
if (!this._id) await this.getOne(query, null);
|
||||
dataBeforeChange = this.get();
|
||||
|
||||
this.set(dataThatHaveBeenChanged);
|
||||
if (this.beforeUpdate) await this.beforeUpdate();
|
||||
|
||||
const result = await (await this.db()).findOneAndUpdate(
|
||||
query,
|
||||
{ $set: dataThatHaveBeenChanged },
|
||||
{ upsert: false, returnOriginal: false },
|
||||
);
|
||||
if (result.ok != 1) throw newError(1000, result);
|
||||
this.set(result.value);
|
||||
}
|
||||
|
||||
const changeStreamId = uuid();
|
||||
const changeStreamData = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.update,
|
||||
collection: this.collectionName,
|
||||
documentKey: this._id,
|
||||
updatedValues: dataThatHaveBeenChanged,
|
||||
beforeUpdateValues: dataBeforeChange,
|
||||
hookStatus: PostHookStatus.new,
|
||||
createdAt: new Date(),
|
||||
updateAt: new Date(),
|
||||
};
|
||||
await (await DB.getInstance()).db.collection('stream.changes').insertOne(changeStreamData);
|
||||
|
||||
if (this.afterUpdate && (await asyncHook(changeStreamData))) {
|
||||
await this.afterUpdate(changeStreamData);
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection('stream.changes')
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async updateOneCustom(query: FilterQuery<this>, set: UpdateQuery<this>, ctx: ApolloContext | null): Promise<this> {
|
||||
try {
|
||||
let dataBeforeChange: any;
|
||||
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
// Check if doc exists
|
||||
if (!this._id) await this.getOne(query, ctx);
|
||||
dataBeforeChange = this.get();
|
||||
|
||||
checkPermissions(this, account, 'w');
|
||||
|
||||
// Validate new data that will go into the DB
|
||||
// const validation = await this.validate();
|
||||
// if (!validation.success) return validation;
|
||||
|
||||
// Prepare data
|
||||
this.updatedAt = new Date();
|
||||
|
||||
const result = await (await this.db()).findOneAndUpdate(query, set, { upsert: false, returnOriginal: false });
|
||||
if (result.ok != 1) throw newError(1000, result);
|
||||
|
||||
if (result.value) this.set(result.value);
|
||||
} else {
|
||||
if (!this._id) await this.getOne(query, null);
|
||||
dataBeforeChange = this.get();
|
||||
|
||||
// Prepare data
|
||||
this.updatedAt = new Date();
|
||||
|
||||
const result = await (await this.db()).findOneAndUpdate(query, set, { upsert: false, returnOriginal: false });
|
||||
if (result.ok != 1) throw newError(1000, result);
|
||||
|
||||
if (result.value) this.set(result.value);
|
||||
}
|
||||
|
||||
const changeStreamId = uuid();
|
||||
const changeStreamData = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.update,
|
||||
collection: this.collectionName,
|
||||
documentKey: this._id,
|
||||
hookStatus: PostHookStatus.new,
|
||||
updatedValues: JSON.stringify(set),
|
||||
beforeUpdateValues: dataBeforeChange,
|
||||
createdAt: new Date(),
|
||||
updateAt: new Date(),
|
||||
};
|
||||
await (await DB.getInstance()).db.collection('stream.changes').insertOne(changeStreamData);
|
||||
if (this.afterUpdate && (await asyncHook(changeStreamData))) {
|
||||
await this.afterUpdate(changeStreamData);
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection('stream.changes')
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteOne(query: FilterQuery<this>, ctx: ApolloContext | null): Promise<this> {
|
||||
try {
|
||||
if (ctx) {
|
||||
const account = ctx.ctx.user;
|
||||
if (!this._id) await this.getOne(query, ctx);
|
||||
checkPermissions(this, account, 'd');
|
||||
|
||||
if (this.beforeDelete) await this.beforeDelete(ctx);
|
||||
|
||||
const result = await (await this.db()).findOneAndDelete(query);
|
||||
|
||||
if (result.ok != 1) throw newError(1001);
|
||||
|
||||
const deletedModel = '_deleted_' + this.collectionName;
|
||||
await (await DB.getInstance()).db.collection(deletedModel).insertOne({ ...result.value, deletedBy: account._id });
|
||||
} else {
|
||||
if (!this._id) await this.getOne(query, null);
|
||||
|
||||
if (this.beforeDelete) await this.beforeDelete();
|
||||
|
||||
const result = await (await this.db()).findOneAndDelete(query);
|
||||
|
||||
if (result.ok != 1) throw newError(1001);
|
||||
|
||||
const deletedModel = '_deleted_' + this.collectionName;
|
||||
await (await DB.getInstance()).db.collection(deletedModel).insertOne(result.value);
|
||||
}
|
||||
const changeStreamId = uuid();
|
||||
|
||||
const changeStreamData = {
|
||||
_id: changeStreamId,
|
||||
operation: StreamOperationType.delete,
|
||||
collection: this.collectionName,
|
||||
documentKey: this._id,
|
||||
hookStatus: PostHookStatus.new,
|
||||
beforeUpdateValues: this.get(),
|
||||
createdAt: new Date(),
|
||||
updateAt: new Date(),
|
||||
};
|
||||
await (await DB.getInstance()).db.collection('stream.changes').insertOne(changeStreamData);
|
||||
|
||||
if (this.afterDelete && (await asyncHook(changeStreamData))) {
|
||||
await this.afterDelete(changeStreamData);
|
||||
|
||||
await (await DB.getInstance()).db
|
||||
.collection('stream.changes')
|
||||
.updateOne({ _id: changeStreamId }, { $set: { hookStatus: PostHookStatus.completed } });
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Permissions
|
||||
*
|
||||
*/
|
||||
|
||||
public async updateQueryWithPermission(query: FilterQuery<this>, ctx: ApolloContext): Promise<FilterQuery<this>> {
|
||||
const account = ctx.ctx.user;
|
||||
const organisationId = ctx.ctx.organisationId;
|
||||
|
||||
// We add the permission (on r field) to make sure that kind of user has access to the ressource
|
||||
const finalQuery = addPermissionToQuery(account, 'get', { ...query });
|
||||
|
||||
// We check if it comes from an organisation
|
||||
// Check of the user's correct authorisation on that organisation comes from the middleware
|
||||
if (organisationId) {
|
||||
finalQuery['organisationId'] = { $eq: organisationId };
|
||||
} else {
|
||||
finalQuery['organisationId'] = { $exists: false };
|
||||
}
|
||||
|
||||
return finalQuery;
|
||||
}
|
||||
|
||||
// public async updateOnePermission(
|
||||
// query: FilterQuery<this>,
|
||||
// add: 'add' | 'remove',
|
||||
// type: 'r' | 'w' | 'd',
|
||||
// newPerm: string,
|
||||
// account?: AccountModel,
|
||||
// ): Promise<this> {
|
||||
// // Get the object from the DB
|
||||
// try {
|
||||
// // Check if doc exists
|
||||
// // TODO : if (this.get() returns, then don't)
|
||||
// await this.getOne(query, account);
|
||||
// if (account) this.checkPermissions(account.get(), 'w');
|
||||
|
||||
// // Validate new data that will go into the DB
|
||||
// // const validation = await this.validate();
|
||||
// // if (!validation.success) return validation;
|
||||
|
||||
// // Prepare data
|
||||
// this.updatedAt = new Date();
|
||||
|
||||
// if (add == 'add') {
|
||||
// const result = await (await this.db()).findOneAndUpdate(
|
||||
// query,
|
||||
// { $addToSet: { [type]: newPerm } },
|
||||
// { upsert: false, returnOriginal: false },
|
||||
// );
|
||||
// if (result.ok != 1) throw newError('general.dbError', '400', result);
|
||||
|
||||
// this.set(result.value);
|
||||
// } else if (add == 'remove') {
|
||||
// const result = await (await this.db()).findOneAndUpdate(
|
||||
// query,
|
||||
// { $pull: { [type]: newPerm } },
|
||||
// { upsert: false, returnOriginal: false },
|
||||
// );
|
||||
// if (result.ok != 1) throw newError('general.dbError', '400', result);
|
||||
|
||||
// this.set(result.value);
|
||||
// }
|
||||
|
||||
// return this;
|
||||
// } catch (error) {
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
|
||||
public async addOneSubModel<SUB extends BaseGraphModel>(
|
||||
parentQ: { _id: string },
|
||||
subModelRefField: string,
|
||||
subModelFieldName: keyof this,
|
||||
subModel: SUB,
|
||||
subModelInput: any,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<SUB> {
|
||||
try {
|
||||
subModelInput[subModelRefField] = parentQ._id;
|
||||
const subM = await subModel.saveOne(subModelInput, ctx);
|
||||
await this.updateOneCustom(parentQ as any, { $push: { [`${subModelFieldName}`]: subM._id } } as any, null);
|
||||
return subM;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async editOneSubModel<SUB extends BaseGraphModel>(
|
||||
subModel: SUB,
|
||||
subModelId: string,
|
||||
subModelInput: any,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<SUB> {
|
||||
try {
|
||||
// No need to check if belongs to the correct organisation, the middleware does that
|
||||
const subM = await subModel.updateOne({ _id: subModelId } as any, subModelInput, ctx);
|
||||
return subM;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteOneSubModel<SUB extends BaseGraphModel>(
|
||||
parentQ: FilterQuery<this>,
|
||||
subModelFieldName: keyof this,
|
||||
subModel: SUB,
|
||||
subModelId: string,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<SUB> {
|
||||
try {
|
||||
const subM = await subModel.deleteOne({ _id: subModelId } as any, ctx);
|
||||
await this.updateOneCustom(parentQ, { $pull: { [`${subModelFieldName}`]: subM._id } } as any, null);
|
||||
return subM;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public getOneSubRessource(subRessoureFieldName: keyof this | string, subRessoureId: string): any | undefined {
|
||||
try {
|
||||
const subress = _.get(this, subRessoureFieldName);
|
||||
if (subress) {
|
||||
const ind = _.findIndex(subress, { _id: subRessoureId });
|
||||
if (ind !== -1) return subress[ind];
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public getDefaultSubRessource(subRessoureFieldName: keyof this | string): any | undefined {
|
||||
try {
|
||||
const subress = _.get(this, subRessoureFieldName);
|
||||
if (subress) {
|
||||
const ind = _.findIndex(subress, { default: true });
|
||||
if (ind !== -1) return subress[ind];
|
||||
return subress[0];
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async addOneSubRessource(
|
||||
parentQ: { _id: string },
|
||||
subRessoureFieldName: keyof this | string,
|
||||
subRessoure: any,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<any> {
|
||||
try {
|
||||
if (subRessoure.default && _.get(this, subRessoureFieldName)) {
|
||||
// Remove all default from others
|
||||
await this.updateOneCustom(
|
||||
{ _id: this._id } as any,
|
||||
{
|
||||
$set: { [`${subRessoureFieldName}.$[].default`]: false } as any,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
} else subRessoure.default = false;
|
||||
|
||||
// Generate ID if not in the model
|
||||
if (!subRessoure._id) subRessoure._id = uuid();
|
||||
await this.updateOneCustom(parentQ as any, { $push: { [`${subRessoureFieldName}`]: subRessoure } } as any, ctx);
|
||||
return subRessoure;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async editOneSubRessource(
|
||||
parentQ: { _id: string },
|
||||
subRessoureFieldName: keyof this | string,
|
||||
subRessoureId: string,
|
||||
subRessoure: any,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<any> {
|
||||
try {
|
||||
if (!this.getOneSubRessource(subRessoureFieldName, subRessoureId)) throw newError(404);
|
||||
|
||||
if (subRessoure.default) {
|
||||
// Remove all default from others
|
||||
await this.updateOneCustom(
|
||||
{ _id: this._id } as any,
|
||||
{
|
||||
$set: { [`${subRessoureFieldName}.$[].default`]: false } as any,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
} else subRessoure.default = false;
|
||||
|
||||
const filterQ = {
|
||||
...parentQ,
|
||||
[`${subRessoureFieldName}._id`]: subRessoureId,
|
||||
};
|
||||
await this.updateOneCustom(
|
||||
filterQ as any,
|
||||
{
|
||||
$set: {
|
||||
[`${subRessoureFieldName}.$`]: {
|
||||
...subRessoure,
|
||||
_id: subRessoureId,
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
return {
|
||||
...subRessoure,
|
||||
_id: subRessoureId,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteOneSubRessource(
|
||||
parentQ: { _id: string },
|
||||
subRessoureFieldName: keyof this | string,
|
||||
subRessoureId: string,
|
||||
ctx: ApolloContext | null,
|
||||
): Promise<string> {
|
||||
try {
|
||||
if (!this.getOneSubRessource(subRessoureFieldName, subRessoureId)) throw newError(404);
|
||||
|
||||
const filterQ = {
|
||||
...parentQ,
|
||||
[`${subRessoureFieldName}._id`]: subRessoureId,
|
||||
};
|
||||
|
||||
await this.updateOneCustom(filterQ as any, { $pull: { [`${subRessoureFieldName}`]: { _id: subRessoureId } } } as any, ctx);
|
||||
return subRessoureId;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
lib/seed/graphql/BaseService.ts
Normal file
122
lib/seed/graphql/BaseService.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ApolloError } from 'apollo-server-lambda';
|
||||
|
||||
import { GetArgs } from './Request';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { BaseGraphModel } from './BaseGraphModel';
|
||||
import { NewPermissionInput } from '@seed/interfaces/components';
|
||||
import { baseSearchFunction } from '@seed/services/database/DBRequestService';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
|
||||
export const getOneGeneric = async <T extends BaseGraphModel>(model: T, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
await model.getOne({ _id: id } as any, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const addOneGeneric = async <T extends BaseGraphModel>(model: T, body: Partial<T>, ctx: ApolloContext): Promise<T> => {
|
||||
try {
|
||||
await model.saveOne(body, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const editOneGeneric = async <T extends BaseGraphModel>(model: T, id: string, body: Partial<T>, ctx: ApolloContext): Promise<T> => {
|
||||
try {
|
||||
await model.updateOne({ _id: id } as any, body, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteOneGeneric = async <T extends BaseGraphModel>(model: T, id: string, ctx: ApolloContext): Promise<T> => {
|
||||
try {
|
||||
await model.deleteOne({ _id: id } as any, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getManyGeneric = async <T extends BaseGraphModel>(model: T, query: any, pagination: GetArgs, ctx: ApolloContext): Promise<any> => {
|
||||
return baseSearchFunction({
|
||||
model: model,
|
||||
query: query,
|
||||
pagination: pagination,
|
||||
ctx: ctx,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCountGeneric = async <T extends BaseGraphModel>(model: T, baseArguments: any, ctx: ApolloContext): Promise<number> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { pagination, ...args } = baseArguments;
|
||||
|
||||
const result = await baseSearchFunction({
|
||||
model: model,
|
||||
query: args,
|
||||
count: true,
|
||||
ctx: ctx,
|
||||
});
|
||||
|
||||
return result.count || 0;
|
||||
};
|
||||
|
||||
export const getManyGenericWithArgs = async <T extends BaseGraphModel>(model: T, baseArguments: any, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
const { pagination, ...args } = baseArguments;
|
||||
return baseSearchFunction({
|
||||
model: model,
|
||||
query: args,
|
||||
pagination: pagination,
|
||||
ctx: ctx,
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const permissionsAddOne = async (model: any, id: string, input: NewPermissionInput, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
await model.updateOneCustom({ _id: id }, { $addToSet: { [`${input.permissionType}`]: input.permission } }, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const permissionsRemoveOne = async (model: any, id: string, input: NewPermissionInput, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
await model.updateOneCustom({ _id: id }, { $pull: { [`${input.permissionType}`]: input.permission } }, ctx);
|
||||
return model.get();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const publishOneGeneric = async <T extends BaseGraphModel>(model: T, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
const missingInputs: string[] = [];
|
||||
// TODO THE CHECK OF THE INPUTS
|
||||
|
||||
if (missingInputs.length > 0) throw newError(3000, missingInputs);
|
||||
|
||||
return editOneGeneric(model, id, { published: true, publicationDate: new Date() } as any, ctx);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const unpublishOneGeneric = async <T extends BaseGraphModel>(model: T, id: string, ctx: ApolloContext): Promise<any> => {
|
||||
try {
|
||||
return editOneGeneric(model, id, { published: false } as any, ctx);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
204
lib/seed/graphql/Middleware.ts
Normal file
204
lib/seed/graphql/Middleware.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import _ from 'lodash';
|
||||
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
|
||||
import { ApolloError } from 'apollo-server-lambda';
|
||||
import { AuthChecker, createMethodDecorator } from 'type-graphql';
|
||||
|
||||
import Firebase from '@seed/services/auth/FirebaseService';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
|
||||
import AccountModel from 'src/accounts/account.model';
|
||||
import Loaders from '@src/__indexes/__loaders';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
|
||||
import { simpleEstimator, getComplexity, fieldExtensionsEstimator } from 'graphql-query-complexity';
|
||||
import { clog } from '@seed/helpers/Utils';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
import { SecurityLevelEnum } from '@services/module-accounts/account.components';
|
||||
|
||||
export const ctxMiddleware = async (ctx: { event?: APIGatewayProxyEvent; context?: Context; req?: any }): Promise<ApolloContext> => {
|
||||
const user: AccountModel = new AccountModel();
|
||||
let headers: any = {};
|
||||
let organisationId = null;
|
||||
|
||||
try {
|
||||
if (ctx.event) headers = ctx.event.headers;
|
||||
else if (ctx.req) headers = ctx.req.headers;
|
||||
|
||||
/*
|
||||
* Deal with Auth
|
||||
*/
|
||||
|
||||
const authorization: string = headers['x-authorization'] || headers.authorization || headers.Authorization;
|
||||
|
||||
if (authorization) {
|
||||
// Verify the token
|
||||
const firebaseRes = await Firebase.getInstance().tokenIdentify(authorization);
|
||||
// Verify that exists in DB
|
||||
await user.getOne({ _id: firebaseRes.uid }, null);
|
||||
|
||||
/*
|
||||
* Deal with Security
|
||||
*/
|
||||
|
||||
const securityCheck = user.securityCheck();
|
||||
if (!securityCheck) {
|
||||
throw newError(2400, {
|
||||
security: user.security,
|
||||
securityLevel: process.env.SECURITY_LEVEL || SecurityLevelEnum.nothing,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Deal with Organisation
|
||||
*/
|
||||
organisationId = headers.organisationId || headers.organisationid;
|
||||
if (organisationId) {
|
||||
// Check if admin
|
||||
if (user.types && user.types.includes(AccountTypeEnum.admin)) {
|
||||
console.log('[ADMIN BYPASS]');
|
||||
} else if (!user._id || !user.organisationIds || !user.organisationIds?.includes(organisationId))
|
||||
throw newError(2101, {
|
||||
accountOrganisationIds: user.organisationIds,
|
||||
organisationId: organisationId,
|
||||
});
|
||||
|
||||
console.log('[ORG]', organisationId);
|
||||
}
|
||||
|
||||
console.log('[AUTH - ]', user.types, user?._id);
|
||||
} else {
|
||||
console.log('no auth');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
event: ctx.event || ctx.req || {},
|
||||
context: ctx.context || ctx.req || {},
|
||||
ctx: {
|
||||
organisationId: organisationId,
|
||||
user: user,
|
||||
loaders: new Loaders(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const authMiddleware: AuthChecker<ApolloContext> = ({ root, args, context, info }, roles) => {
|
||||
const user = context.ctx.user;
|
||||
|
||||
if (!user || !user._id) {
|
||||
throw newError(2004);
|
||||
}
|
||||
|
||||
if (roles.length > 0) {
|
||||
roles.push(AccountTypeEnum.admin);
|
||||
|
||||
const intersection = _.intersection(roles, user.types);
|
||||
if (intersection.length > 0) return true;
|
||||
|
||||
// no roles matched, restrict access
|
||||
throw newError(2000, { you: user.types, allowed: roles });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const checkOrganisation = (check = false): any => {
|
||||
return createMethodDecorator(async ({ root, args, context, info }, next) => {
|
||||
if (check && !(context as any).ctx.organisationId) throw newError(2102, '400');
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
export const totalPublic = (check = false): any => {
|
||||
return createMethodDecorator(async ({ root, args, context, info }, next) => {
|
||||
if (check) (context as any).ctx.noOrganisationCheck = true;
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
export const errorMiddleware = (err): any => {
|
||||
// Don't give the specific errors to the client.
|
||||
|
||||
if (err.extensions) {
|
||||
const { translations, ...data } = err.extensions;
|
||||
const errorToReturn = {
|
||||
code: err.extensions.code,
|
||||
message: err.extensions.message || err.message,
|
||||
translations,
|
||||
data: process.env.NODE_ENV == 'production' ? { message: 'see logs' } : data,
|
||||
};
|
||||
clog(errorToReturn);
|
||||
|
||||
return errorToReturn;
|
||||
}
|
||||
|
||||
// Otherwise return the original error. The error can also
|
||||
// be manipulated in other ways, so long as it's returned.
|
||||
return err;
|
||||
};
|
||||
|
||||
export const complexityMiddleware = (schema: any, variables: any, query: any) => {
|
||||
const maximumComplexity = process.env.GRAPH_COMPLEXITY ? parseInt(process.env.GRAPH_COMPLEXITY) : 1000;
|
||||
|
||||
const complexity = getComplexity({
|
||||
schema: schema,
|
||||
|
||||
// The maximum allowed query complexity, queries above this threshold will be rejected
|
||||
|
||||
// The query variables. This is needed because the variables are not available
|
||||
// in the visitor of the graphql-js library
|
||||
variables: variables,
|
||||
|
||||
// specify operation name only when pass multi-operation documents
|
||||
query: query,
|
||||
|
||||
// Add any number of estimators. The estimators are invoked in order, the first
|
||||
// numeric value that is being returned by an estimator is used as the field complexity.
|
||||
// If no estimator returns a value, an exception is raised.
|
||||
estimators: [
|
||||
// Add more estimators here...
|
||||
fieldExtensionsEstimator(),
|
||||
// This will assign each field a complexity of 1 if no other estimator
|
||||
// returned a value.
|
||||
simpleEstimator({
|
||||
defaultComplexity: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Here we can react to the calculated complexity,
|
||||
// like compare it with max and throw error when the threshold is reached.
|
||||
if (complexity >= maximumComplexity) {
|
||||
throw new Error(`Sorry, too complicated query! ${complexity} is over ${maximumComplexity} that is the max allowed complexity.`);
|
||||
}
|
||||
// And here we can e.g. subtract the complexity point from hourly API calls limit.
|
||||
console.log('Used query complexity points:', complexity);
|
||||
};
|
||||
|
||||
export const oneToManyComplexity = ({ args, childComplexity }): number => {
|
||||
if (args.pagination && args.pagination.limit) return childComplexity * args.pagination.limit;
|
||||
else return childComplexity * 10;
|
||||
};
|
||||
|
||||
export const oneToManyComplexityNoLoader = ({ args, childComplexity }): number => {
|
||||
if (args.pagination && args.pagination.limit) return childComplexity * args.pagination.limit * 100;
|
||||
else return childComplexity * 10 * 100;
|
||||
};
|
||||
|
||||
export const createApolloContext = async (accountId?: string, organisationId: string | null = null): Promise<ApolloContext> => {
|
||||
const user: AccountModel = new AccountModel();
|
||||
if (accountId) {
|
||||
await user.getOne({ _id: accountId }, null);
|
||||
}
|
||||
return {
|
||||
event: {} as any,
|
||||
context: {} as any,
|
||||
ctx: {
|
||||
organisationId,
|
||||
user: user,
|
||||
loaders: new Loaders(),
|
||||
},
|
||||
};
|
||||
};
|
||||
132
lib/seed/graphql/MiddlewareV2.ts
Normal file
132
lib/seed/graphql/MiddlewareV2.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import _ from 'lodash';
|
||||
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
|
||||
import { ApolloError } from 'apollo-server-lambda';
|
||||
import { ClassType, createMethodDecorator, MiddlewareFn } from 'type-graphql';
|
||||
|
||||
import Firebase from '@seed/services/auth/FirebaseService';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
|
||||
import AccountModel from 'src/accounts/account.model';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { validateOrReject } from 'class-validator';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { newError } from '@seed/helpers/Error';
|
||||
|
||||
export interface EngineMiddlewareInput {
|
||||
authorization?: AccountTypeEnum[];
|
||||
apiKey?: boolean;
|
||||
checkOrganisation?: boolean;
|
||||
noOrganisationCheck?: boolean;
|
||||
noPermissionCheck?: boolean;
|
||||
validations?: {
|
||||
inputName?: string;
|
||||
schema: ClassType<any>;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const checkApiKey = async ({ context }): Promise<boolean> => {
|
||||
const user: AccountModel = new AccountModel();
|
||||
let headers: any = {};
|
||||
if (context.event) headers = context.event.headers;
|
||||
else if (context.req) headers = context.req.headers;
|
||||
|
||||
/*
|
||||
* Deal with Auth
|
||||
*/
|
||||
|
||||
const apiKey: string = headers['x-api-key'];
|
||||
|
||||
if (apiKey) {
|
||||
// Verify that exists in DB
|
||||
try {
|
||||
const account = await user.getOne({ 'apiKeys.key': apiKey }, null);
|
||||
context.ctx.user = account;
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw newError(2200, '403');
|
||||
}
|
||||
} else throw newError(2202, { required: 'X-API-Key' });
|
||||
};
|
||||
|
||||
export const checkAuth = ({ context }, user, roles: string[]): boolean => {
|
||||
let headers: any = {};
|
||||
if (context.event) headers = context.event.headers;
|
||||
else if (context.req) headers = context.req.headers;
|
||||
|
||||
if (roles.includes(AccountTypeEnum.public)) return true;
|
||||
|
||||
const authorization: string = headers['x-authorization'] || headers.authorization || headers.Authorization;
|
||||
if (!authorization) {
|
||||
throw newError(2002);
|
||||
}
|
||||
|
||||
if (!user || !user._id) {
|
||||
throw newError(2004, '403');
|
||||
}
|
||||
|
||||
if (roles.length > 0) {
|
||||
roles.push(AccountTypeEnum.admin);
|
||||
user.types.push(AccountTypeEnum.public);
|
||||
|
||||
const rolesToVerif = _.uniq(roles);
|
||||
const userTypes = _.uniq(user.types);
|
||||
|
||||
const intersection = _.intersection(rolesToVerif, userTypes);
|
||||
if (intersection.length > 0) return true;
|
||||
|
||||
// no roles matched, restrict access
|
||||
throw newError(2000, { you: user.types, allowed: roles });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const EngineMiddleware = (init: EngineMiddlewareInput): any => {
|
||||
return createMethodDecorator(async (data, next) => {
|
||||
// console.log('engine', data);
|
||||
const { context, args } = data;
|
||||
let returnError;
|
||||
|
||||
if (init.authorization) {
|
||||
const user = (context as any).ctx.user;
|
||||
try {
|
||||
checkAuth({ context }, user, init.authorization);
|
||||
} catch (error) {
|
||||
returnError = error;
|
||||
}
|
||||
}
|
||||
if (init.apiKey) {
|
||||
try {
|
||||
await checkApiKey({ context });
|
||||
returnError = null;
|
||||
} catch (error) {
|
||||
returnError = error;
|
||||
}
|
||||
}
|
||||
|
||||
if (returnError) throw returnError;
|
||||
|
||||
if (init.checkOrganisation && !(context as any).ctx.organisationId) throw newError(2102, '400');
|
||||
if (init.noOrganisationCheck) (context as any).ctx.noOrganisationCheck = true;
|
||||
if (init.noPermissionCheck) (context as any).ctx.noPermissionCheck = true;
|
||||
|
||||
if (init.validations)
|
||||
for (let index = 0; index < init.validations.length; index++) {
|
||||
const element = init.validations[index];
|
||||
if (!element.inputName) element.inputName = 'input';
|
||||
try {
|
||||
const instance = plainToClass(element.schema, args[element.inputName]);
|
||||
await validateOrReject(instance);
|
||||
} catch (error) {
|
||||
throw newError(3001, error);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
export const inputValidation: MiddlewareFn<any> = async ({ root, args, context, info }, next) => {
|
||||
console.log({ root, args, context, info });
|
||||
return next();
|
||||
};
|
||||
54
lib/seed/graphql/Request.ts
Normal file
54
lib/seed/graphql/Request.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Field, Int, InputType, ArgsType } from 'type-graphql';
|
||||
import { Min, Max } from 'class-validator';
|
||||
import { DateTabType, RangeType } from '@seed/interfaces/components';
|
||||
import { DateRangeComponent } from '@seed/interfaces/components.dates';
|
||||
|
||||
@InputType()
|
||||
export class GetArgs {
|
||||
@Field(() => Int)
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit: number;
|
||||
|
||||
@Field(() => Int)
|
||||
@Min(0)
|
||||
skip: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class GetManyArgs {
|
||||
@Field(() => [String], { nullable: true })
|
||||
_ids?: string[];
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
search?: string;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
afterCreatedAt?: Date;
|
||||
@Field(() => Date, { nullable: true })
|
||||
afterUpdatedAt?: Date;
|
||||
|
||||
@Field(() => GetArgs, { nullable: true })
|
||||
pagination?: GetArgs;
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class GetManyArgsWithDateRange extends GetManyArgs {
|
||||
@Field({ nullable: true })
|
||||
startDate?: Date;
|
||||
|
||||
@Field({ nullable: true })
|
||||
endDate?: Date;
|
||||
|
||||
@Field(() => RangeType, { nullable: true })
|
||||
rangeType?: RangeType;
|
||||
|
||||
@Field(() => DateTabType, { nullable: true })
|
||||
dateTabType?: DateTabType;
|
||||
|
||||
@Field(() => DateRangeComponent, { nullable: true })
|
||||
dateRange?: DateRangeComponent;
|
||||
}
|
||||
102
lib/seed/graphql/Server.ts
Normal file
102
lib/seed/graphql/Server.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { nullToUndefined } from '@seed/helpers/Utils';
|
||||
import { ApolloServer } from 'apollo-server-lambda';
|
||||
import { buildSchema } from 'type-graphql';
|
||||
import { ctxMiddleware, authMiddleware, errorMiddleware, complexityMiddleware } from './Middleware';
|
||||
|
||||
import { SettingsCache } from './Settings';
|
||||
|
||||
export async function createServer(resolversEntry: any): Promise<ApolloServer> {
|
||||
const schema = await buildSchema({
|
||||
resolvers: resolversEntry,
|
||||
authChecker: authMiddleware,
|
||||
validate: false,
|
||||
});
|
||||
|
||||
const server = new ApolloServer({
|
||||
// typeDefs,
|
||||
// resolvers,
|
||||
schema,
|
||||
formatError: errorMiddleware,
|
||||
formatResponse: (response): any => {
|
||||
return nullToUndefined(response);
|
||||
},
|
||||
context: ctxMiddleware,
|
||||
tracing: true,
|
||||
plugins: [
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
requestDidStart: () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
didResolveOperation({ request, document }) {
|
||||
return complexityMiddleware(schema, request.variables, document);
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
return server;
|
||||
}
|
||||
|
||||
export class ServerInstance {
|
||||
private static instance: ServerInstance;
|
||||
public handler!: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
public static async getInstance(resolversEntry?: any): Promise<ServerInstance> {
|
||||
const settingsI = SettingsCache.getInstance();
|
||||
await settingsI.refreshCache();
|
||||
|
||||
if (!this.instance) {
|
||||
const instance = new ServerInstance();
|
||||
|
||||
try {
|
||||
// let { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
|
||||
// resolvers: resolversEntry,
|
||||
// authChecker: authMiddleware,
|
||||
// });
|
||||
|
||||
// typeDefs = typeDefs.replace('scalar Upload', 'scalar UploadFix');
|
||||
// resolvers = resolvers;
|
||||
|
||||
const server = await createServer(resolversEntry);
|
||||
instance.handler = server.createHandler({
|
||||
cors: {
|
||||
origin: '*',
|
||||
credentials: true,
|
||||
allowedHeaders: 'Content-Type,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,organisationid',
|
||||
},
|
||||
});
|
||||
console.log(`[GraphQL] Using new server in ${process.env.NODE_ENV}`);
|
||||
this.instance = instance;
|
||||
} catch (error) {
|
||||
console.log('[GRAPH ERROR]', error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
console.log(`[GraphQL] Using same server in ${process.env.NODE_ENV}`);
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public run(event: AWSLambda.APIGatewayProxyEvent, context: AWSLambda.Context): Promise<any> {
|
||||
try {
|
||||
console.log('BEGIN ACTION', event.body ? JSON.parse(event.body) : 'NO BODY');
|
||||
} catch (error) {
|
||||
console.log('BEGIN ACTION - Not JSON');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (error: any, body: unknown): void => {
|
||||
console.log('END ACTION');
|
||||
if (error) {
|
||||
console.error(error);
|
||||
reject(error);
|
||||
} else resolve(body);
|
||||
};
|
||||
|
||||
this.handler(event, context, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
202
lib/seed/graphql/Settings.ts
Normal file
202
lib/seed/graphql/Settings.ts
Normal file
@ -0,0 +1,202 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
import { DateTime } from 'luxon';
|
||||
import _ from 'lodash';
|
||||
|
||||
import env from '@config/.env.json';
|
||||
import packageFile from 'package.json';
|
||||
|
||||
import { SettingsType } from '@seed/interfaces/components';
|
||||
|
||||
import { ErrorsConfig, NotificationEnum } from '@config/config';
|
||||
import { EngineNotificationEnum } from '@seed/interfaces/components.notifications';
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
import { promiseAll } from '@seed/helpers/Utils';
|
||||
import DB from '@seed/services/database/DBService';
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export interface GetSettingsOutput {
|
||||
env: any[];
|
||||
errors: any[];
|
||||
emails: any[];
|
||||
}
|
||||
|
||||
export interface EngineServerCacheInfo {
|
||||
emailsContent: {
|
||||
[key in NotificationEnum | EngineNotificationEnum]: any;
|
||||
};
|
||||
errorsContent: {
|
||||
[key: string]: {
|
||||
message: string;
|
||||
translations?: {
|
||||
subject?: TranslatableComponent;
|
||||
body?: TranslatableComponent;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const getSettingsFromDB = async (): Promise<GetSettingsOutput> => {
|
||||
const settings = (await await (await DB.getInstance()).db.collection('settings').find().toArray()) as (any | any | any)[];
|
||||
|
||||
// filtering client side because not too much data and no need to index db
|
||||
return {
|
||||
env: _.filter(settings, { type: SettingsType.env }) as any[],
|
||||
errors: _.filter(settings, { type: SettingsType.errors }) as any[],
|
||||
emails: _.filter(settings, { type: SettingsType.emails }) as any[],
|
||||
};
|
||||
};
|
||||
|
||||
export const syncEnvFromDB = (currentEnvInDB: GetSettingsOutput): void => {
|
||||
// update env
|
||||
currentEnvInDB.env.map((env) => {
|
||||
process.env[env.key] = env.value;
|
||||
});
|
||||
};
|
||||
|
||||
export const initEnvironement = async (env: any): Promise<GetSettingsOutput> => {
|
||||
const currentEnv = process.env.NODE_ENV && env[process.env.NODE_ENV] ? env[process.env.NODE_ENV] : env.default;
|
||||
|
||||
for (const k in currentEnv) {
|
||||
process.env[k] = currentEnv[k];
|
||||
}
|
||||
|
||||
// Init also Package.json
|
||||
process.env.awsRegion = packageFile.devops.awsRegion;
|
||||
|
||||
const currentSettings = await getSettingsFromDB();
|
||||
syncEnvFromDB(currentSettings);
|
||||
|
||||
// Force the local to be > DB
|
||||
if (process.env.NODE_ENV == 'local') {
|
||||
for (const k in currentEnv) {
|
||||
process.env[k] = currentEnv[k];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Server - Env] Loaded');
|
||||
|
||||
return currentSettings;
|
||||
};
|
||||
|
||||
export class SettingsCache {
|
||||
private static instance: SettingsCache;
|
||||
private lastCacheDate: DateTime;
|
||||
public cache: EngineServerCacheInfo;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): SettingsCache {
|
||||
if (!this.instance) {
|
||||
this.instance = new SettingsCache();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public async refreshCache(): Promise<void> {
|
||||
// check if needs to refresh the cache
|
||||
if (!this.lastCacheDate || this.lastCacheDate.diff(DateTime.local(), 'minutes').minutes > 5) {
|
||||
const settings = await initEnvironement(env);
|
||||
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
// Map for errors
|
||||
const errorsContent = await mapErrorContent(settings, promises);
|
||||
|
||||
// Map for emails
|
||||
const emailsContent: {
|
||||
[key in NotificationEnum | EngineNotificationEnum]: any;
|
||||
} = await mapEmailSettings(settings, promises);
|
||||
|
||||
this.cache = {
|
||||
errorsContent,
|
||||
emailsContent,
|
||||
};
|
||||
this.lastCacheDate = DateTime.local();
|
||||
|
||||
if (promises.length > 0) {
|
||||
console.log('Creating defaut in DB');
|
||||
await promiseAll(promises);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async function mapErrorContent(settings: GetSettingsOutput, newData: Promise<any>[]) {
|
||||
const errorsContent: {
|
||||
[key: string]: {
|
||||
message: string;
|
||||
translations?: {
|
||||
subject?: TranslatableComponent;
|
||||
body?: TranslatableComponent;
|
||||
};
|
||||
};
|
||||
} = {};
|
||||
for (const key in ErrorsConfig) {
|
||||
if (Object.prototype.hasOwnProperty.call(ErrorsConfig, key)) {
|
||||
const errIndex = _.findIndex(settings.errors, { key: key });
|
||||
errorsContent[key] = {
|
||||
message: ErrorsConfig[key],
|
||||
};
|
||||
if (errIndex === -1) {
|
||||
console.error('[SETTINGS - ERROR] Creating default', key);
|
||||
newData.push(
|
||||
(await DB.getInstance()).db.collection('settings').insertOne({
|
||||
key,
|
||||
type: SettingsType.errors,
|
||||
value: ErrorsConfig[key],
|
||||
body: new TranslatableComponent(),
|
||||
_id: uuid(),
|
||||
r: ['admin'],
|
||||
w: ['admin'],
|
||||
d: ['admin'],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
errorsContent[key].translations = {
|
||||
subject: settings.errors[errIndex].subject,
|
||||
body: settings.errors[errIndex].body,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorsContent;
|
||||
}
|
||||
|
||||
async function mapEmailSettings(settings: GetSettingsOutput, newData: Promise<any>[]) {
|
||||
const emails = _.groupBy(settings.emails, 'key');
|
||||
const emailsContent: {
|
||||
[key in NotificationEnum | EngineNotificationEnum]: any;
|
||||
} = {} as any;
|
||||
|
||||
const enumConcat = [...Object.keys(NotificationEnum), ...Object.keys(EngineNotificationEnum)];
|
||||
|
||||
enumConcat.forEach(async (element) => {
|
||||
if (!emails[element]) {
|
||||
console.error('[SETTINGS - EMAIL] Creating default', element);
|
||||
newData.push(
|
||||
(await DB.getInstance()).db.collection('settings').insertOne({
|
||||
key: element,
|
||||
type: SettingsType.emails,
|
||||
body: new TranslatableComponent(),
|
||||
custom: false,
|
||||
fromEmail: '',
|
||||
fromName: '',
|
||||
replyToEmail: '',
|
||||
r: ['admin'],
|
||||
w: ['admin'],
|
||||
d: ['admin'],
|
||||
_id: uuid(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
);
|
||||
} else emailsContent[element] = emails[element];
|
||||
});
|
||||
return emailsContent;
|
||||
}
|
||||
106
lib/seed/graphql/baseModels/BaseContentModel.ts
Normal file
106
lib/seed/graphql/baseModels/BaseContentModel.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { ObjectType, Field, ID, ArgsType, InputType, Int } from 'type-graphql';
|
||||
|
||||
import { BaseGraphModel } from '@seed/graphql/BaseGraphModel';
|
||||
import ImageComponent, { SEOField } from '@seed/interfaces/components';
|
||||
import { GetArgs, GetManyArgs } from '@seed/graphql/Request';
|
||||
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
import BaseSEOModel, { NewBaseSEOInput, EditBaseSEOInput } from './BaseSeoModel';
|
||||
import CategoryModel from '@services/module-cms/functions/categories/category.model';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
|
||||
const permissions: Permission = {
|
||||
c: [AccountTypeEnum.admin],
|
||||
r: [AccountTypeEnum.public],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
};
|
||||
|
||||
@ObjectType()
|
||||
export default class BaseContentModel extends BaseSEOModel {
|
||||
public constructor(collectionName?: string, perm?: Permission) {
|
||||
const cName = collectionName ? collectionName : 'articles';
|
||||
const permission = perm ? perm : permissions;
|
||||
super(cName, permission);
|
||||
}
|
||||
|
||||
@Field()
|
||||
published?: boolean;
|
||||
@Field({ nullable: true })
|
||||
publicationDate?: Date;
|
||||
|
||||
@Field({ nullable: true })
|
||||
private?: boolean;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
categoryIds?: string[];
|
||||
@Field(() => [CategoryModel], { nullable: 'itemsAndList' })
|
||||
getCategories?: CategoryModel[];
|
||||
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
downloadableRessources?: ImageComponent[];
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
testimonials?: TranslatableComponent;
|
||||
|
||||
searchOptions(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class BaseContentArgs extends GetManyArgs {}
|
||||
|
||||
@InputType()
|
||||
export class NewBaseContentInput extends NewBaseSEOInput implements Partial<BaseContentModel> {
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
teaser?: TranslatableComponent;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
categoryIds?: string[];
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
downloadableRessources?: ImageComponent[];
|
||||
|
||||
published = false;
|
||||
|
||||
@Field({ nullable: true })
|
||||
private?: boolean;
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
testimonials?: TranslatableComponent;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class EditBaseContentInput extends EditBaseSEOInput implements Partial<BaseContentModel> {
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
teaser?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
content?: TranslatableComponent;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
categoryIds?: string[];
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
downloadableRessources?: ImageComponent[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
private?: boolean;
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
testimonials?: TranslatableComponent;
|
||||
}
|
||||
103
lib/seed/graphql/baseModels/BaseSeoModel.ts
Normal file
103
lib/seed/graphql/baseModels/BaseSeoModel.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { ObjectType, Field, ID, ArgsType, InputType, Int } from 'type-graphql';
|
||||
|
||||
import { BaseGraphModel } from '@seed/graphql/BaseGraphModel';
|
||||
import ImageComponent, { SEOField } from '@seed/interfaces/components';
|
||||
import { GetArgs, GetManyArgs } from '@seed/graphql/Request';
|
||||
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
|
||||
const permissions: Permission = {
|
||||
c: [AccountTypeEnum.admin],
|
||||
r: [AccountTypeEnum.public],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
};
|
||||
|
||||
@ObjectType()
|
||||
export default class BaseSEOModel extends BaseGraphModel {
|
||||
public constructor(collectionName?: string, perm?: Permission) {
|
||||
const cName = collectionName ? collectionName : 'articles';
|
||||
const permission = perm ? perm : permissions;
|
||||
super({
|
||||
// ...init,
|
||||
collectionName: cName,
|
||||
permissions: permission,
|
||||
});
|
||||
}
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
teaser?: TranslatableComponent;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
extraImages?: ImageComponent[];
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
content?: TranslatableComponent;
|
||||
|
||||
@Field(() => SEOField, { nullable: true })
|
||||
seo?: SEOField;
|
||||
@Field({ nullable: true })
|
||||
urls?: TranslatableComponent;
|
||||
|
||||
searchOptions(): string[] {
|
||||
return [];
|
||||
}
|
||||
filterOptions(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class BaseSEOArgs extends GetManyArgs {}
|
||||
|
||||
@InputType()
|
||||
export class NewBaseSEOInput implements Partial<BaseSEOModel> {
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
teaser?: TranslatableComponent;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
content?: TranslatableComponent;
|
||||
|
||||
@Field(() => SEOField, { nullable: true })
|
||||
seo?: SEOField;
|
||||
@Field({ nullable: true })
|
||||
urls?: TranslatableComponent;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class EditBaseSEOInput implements Partial<BaseSEOModel> {
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title?: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
teaser?: TranslatableComponent;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
content?: TranslatableComponent;
|
||||
|
||||
@Field(() => SEOField, { nullable: true })
|
||||
seo?: SEOField;
|
||||
@Field({ nullable: true })
|
||||
urls?: TranslatableComponent;
|
||||
}
|
||||
107
lib/seed/graphql/baseModels/BaseSeoSimpleModel.ts
Normal file
107
lib/seed/graphql/baseModels/BaseSeoSimpleModel.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { ObjectType, Field, ID, ArgsType, InputType, Int } from 'type-graphql';
|
||||
|
||||
import { BaseGraphModel } from '@seed/graphql/BaseGraphModel';
|
||||
import ImageComponent, { SEOSimpleField } from '@seed/interfaces/components';
|
||||
import { GetArgs, GetManyArgs } from '@seed/graphql/Request';
|
||||
|
||||
import { Permission } from '@seed/interfaces/permission';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
|
||||
const permissions: Permission = {
|
||||
c: [AccountTypeEnum.admin],
|
||||
r: [AccountTypeEnum.public],
|
||||
w: [AccountTypeEnum.admin],
|
||||
d: [AccountTypeEnum.admin],
|
||||
};
|
||||
|
||||
@ObjectType()
|
||||
export default class BaseSEOSimpleModel extends BaseGraphModel {
|
||||
public constructor(collectionName?: string, perm?: Permission) {
|
||||
const cName = collectionName ? collectionName : 'articles';
|
||||
const permission = perm ? perm : permissions;
|
||||
super({
|
||||
// ...init,
|
||||
collectionName: cName,
|
||||
permissions: permission,
|
||||
});
|
||||
}
|
||||
|
||||
@Field({ nullable: true })
|
||||
title?: string;
|
||||
@Field({ nullable: true })
|
||||
teaser?: string;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
extraImages?: ImageComponent[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
content?: string;
|
||||
|
||||
@Field(() => SEOSimpleField, { nullable: true })
|
||||
seo?: SEOSimpleField;
|
||||
@Field({ nullable: true })
|
||||
urls?: string;
|
||||
|
||||
searchOptions(): string[] {
|
||||
return [];
|
||||
}
|
||||
filterOptions(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class BaseSEOArgs extends GetManyArgs {}
|
||||
|
||||
@InputType()
|
||||
export class NewBaseSEOSimpleInput implements Partial<BaseSEOSimpleModel> {
|
||||
@Field({ nullable: true })
|
||||
title?: string;
|
||||
@Field({ nullable: true })
|
||||
teaser?: string;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
extraImages?: ImageComponent[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
content?: string;
|
||||
|
||||
@Field(() => SEOSimpleField, { nullable: true })
|
||||
seo?: SEOSimpleField;
|
||||
@Field({ nullable: true })
|
||||
urls?: string;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class EditBaseSEOSimpleInput implements Partial<BaseSEOSimpleModel> {
|
||||
@Field({ nullable: true })
|
||||
title?: string;
|
||||
@Field({ nullable: true })
|
||||
teaser?: string;
|
||||
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
cover?: ImageComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
|
||||
@Field(() => [ImageComponent], { nullable: true })
|
||||
extraImages?: ImageComponent[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
content?: string;
|
||||
|
||||
@Field(() => SEOSimpleField, { nullable: true })
|
||||
seo?: SEOSimpleField;
|
||||
@Field({ nullable: true })
|
||||
urls?: string;
|
||||
}
|
||||
143
lib/seed/graphql/baseResolvers/BasePublicResolver.ts
Normal file
143
lib/seed/graphql/baseResolvers/BasePublicResolver.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { Query, Arg, Int, Resolver, Authorized, Ctx, Args, Mutation, ClassType } from 'type-graphql';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { getOneGeneric, getManyGenericWithArgs, addOneGeneric, editOneGeneric, deleteOneGeneric, getCountGeneric } from '../BaseService';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { totalPublic } from '../Middleware';
|
||||
|
||||
export const createBasePublicResolver = <T extends ClassType<any>, ARGS extends ClassType<any>>(
|
||||
domain: string,
|
||||
objectTypeCls: T,
|
||||
argsType: ARGS,
|
||||
pub = false,
|
||||
): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BasePublicResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => objectTypeCls, { name: `${domain}GetOne` })
|
||||
@totalPublic(pub)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [objectTypeCls], { name: `${domain}GetMany` })
|
||||
@totalPublic(pub)
|
||||
async getMany(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new objectTypeCls();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
|
||||
@Query(() => Number, { name: `${domain}GetCount` })
|
||||
@totalPublic(pub)
|
||||
async getCount(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<number> {
|
||||
const model = new objectTypeCls();
|
||||
return getCountGeneric(model, args, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BasePublicResolver;
|
||||
};
|
||||
|
||||
export const createBaseMyResolver = <T extends ClassType<any>, ARGS extends ClassType<any>>(
|
||||
domain: string,
|
||||
objectTypeCls: T,
|
||||
argsType: ARGS,
|
||||
): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseMyResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => objectTypeCls, { name: `${domain}GetOneMine` })
|
||||
@Authorized()
|
||||
async getMineOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [objectTypeCls], { name: `${domain}GetManyMine` })
|
||||
@Authorized()
|
||||
async getMine(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new objectTypeCls();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseMyResolver;
|
||||
};
|
||||
|
||||
export const createBaseReadOnlyResolver = <T extends ClassType<any>, ARGS extends ClassType<any>>(
|
||||
domain: string,
|
||||
objectTypeCls: T,
|
||||
argsType: ARGS,
|
||||
auth: AccountTypeEnum[],
|
||||
): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseReadOnlyResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => objectTypeCls, { name: `${domain}GetOne` })
|
||||
@Authorized(auth)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [objectTypeCls], { name: `${domain}GetMany` })
|
||||
@Authorized(auth)
|
||||
async getMany(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new objectTypeCls();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseReadOnlyResolver;
|
||||
};
|
||||
100
lib/seed/graphql/baseResolvers/BaseResolver.ts
Normal file
100
lib/seed/graphql/baseResolvers/BaseResolver.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Query, Arg, Int, Resolver, Authorized, Ctx, Args, Mutation, ClassType } from 'type-graphql';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { getOneGeneric, getManyGenericWithArgs, addOneGeneric, editOneGeneric, deleteOneGeneric, getCountGeneric } from '../BaseService';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { checkOrganisation as CheckOrganisation } from '../Middleware';
|
||||
|
||||
export const createBaseResolver = <T extends ClassType<any>, ARGS extends ClassType<any>, NEW extends ClassType<any>, EDIT extends ClassType<any>>(
|
||||
domain: string,
|
||||
objectTypeCls: T,
|
||||
argsType: ARGS,
|
||||
newInput: NEW,
|
||||
editInput: EDIT,
|
||||
auth: AccountTypeEnum[],
|
||||
organisationCheck = false,
|
||||
): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => objectTypeCls, { name: `${domain}GetOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [objectTypeCls], { name: `${domain}GetMany` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getMany(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new objectTypeCls();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
|
||||
@Query(() => Number, { name: `${domain}GetCount` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getCount(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<number> {
|
||||
const model = new objectTypeCls();
|
||||
return getCountGeneric(model, args, ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
||||
████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
||||
██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
||||
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
||||
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||
*/
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}AddOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async addOne(@Arg('input', (type) => newInput) input: NEW, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onCreate) await model.onCreate(input);
|
||||
|
||||
return addOneGeneric(model, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}EditOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async editOne(@Arg('id') id: string, @Arg('input', (type) => editInput) input: EDIT, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onUpdate) await model.onUpdate(input);
|
||||
|
||||
return editOneGeneric(model, id, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}DeleteOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async deleteOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onDelete) await model.onDelete();
|
||||
|
||||
return deleteOneGeneric(model, id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseResolver;
|
||||
};
|
||||
97
lib/seed/graphql/genericResolvers/BaseGenericResolver.ts
Normal file
97
lib/seed/graphql/genericResolvers/BaseGenericResolver.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { Query, Arg, Resolver, Ctx, Args, Mutation, ClassType } from 'type-graphql';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { getOneGeneric, getManyGenericWithArgs, addOneGeneric, editOneGeneric, deleteOneGeneric, getCountGeneric } from '../BaseService';
|
||||
import { EngineMiddleware, EngineMiddlewareInput } from '../MiddlewareV2';
|
||||
|
||||
export const createGenericQueryResolver = <T extends ClassType<any>, ARGS extends ClassType<any>>(init: {
|
||||
domain: string;
|
||||
modelName: T;
|
||||
argsType: ARGS;
|
||||
customMiddlewareInput: EngineMiddlewareInput;
|
||||
}): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseQueryResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => init.modelName, { name: `${init.domain}GetOne` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new init.modelName();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [init.modelName], { name: `${init.domain}GetMany` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async getMany(@Args(() => init.argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new init.modelName();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
|
||||
@Query(() => Number, { name: `${init.domain}GetCount` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async getCount(@Args(() => init.argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<number> {
|
||||
const model = new init.modelName();
|
||||
return getCountGeneric(model, args, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseQueryResolver;
|
||||
};
|
||||
|
||||
export const createGenericMutationResolver = <T extends ClassType<any>, NEW extends ClassType<any>, EDIT extends ClassType<any>>(init: {
|
||||
domain: string;
|
||||
modelName: T;
|
||||
newInput: NEW;
|
||||
editInput: EDIT;
|
||||
customMiddlewareInput: EngineMiddlewareInput;
|
||||
}): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseMutationResolver {
|
||||
/*
|
||||
███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
||||
████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
||||
██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
||||
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
||||
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||
*/
|
||||
@Mutation(() => init.modelName, { name: `${init.domain}AddOne` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async addOne(@Arg('input', () => init.newInput) input: NEW, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new init.modelName();
|
||||
return addOneGeneric(model, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => init.modelName, { name: `${init.domain}EditOne` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async editOne(@Arg('id') id: string, @Arg('input', () => init.editInput) input: EDIT, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new init.modelName();
|
||||
return editOneGeneric(model, id, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => init.modelName, { name: `${init.domain}DeleteOne` })
|
||||
@EngineMiddleware(init.customMiddlewareInput)
|
||||
async deleteOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new init.modelName();
|
||||
return deleteOneGeneric(model, id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseMutationResolver;
|
||||
};
|
||||
100
lib/seed/graphql/genericResolvers/BaseResolver.ts
Normal file
100
lib/seed/graphql/genericResolvers/BaseResolver.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Query, Arg, Int, Resolver, Authorized, Ctx, Args, Mutation, ClassType } from 'type-graphql';
|
||||
import { ApolloContext } from '@seed/interfaces/context';
|
||||
import { getOneGeneric, getManyGenericWithArgs, addOneGeneric, editOneGeneric, deleteOneGeneric, getCountGeneric } from '../BaseService';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { checkOrganisation as CheckOrganisation } from '../Middleware';
|
||||
|
||||
export const createBaseResolver = <T extends ClassType<any>, ARGS extends ClassType<any>, NEW extends ClassType<any>, EDIT extends ClassType<any>>(
|
||||
domain: string,
|
||||
objectTypeCls: T,
|
||||
argsType: ARGS,
|
||||
newInput: NEW,
|
||||
editInput: EDIT,
|
||||
auth: AccountTypeEnum[],
|
||||
organisationCheck = false,
|
||||
): any => {
|
||||
@Resolver({ isAbstract: true })
|
||||
abstract class BaseResolver {
|
||||
/*
|
||||
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
||||
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
||||
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
||||
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
||||
██║ ██║███████╗███████╗██████╔╝███████║
|
||||
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
||||
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
||||
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
||||
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
||||
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
||||
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
@Query(() => objectTypeCls, { name: `${domain}GetOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
return getOneGeneric(model, id, ctx);
|
||||
}
|
||||
|
||||
@Query(() => [objectTypeCls], { name: `${domain}GetMany` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getMany(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<T[]> {
|
||||
const model = new objectTypeCls();
|
||||
return getManyGenericWithArgs(model, args, ctx);
|
||||
}
|
||||
|
||||
@Query(() => Number, { name: `${domain}GetCount` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async getCount(@Args((type) => argsType) args: ARGS, @Ctx() ctx: ApolloContext): Promise<number> {
|
||||
const model = new objectTypeCls();
|
||||
return getCountGeneric(model, args, ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
||||
████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
||||
██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
||||
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
||||
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
||||
*/
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}AddOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async addOne(@Arg('input', (type) => newInput) input: NEW, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onCreate) await model.onCreate(input);
|
||||
|
||||
return addOneGeneric(model, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}EditOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async editOne(@Arg('id') id: string, @Arg('input', (type) => editInput) input: EDIT, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onUpdate) await model.onUpdate(input);
|
||||
|
||||
return editOneGeneric(model, id, input, ctx);
|
||||
}
|
||||
|
||||
@Mutation(() => objectTypeCls, { name: `${domain}DeleteOne` })
|
||||
@Authorized(auth)
|
||||
@CheckOrganisation(organisationCheck)
|
||||
async deleteOne(@Arg('id') id: string, @Ctx() ctx: ApolloContext): Promise<T> {
|
||||
const model = new objectTypeCls();
|
||||
if (model.onDelete) await model.onDelete();
|
||||
|
||||
return deleteOneGeneric(model, id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return BaseResolver;
|
||||
};
|
||||
12
lib/seed/helpers/Context.ts
Normal file
12
lib/seed/helpers/Context.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApolloContextLoadersOnly } from '@seed/interfaces/context';
|
||||
import Loaders from '@src/__indexes/__loaders';
|
||||
|
||||
export const createContext = async (): Promise<ApolloContextLoadersOnly> => {
|
||||
return {
|
||||
event: {} as any,
|
||||
context: {} as any,
|
||||
ctx: {
|
||||
loaders: new Loaders(),
|
||||
},
|
||||
};
|
||||
};
|
||||
43
lib/seed/helpers/Error.ts
Normal file
43
lib/seed/helpers/Error.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ErrorsConfig } from '@config/config';
|
||||
|
||||
import { SettingsCache } from '@seed/graphql/Settings';
|
||||
import { ApolloError } from 'apollo-server-lambda';
|
||||
import { UtilStringHelper } from './Utils.string';
|
||||
|
||||
type ErrorKeys = keyof typeof ErrorsConfig;
|
||||
|
||||
const utilStringHelper = new UtilStringHelper();
|
||||
|
||||
export const newError = (code: ErrorKeys, data?: any): ApolloError => {
|
||||
const errorConfig = SettingsCache.getInstance().cache.errorsContent;
|
||||
|
||||
const message = errorConfig[code].message;
|
||||
const settings = errorConfig[code].translations;
|
||||
|
||||
const translations = {
|
||||
subject: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
if (settings) {
|
||||
if (settings.body) {
|
||||
for (const key in settings.body) {
|
||||
if (Object.prototype.hasOwnProperty.call(settings.body, key)) {
|
||||
translations.body[key] = utilStringHelper.interpolate(settings.body[key], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (settings.subject) {
|
||||
for (const key in settings.subject) {
|
||||
if (Object.prototype.hasOwnProperty.call(settings.subject, key)) {
|
||||
translations.subject[key] = utilStringHelper.interpolate(settings.subject[key], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ApolloError(utilStringHelper.interpolate(message, data), code.toString(), {
|
||||
translations,
|
||||
data,
|
||||
});
|
||||
};
|
||||
64
lib/seed/helpers/FetchHelper.ts
Normal file
64
lib/seed/helpers/FetchHelper.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import fetch from 'node-fetch';
|
||||
import querystring from 'querystring';
|
||||
|
||||
// convert object to a query string
|
||||
export const apiCall = async (url: string, method: 'POST' | 'PUT' | 'PATCH' | 'GET' | 'DELETE', headers?: any, body?: any): Promise<any> => {
|
||||
const q: any = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (headers) q.headers = headers;
|
||||
if (body) {
|
||||
if (typeof body === 'string') q.body = body;
|
||||
else q.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, q);
|
||||
|
||||
const rs = await response.json();
|
||||
if (!response.ok) throw { error: rs };
|
||||
|
||||
return rs;
|
||||
};
|
||||
|
||||
export const fetchCall = async (init: {
|
||||
url: string;
|
||||
method: 'POST' | 'PUT' | 'PATCH' | 'GET' | 'DELETE';
|
||||
query?: any;
|
||||
headers?: any;
|
||||
body?: any;
|
||||
raw?: boolean;
|
||||
}): Promise<any> => {
|
||||
let { url } = init;
|
||||
const { method, query, headers, body, raw } = init;
|
||||
const q: any = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (query) url = url + '?' + querystring.stringify(query, undefined, undefined, { encodeURIComponent: encodeURIComponent });
|
||||
if (headers) q.headers = headers;
|
||||
if (body) {
|
||||
if (typeof body === 'string') q.body = body;
|
||||
else q.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
console.log('fetchCall', url);
|
||||
|
||||
const response = await fetch(url, q);
|
||||
|
||||
let rs;
|
||||
try {
|
||||
rs = await response.json();
|
||||
} catch (error) {
|
||||
rs = await response.text();
|
||||
}
|
||||
if (!response.ok) throw { error: rs };
|
||||
|
||||
return rs;
|
||||
};
|
||||
29
lib/seed/helpers/Request.ts
Normal file
29
lib/seed/helpers/Request.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { GetArgs } from '@seed/graphql/Request';
|
||||
|
||||
const DEFAULT_LIMIT = 100;
|
||||
const DEFAULT_OFFSET = 0;
|
||||
|
||||
export interface QueryPagination {
|
||||
limit: number;
|
||||
skip: number;
|
||||
sort?: {
|
||||
[k: string]: 1 | -1;
|
||||
};
|
||||
}
|
||||
|
||||
export const parsePaginationOptions = (obj?: GetArgs): QueryPagination => {
|
||||
const limit = obj ? (obj.limit ? obj.limit : DEFAULT_LIMIT) : DEFAULT_LIMIT;
|
||||
const skip = obj ? (obj.skip ? obj.skip : DEFAULT_OFFSET) : DEFAULT_OFFSET;
|
||||
|
||||
if (obj && obj.sort) {
|
||||
const sort = {};
|
||||
const sortArray = ('' + obj.sort).split(',');
|
||||
for (let index = 0; index < sortArray.length; index++) {
|
||||
const sortTmp = ('' + obj.sort).split(' ');
|
||||
if (sortTmp[1].toLowerCase() === 'asc') (sort as any)[sortTmp[0]] = 1;
|
||||
if (sortTmp[1].toLowerCase() === 'desc') (sort as any)[sortTmp[0]] = -1;
|
||||
}
|
||||
return { limit, skip, sort };
|
||||
}
|
||||
return { limit, skip };
|
||||
};
|
||||
28
lib/seed/helpers/StringHelper.ts
Normal file
28
lib/seed/helpers/StringHelper.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const getAllTranslationsToString = (str: string, input: TranslatableComponent): string => {
|
||||
for (const key in input) {
|
||||
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
||||
str = str + '#' + input[key].toLowerCase();
|
||||
}
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
export const getSearchString = (...inputs: (string | TranslatableComponent)[]): string => {
|
||||
let str = '';
|
||||
|
||||
for (let index = 0; index < inputs.length; index++) {
|
||||
const element = inputs[index];
|
||||
if (_.isString(element)) str = str + '#' + element.toLowerCase();
|
||||
else str = getAllTranslationsToString(str, element);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
//escape regex => for special characters in email
|
||||
export const escapeRegex = (regex) => {
|
||||
return regex.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
}
|
||||
44
lib/seed/helpers/TestsHelper.ts
Normal file
44
lib/seed/helpers/TestsHelper.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
export const graphRequest = async (query, option): Promise<any> => {
|
||||
console.log('[TESTS] Query', query);
|
||||
|
||||
let params;
|
||||
if (option.authHeader)
|
||||
params = {
|
||||
body: JSON.stringify({ query }),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: option.authHeader,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
else
|
||||
params = {
|
||||
body: JSON.stringify({ query }),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(option.url, params);
|
||||
if (!response.ok) {
|
||||
return new Promise((resolve, reject) => {
|
||||
response
|
||||
.text()
|
||||
.then((text) => {
|
||||
try {
|
||||
console.log('[TESTS] Error', JSON.parse(text));
|
||||
reject(JSON.parse(text));
|
||||
} catch (err) {
|
||||
console.log('[TESTS] Error', text);
|
||||
reject(text);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
const json = await response.json();
|
||||
return json.data;
|
||||
};
|
||||
14
lib/seed/helpers/Utils.checks.ts
Normal file
14
lib/seed/helpers/Utils.checks.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
import { newError } from './Error';
|
||||
|
||||
export const isAllThere = (listIds: string[], data: any[], modelName = 'modelName'): void => {
|
||||
const ids = _.map(data, '_id');
|
||||
const nIds = _.difference(listIds, ids);
|
||||
if (nIds.length > 0) throw newError(4041, { id: nIds.join(','), model: modelName });
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
export const hasDuplicates = (array: any[]): boolean => {
|
||||
return _.uniq(array).length !== array.length;
|
||||
};
|
||||
88
lib/seed/helpers/Utils.dates.intervals.ts
Normal file
88
lib/seed/helpers/Utils.dates.intervals.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { DateTimeRangeComponent, AvailabilityComponent, DateRangeComponent, HourComponent } from '@seed/interfaces/components.dates';
|
||||
import _ from 'lodash';
|
||||
import { Interval, DateTime } from 'luxon';
|
||||
|
||||
export function checkIfDatesAreInAvailabilities(
|
||||
dates: DateTimeRangeComponent[],
|
||||
availabilities: AvailabilityComponent,
|
||||
options?: { zone: string },
|
||||
): boolean {
|
||||
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
||||
const intervalsExceptions = getExceptionsIntervalFromAvailabilities(availabilities, zone);
|
||||
let isInException = false;
|
||||
|
||||
if (intervalsExceptions.length > 0) {
|
||||
isInException = _.some(dates, (d) => {
|
||||
return checkIfDateRangeIsInIntervals(d, intervalsExceptions);
|
||||
});
|
||||
}
|
||||
|
||||
const intervalDates = getDatesIntervalFromAvailabilities(availabilities, zone);
|
||||
let isInDates = true;
|
||||
|
||||
if (intervalDates.length > 0) {
|
||||
isInDates = _.every(dates, (d) => {
|
||||
return checkIfDateRangeIsInIntervals(d, intervalDates);
|
||||
});
|
||||
}
|
||||
|
||||
return !isInException && isInDates;
|
||||
}
|
||||
|
||||
function checkIfDateRangeIsInIntervals(dateR: DateTimeRangeComponent, intervals: Interval[]): boolean {
|
||||
const interval = Interval.fromDateTimes(dateR.startDate, dateR.endDate);
|
||||
return _.some(intervals, (i) => i.overlaps(interval));
|
||||
}
|
||||
|
||||
function getExceptionsIntervalFromAvailabilities(availabilities: AvailabilityComponent, zone: string): Interval[] {
|
||||
const exceptions = availabilities.exceptions;
|
||||
const intervalsExceptions: Interval[] = [];
|
||||
|
||||
// Create all ezxceptions intervals
|
||||
if (exceptions) {
|
||||
exceptions.forEach((e) => {
|
||||
createIntervalFromDatesAndHours(e.dates, zone, e.hours, intervalsExceptions);
|
||||
});
|
||||
}
|
||||
|
||||
return intervalsExceptions;
|
||||
}
|
||||
|
||||
function getDatesIntervalFromAvailabilities(availabilities: AvailabilityComponent, zone: string): Interval[] {
|
||||
const dates = availabilities.dates;
|
||||
const hours = availabilities.hours;
|
||||
const datesExceptions: Interval[] = [];
|
||||
|
||||
// Create all ezxceptions dates
|
||||
if (dates) {
|
||||
createIntervalFromDatesAndHours(dates, zone, hours, datesExceptions);
|
||||
}
|
||||
|
||||
return datesExceptions;
|
||||
}
|
||||
|
||||
function createIntervalFromDatesAndHours(dates: DateRangeComponent, zone: string, hours: HourComponent[] | undefined, datesExceptions: Interval[]) {
|
||||
let dateStart = DateTime.fromJSDate(dates.startDate, { zone });
|
||||
const dateEnd = DateTime.fromJSDate(dates.endDate, { zone });
|
||||
|
||||
if (hours) {
|
||||
while (dateStart < dateEnd) {
|
||||
hours.forEach((h) => {
|
||||
const hStart = parseInt(h.from.split(':')[0]);
|
||||
const mStart = parseInt(h.from.split(':')[1]);
|
||||
|
||||
const hEnd = parseInt(h.to.split(':')[0]);
|
||||
const mEnd = parseInt(h.to.split(':')[1]);
|
||||
|
||||
datesExceptions.push(
|
||||
Interval.fromDateTimes(dateStart.set({ hour: hStart, minute: mStart }), dateStart.set({ hour: hEnd, minute: mEnd })),
|
||||
);
|
||||
});
|
||||
dateStart = dateStart.plus({ day: 1 });
|
||||
}
|
||||
|
||||
// if all day
|
||||
} else {
|
||||
datesExceptions.push(Interval.fromDateTimes(DateTime.fromJSDate(dates.startDate, { zone }), DateTime.fromJSDate(dates.endDate, { zone })));
|
||||
}
|
||||
}
|
||||
310
lib/seed/helpers/Utils.dates.ts
Normal file
310
lib/seed/helpers/Utils.dates.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import {
|
||||
AvailabilityComponent,
|
||||
DateRangeComponent,
|
||||
DateTimeRangeComponent,
|
||||
HourComponent,
|
||||
SlotAvailablity,
|
||||
SlotComponent,
|
||||
} from '@seed/interfaces/components.dates';
|
||||
import _ from 'lodash';
|
||||
import { DateTime, Interval } from 'luxon';
|
||||
|
||||
interface hoursRange {
|
||||
startHours: string;
|
||||
startHoursInNumber: number;
|
||||
endHours: string;
|
||||
endHoursInNumber: number;
|
||||
}
|
||||
/*
|
||||
breakTime: [
|
||||
['11:00', '14:00'],
|
||||
['16:00', '18:00'],
|
||||
],
|
||||
*/
|
||||
|
||||
export function getNumberOfWeekendDays(dateBegin: DateTime, dateEnd: DateTime): number {
|
||||
let weekendDays = 0;
|
||||
|
||||
let begin = dateBegin;
|
||||
|
||||
while (begin <= dateEnd) {
|
||||
if (begin.weekday == 6 || begin.weekday == 7) weekendDays = weekendDays + 1;
|
||||
|
||||
begin = begin.plus({ day: 1 });
|
||||
}
|
||||
|
||||
return weekendDays;
|
||||
}
|
||||
|
||||
function isInTimeInterval(slotTime: DateTime, slotInterval: number, timeRange: hoursRange[]): boolean {
|
||||
const startTime = slotTime;
|
||||
const endTime = slotTime.plus({ minutes: slotInterval });
|
||||
|
||||
const startTimeInNumber = startTime.hour * 60 + startTime.minute;
|
||||
const endTimeInNumber = endTime.hour * 60 + endTime.minute;
|
||||
|
||||
const isInInterval = timeRange.some((br) => {
|
||||
return startTimeInNumber < br.endHoursInNumber && endTimeInNumber > br.startHoursInNumber;
|
||||
// return (date.isSameOrAfter(startTime) && date.isSameOrBefore(endTime)) || (date.isSameOrAfter(startTime) && date.isSame(endTime));
|
||||
});
|
||||
|
||||
return isInInterval;
|
||||
}
|
||||
|
||||
export const getMinutesFromTimeInString = (str: string): number => {
|
||||
const timeArray = str.split(':');
|
||||
return parseInt(timeArray[0]) * 60 + parseInt(timeArray[1]);
|
||||
};
|
||||
|
||||
export const getDateNoTZ = (old: Date): Date => {
|
||||
const userTimezoneOffset = old.getTimezoneOffset() * 60000;
|
||||
return new Date(old.getTime() - userTimezoneOffset);
|
||||
};
|
||||
|
||||
export const slotToDatesRange = (slot: SlotComponent): DateRangeComponent[] => {
|
||||
const dates: DateRangeComponent[] = [];
|
||||
const { startDate, endDate, startTime, endTime } = slot;
|
||||
|
||||
const startTimes = startTime.split(':');
|
||||
const endTimes = endTime.split(':');
|
||||
|
||||
let beginDate = DateTime.fromJSDate(startDate).startOf('day');
|
||||
const stopDate = DateTime.fromJSDate(endDate).endOf('day');
|
||||
|
||||
while (beginDate < stopDate) {
|
||||
dates.push({
|
||||
startDate: beginDate
|
||||
.set({ hour: parseInt(startTimes[0]) })
|
||||
.set({ minute: parseInt(startTimes[1]) })
|
||||
.toJSDate(),
|
||||
endDate: beginDate
|
||||
.set({ hour: parseInt(endTimes[0]) })
|
||||
.set({ minute: parseInt(endTimes[1]) })
|
||||
// Remove one seconds for overlapping
|
||||
.minus({ second: 1 })
|
||||
.toJSDate(),
|
||||
});
|
||||
beginDate = beginDate.plus({ day: 1 });
|
||||
}
|
||||
return dates;
|
||||
};
|
||||
|
||||
export const getOneDaySlots = (input: {
|
||||
day: DateTime;
|
||||
dayUnavailability: DateTimeRangeComponent[];
|
||||
slotInfo: string[][];
|
||||
slotInterval: number;
|
||||
slotDuration: number;
|
||||
options?: {
|
||||
zone?: string;
|
||||
};
|
||||
}): string[] => {
|
||||
const { day, dayUnavailability, slotInfo, slotDuration, slotInterval, options } = input;
|
||||
|
||||
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
||||
|
||||
let currentDayMoment = day;
|
||||
|
||||
// Check if currentDay = today
|
||||
const currentDayStart = currentDayMoment.toISODate();
|
||||
const todayStart = DateTime.local().toISODate();
|
||||
|
||||
if (currentDayStart != todayStart) {
|
||||
currentDayMoment = day.startOf('day');
|
||||
}
|
||||
|
||||
const currentDayTimeInNumber = currentDayMoment.hour * 60 + currentDayMoment.minute;
|
||||
|
||||
console.log('current day', currentDayMoment.toISO(), currentDayMoment.weekday);
|
||||
|
||||
// Getting the slots for that day
|
||||
const daySlot = slotInfo[day.weekday - 1];
|
||||
const currentDaySlots: {
|
||||
startTime: DateTime;
|
||||
endTime: DateTime;
|
||||
}[] = [];
|
||||
|
||||
if (daySlot && daySlot.length > 0) {
|
||||
if (_.isArray(daySlot[0])) {
|
||||
_.each(daySlot, (d) => {
|
||||
currentDaySlots.push({
|
||||
startTime: DateTime.fromFormat(d[0], 'HH:mm', { zone }),
|
||||
endTime: DateTime.fromFormat(d[1], 'HH:mm', { zone }),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
currentDaySlots.push({
|
||||
startTime: DateTime.fromFormat(daySlot[0], 'HH:mm', { zone }),
|
||||
endTime: DateTime.fromFormat(daySlot[1], 'HH:mm', { zone }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const thisDaysUnavailabilities: {
|
||||
startHours: string;
|
||||
startHoursInNumber: number;
|
||||
endHours: string;
|
||||
endHoursInNumber: number;
|
||||
}[] = getUnavailabilityOfThisDay(dayUnavailability, currentDayMoment);
|
||||
|
||||
const currentDateSlots: string[] = [];
|
||||
console.log('thisDaysUnavailabilities', thisDaysUnavailabilities);
|
||||
|
||||
currentDaySlots.forEach((element) => {
|
||||
let { startTime, endTime } = element;
|
||||
|
||||
while (startTime < endTime) {
|
||||
if (
|
||||
!(startTime.hour * 60 + startTime.minute < currentDayTimeInNumber) &&
|
||||
!isInTimeInterval(startTime, slotDuration, thisDaysUnavailabilities) &&
|
||||
startTime.hour * 60 + startTime.minute + slotDuration <= endTime.hour * 60 + endTime.minute
|
||||
) {
|
||||
currentDateSlots.push(startTime.toFormat('HH:mm'));
|
||||
}
|
||||
startTime = startTime.plus({ minutes: slotInterval });
|
||||
}
|
||||
});
|
||||
|
||||
return currentDateSlots;
|
||||
};
|
||||
|
||||
export const getMultipleDaysSlots = (input: {
|
||||
datesWanted: DateRangeComponent;
|
||||
datesUnavailable: DateTimeRangeComponent[];
|
||||
slotInterval: number;
|
||||
slotInfo: string[][];
|
||||
slotDuration: number;
|
||||
options?: {
|
||||
zone?: string;
|
||||
};
|
||||
}): SlotAvailablity[] => {
|
||||
const returnSlots: SlotAvailablity[] = [];
|
||||
const { datesWanted, datesUnavailable, slotInterval, slotInfo, slotDuration, options } = input;
|
||||
const { startDate, endDate } = datesWanted;
|
||||
|
||||
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
||||
|
||||
let beginDate = DateTime.fromJSDate(startDate, { zone });
|
||||
const stopDate = DateTime.fromJSDate(endDate, { zone });
|
||||
|
||||
// Two loops
|
||||
// Looping into the wanted dates and keeping an index for the slotInfo
|
||||
while (beginDate < stopDate) {
|
||||
// Get the slot info from the array of array
|
||||
const slots = getOneDaySlots({
|
||||
day: beginDate,
|
||||
dayUnavailability: datesUnavailable,
|
||||
slotInfo,
|
||||
slotInterval,
|
||||
slotDuration,
|
||||
options: { zone },
|
||||
});
|
||||
|
||||
// Adding it to the array
|
||||
returnSlots.push({
|
||||
date: beginDate.toJSDate(),
|
||||
slots,
|
||||
});
|
||||
|
||||
beginDate = beginDate.plus({ day: 1 }).startOf('day');
|
||||
}
|
||||
|
||||
return returnSlots;
|
||||
};
|
||||
|
||||
export function getUnavailabilityOfThisDay(dayUnavailability: DateTimeRangeComponent[], currentDayMoment: DateTime) {
|
||||
const thisDaysUnavailabilities: {
|
||||
startHours: string;
|
||||
startHoursInNumber: number;
|
||||
endHours: string;
|
||||
endHoursInNumber: number;
|
||||
}[] = [];
|
||||
|
||||
dayUnavailability.forEach((element) => {
|
||||
const startDate = element.startDate;
|
||||
const endDate = element.endDate;
|
||||
|
||||
// If there are some reservations in that day or in multiple days
|
||||
const isSameThanStartDate = currentDayMoment.hasSame(startDate, 'day');
|
||||
const isSameThanEndDate = currentDayMoment.hasSame(endDate, 'day');
|
||||
|
||||
const startHours = startDate.toFormat('HH:mm');
|
||||
const endHours = endDate.toFormat('HH:mm');
|
||||
const starts = startHours.split(':');
|
||||
const ends = endHours.split(':');
|
||||
|
||||
const startDateDay = startDate.startOf('day');
|
||||
const endDateDay = startDate.endOf('day');
|
||||
|
||||
const startHoursInNumber = parseInt(starts[0]) * 60 + parseInt(starts[1]);
|
||||
const endHoursInNumber = parseInt(ends[0]) * 60 + parseInt(ends[1]);
|
||||
|
||||
// If the reservation only last a day
|
||||
if (isSameThanStartDate && isSameThanEndDate) {
|
||||
thisDaysUnavailabilities.push({
|
||||
startHours,
|
||||
endHours,
|
||||
startHoursInNumber,
|
||||
endHoursInNumber,
|
||||
});
|
||||
}
|
||||
|
||||
// If the reservation last multiple days
|
||||
else if (currentDayMoment >= startDateDay && currentDayMoment <= endDateDay) {
|
||||
// check if start date
|
||||
if (isSameThanStartDate)
|
||||
thisDaysUnavailabilities.push({
|
||||
startHours,
|
||||
endHours: '23:59',
|
||||
startHoursInNumber,
|
||||
endHoursInNumber: 23 * 60 + 59,
|
||||
});
|
||||
else if (isSameThanEndDate)
|
||||
thisDaysUnavailabilities.push({
|
||||
startHours: '00:00',
|
||||
endHours,
|
||||
startHoursInNumber: 0,
|
||||
endHoursInNumber,
|
||||
});
|
||||
else
|
||||
thisDaysUnavailabilities.push({
|
||||
startHours: '00:00',
|
||||
endHours: '23:59',
|
||||
startHoursInNumber: 0,
|
||||
endHoursInNumber: 23 * 60 + 59,
|
||||
});
|
||||
}
|
||||
});
|
||||
return thisDaysUnavailabilities;
|
||||
}
|
||||
|
||||
export function isTimeSlotInDates(dates: DateTimeRangeComponent[], startTime: string, endTime: string) {
|
||||
const startTimesInMinutes = getMinutesFromTimeInString(startTime);
|
||||
const endTimesInMinutes = getMinutesFromTimeInString(endTime);
|
||||
|
||||
// Check for each date if the minutes are in it
|
||||
return dates.some((br) => {
|
||||
const brStartTimeInMinutes = br.startDate.hour * 60 + br.startDate.minute;
|
||||
const brEndTimeInMinutes = br.endDate.hour * 60 + br.endDate.minute;
|
||||
return startTimesInMinutes < brEndTimeInMinutes && endTimesInMinutes > brStartTimeInMinutes;
|
||||
});
|
||||
}
|
||||
|
||||
export function converDateRangeToDateTime(
|
||||
dates: DateRangeComponent[],
|
||||
options?: {
|
||||
zone?: string;
|
||||
},
|
||||
): DateTimeRangeComponent[] {
|
||||
const results: DateTimeRangeComponent[] = [];
|
||||
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
||||
|
||||
dates.forEach((element) => {
|
||||
results.push({
|
||||
startDate: DateTime.fromJSDate(element.startDate, { zone }),
|
||||
endDate: DateTime.fromJSDate(element.endDate, { zone }),
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
38
lib/seed/helpers/Utils.string.ts
Normal file
38
lib/seed/helpers/Utils.string.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class UtilStringHelper {
|
||||
interpolate = function(
|
||||
string: string,
|
||||
data: any,
|
||||
replace?: {
|
||||
withEol?: boolean;
|
||||
},
|
||||
): any {
|
||||
let finalString = string;
|
||||
|
||||
if (!replace || !replace.withEol) finalString = finalString = finalString.replace(/\s{2,}/g, '');
|
||||
// finalString.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||
// // else
|
||||
// finalString = finalString.replace(/\s{2,}/g, ''); // finalString.replace(/(?:\r\n|\r|\n)/g, '');
|
||||
|
||||
try {
|
||||
const compiled = _.template(finalString);
|
||||
if (finalString.includes('${data.')) return compiled({ data });
|
||||
return compiled(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const names = Object.keys(data);
|
||||
const vals = Object.values(data) as any;
|
||||
for (let index = 0; index < names.length; index++) {
|
||||
finalString = finalString.replace('${' + names[index] + '}', vals[index]);
|
||||
}
|
||||
return finalString;
|
||||
// return new Function(...names, "return String.raw`${this}`")(...vals);
|
||||
}
|
||||
};
|
||||
|
||||
removeTabAndReturn = function(string: string): string {
|
||||
return string.replace(/[\n\t]+/g, '');
|
||||
};
|
||||
}
|
||||
64
lib/seed/helpers/Utils.ts
Normal file
64
lib/seed/helpers/Utils.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import _ from 'lodash';
|
||||
import { UtilStringHelper } from './Utils.string';
|
||||
|
||||
const StringHelper = new UtilStringHelper();
|
||||
|
||||
export const interpolate = StringHelper.interpolate;
|
||||
|
||||
export const sleep = async (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
};
|
||||
|
||||
export const promiseAll = async (promises: Promise<any>[]): Promise<any> => {
|
||||
const results = await Promise.allSettled(promises);
|
||||
|
||||
results.forEach((element) => {
|
||||
if (element.status == 'rejected') console.error('[ASYNC - ERROR]', element.reason);
|
||||
});
|
||||
|
||||
console.log('[PROMISE ALL - DONE]');
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
const isNullBlankOrUndefined = function(o) {
|
||||
return typeof o === 'undefined' || o == null || o === '';
|
||||
};
|
||||
/**
|
||||
* Deep diff between two object, using lodash
|
||||
* @param {Object} object Object compared
|
||||
* @param {Object} base Object to compare with
|
||||
* @return {Object} Return a new object who represent the diff
|
||||
*/
|
||||
export const differenceBetweenObject = (object, base, ignoreBlanks = false): any => {
|
||||
if (!_.isObject(object) || _.isDate(object) || Array.isArray(object)) return object; // special case dates & array
|
||||
return _.transform(object as any, (result, value, key) => {
|
||||
if (!_.isEqual(value, base[key])) {
|
||||
if (ignoreBlanks && isNullBlankOrUndefined(value) && isNullBlankOrUndefined(base[key])) return;
|
||||
result[key] = _.isObject(value) && _.isObject(base[key]) ? differenceBetweenObject(value, base[key]) : value;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const generateCodeNumber = () => {
|
||||
return Math.floor(100000 + Math.random() * 900000);
|
||||
};
|
||||
|
||||
export const clog = (data: any): void => {
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
};
|
||||
|
||||
export const nullToUndefined = (value: any): any | undefined => {
|
||||
if (_.isString(value) || _.isBoolean(value) || _.isDate(value) || _.isFinite(value) || _.isInteger(value)) return value;
|
||||
|
||||
if (_.isArray(value)) return (value as any[]).map(nullToUndefined);
|
||||
|
||||
if (_.isObject(value)) return _.mapValues(value as any, nullToUndefined);
|
||||
|
||||
if (value === null) return undefined;
|
||||
|
||||
return value;
|
||||
};
|
||||
114
lib/seed/interfaces/components.dates.ts
Normal file
114
lib/seed/interfaces/components.dates.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { InputType, Int, Field, ArgsType, ObjectType } from 'type-graphql';
|
||||
import { IsDate, IsMilitaryTime } from 'class-validator';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
@ObjectType()
|
||||
@InputType('ScheduleInput')
|
||||
export class ScheduleComponent {
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
mon?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
tue?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
wed?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
thu?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
fri?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
sat?: string[][];
|
||||
@Field(() => [[String]], { nullable: true })
|
||||
sun?: string[][];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('HoursInput')
|
||||
export class HourComponent {
|
||||
@Field()
|
||||
from: string;
|
||||
@Field()
|
||||
to: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('DateRangeComponentInput')
|
||||
@ArgsType()
|
||||
export class DateRangeComponent {
|
||||
@Field()
|
||||
@IsDate()
|
||||
startDate: Date;
|
||||
@Field()
|
||||
@IsDate()
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
export class DateTimeRangeComponent {
|
||||
startDate: DateTime;
|
||||
endDate: DateTime;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('SlotAvailablityInput')
|
||||
export class SlotAvailablity {
|
||||
@Field()
|
||||
@IsDate()
|
||||
date: Date;
|
||||
|
||||
@Field(() => [String])
|
||||
slots: string[];
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class SlotAvailablityRequest extends DateRangeComponent {
|
||||
@Field(() => Int)
|
||||
interval: number;
|
||||
|
||||
@Field(() => Int)
|
||||
duration: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('SlotComponentInput')
|
||||
export class SlotComponent {
|
||||
@Field()
|
||||
@IsDate()
|
||||
startDate: Date;
|
||||
|
||||
@Field()
|
||||
@IsDate()
|
||||
endDate: Date;
|
||||
|
||||
@Field()
|
||||
@IsMilitaryTime()
|
||||
startTime: string;
|
||||
|
||||
@Field()
|
||||
@IsMilitaryTime()
|
||||
endTime: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('BaseAvailabilityComponentInput')
|
||||
export class BaseAvailabilityComponent {
|
||||
@Field(() => DateRangeComponent)
|
||||
dates: DateRangeComponent;
|
||||
@Field(() => [HourComponent], { nullable: true })
|
||||
hours?: HourComponent[];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('DateExceptionComponentInput')
|
||||
export class DateExceptionComponent extends BaseAvailabilityComponent {
|
||||
@Field({ nullable: true })
|
||||
allDay?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('AvailabilityInput')
|
||||
export class AvailabilityComponent extends BaseAvailabilityComponent {
|
||||
@Field(() => [DateExceptionComponent], { nullable: true })
|
||||
exceptions?: DateExceptionComponent[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
noWeekend?: boolean;
|
||||
}
|
||||
99
lib/seed/interfaces/components.errors.ts
Normal file
99
lib/seed/interfaces/components.errors.ts
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
|
||||
export const seedErrorConfig = {
|
||||
/* HTTP like errors */
|
||||
404: 'The document was not found',
|
||||
4040: 'The ressource ${obj.ressourceId} for the model ${obj.ressourceModel} does not exists',
|
||||
4041: 'The id "${id}" for the model "${model}" was not found',
|
||||
|
||||
/* ************* */
|
||||
/* General & DB */
|
||||
/* ************* */
|
||||
1000: 'General database error',
|
||||
1001: 'The document was not deleted',
|
||||
1002: 'This action is not allowed',
|
||||
|
||||
/* ************* */
|
||||
/* Permissions - Accounts & Organisation */
|
||||
/* ************* */
|
||||
|
||||
// Accounts
|
||||
2000: 'Your permissions level are not allowed',
|
||||
2002: 'Authorization headers are required for this action',
|
||||
2003: 'The account is already registered',
|
||||
2004: 'We have no records of your account in our database',
|
||||
2005: 'There was an error creating your account',
|
||||
2006: 'The password confirmation you entered is incorrect',
|
||||
20060: 'The old confirmation you entered is incorrect',
|
||||
2007: 'There was an error updating your permission',
|
||||
|
||||
// Firebase
|
||||
2010: 'Your login failed. Please verify your credentials',
|
||||
2011: 'We had issues with your token. Please retry',
|
||||
2012: 'We had issues creating your account. Please retry',
|
||||
2013: 'There was an issue with the autentication provider',
|
||||
2015: "Linking your account didn't work. Please contact administrator",
|
||||
|
||||
// Organisations
|
||||
2100: 'Your permissions on the organisation level are not allowed',
|
||||
2101: "Your account doesn't operate in that organisation",
|
||||
2102: 'Organisation headers are required for this action',
|
||||
|
||||
// Api keys
|
||||
2200: 'Your api keys are not in our system',
|
||||
2202: 'Api keys are required for this action',
|
||||
|
||||
// Codes
|
||||
2300: 'The code you entered does not exists',
|
||||
2301: 'The code you entered has expired',
|
||||
2302: 'Too many attempts, please retry in 15 mins',
|
||||
|
||||
/* ************* */
|
||||
/* Security */
|
||||
/* ************* */
|
||||
2400: 'You need to complete security protocols before doing that action',
|
||||
|
||||
/* ************* */
|
||||
/* DataValidation */
|
||||
/* ************* */
|
||||
|
||||
3000: 'There are some inputs missing',
|
||||
3001: 'This is a validation error',
|
||||
|
||||
3005: 'There was an error with the errors range time',
|
||||
|
||||
// -- Forms
|
||||
3100: 'Items are required for that form type',
|
||||
3101: 'The form has duplicate name entries',
|
||||
3102: "The section '${sName}' for this form doesn't exists",
|
||||
3103: "The question '${qName}' for the section '${sName}' for this form doesn't exists",
|
||||
3104: "The answers of the section '${sectionName}' doesn't exists",
|
||||
31040: "The answer ${fieldName} of the section '${sectionName}' doesn't exists",
|
||||
3105: 'The answer "${name}" for this field "${formName}" is not acceptable',
|
||||
|
||||
3106: "The tab ${tabName} for this form doesn't exists",
|
||||
3107: 'Please select a section to answer',
|
||||
3108: 'Some input are missing, ${name} for section ${sectionName}',
|
||||
3109: 'Data integrity breach, ${name} should be ${type}',
|
||||
3110: 'Only one answer (key) please',
|
||||
|
||||
// -- Files
|
||||
3200: 'System cannot read the extension of the file',
|
||||
3201: 'The file extensions are not allowed - allowed : ${allowedMime}',
|
||||
};
|
||||
192
lib/seed/interfaces/components.geo.ts
Normal file
192
lib/seed/interfaces/components.geo.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { InputType, Int, Field, ArgsType, ObjectType, Args } from 'type-graphql';
|
||||
|
||||
import { Max, IsNotEmpty } from 'class-validator';
|
||||
|
||||
import { registerEnumType } from 'type-graphql';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { IsRefExist } from '@seed/engine/decorators/db.guard';
|
||||
import { ScheduleComponent } from './components.dates';
|
||||
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
|
||||
/*
|
||||
* ADDRESS
|
||||
*/
|
||||
@InputType('LocComponentInput')
|
||||
@ObjectType()
|
||||
export class LocComponent {
|
||||
@Field()
|
||||
type: string;
|
||||
@Field(() => [Number])
|
||||
coordinates: number[];
|
||||
}
|
||||
|
||||
@InputType('AddressInput')
|
||||
@ObjectType()
|
||||
export class AddressComponent {
|
||||
@Field({ nullable: true })
|
||||
number?: string;
|
||||
@Field({ nullable: true })
|
||||
street?: string;
|
||||
@Field({ nullable: true })
|
||||
streetBis?: string;
|
||||
@Field({ nullable: true })
|
||||
floor?: string;
|
||||
@Field({ nullable: true })
|
||||
box?: string;
|
||||
@Field({ nullable: true })
|
||||
zip?: string;
|
||||
@Field({ nullable: true })
|
||||
state?: string;
|
||||
@Field()
|
||||
city: string;
|
||||
@Field()
|
||||
country: string;
|
||||
|
||||
returnFullAddress() {
|
||||
return `${this.number} ${this.street}, ${this.zip} ${this.city} ${this.country}`
|
||||
}
|
||||
}
|
||||
|
||||
@InputType('AddressStrictInput')
|
||||
@ObjectType()
|
||||
export class AddressStrictComponent {
|
||||
@Field()
|
||||
number: string;
|
||||
@Field()
|
||||
street: string;
|
||||
@Field()
|
||||
zip: string;
|
||||
@Field()
|
||||
city: string;
|
||||
@Field()
|
||||
country: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
streetBis?: string;
|
||||
@Field({ nullable: true })
|
||||
floor?: string;
|
||||
@Field({ nullable: true })
|
||||
box?: string;
|
||||
@Field({ nullable: true })
|
||||
state?: string;
|
||||
|
||||
|
||||
returnFullAddress() {
|
||||
return `${this.number} ${this.street}, ${this.zip} ${this.city} ${this.country}`
|
||||
}
|
||||
}
|
||||
|
||||
@InputType('PlaceInput')
|
||||
@ObjectType()
|
||||
export class PlaceComponent {
|
||||
@Field()
|
||||
placeId: string;
|
||||
@Field({ nullable: true })
|
||||
address: AddressComponent;
|
||||
@Field(() => LocComponent)
|
||||
loc: LocComponent;
|
||||
@Field()
|
||||
formattedAddress: string;
|
||||
}
|
||||
|
||||
@InputType('PlaceComponentOptionnalInput')
|
||||
@ObjectType()
|
||||
export class PlaceComponentOptionnal {
|
||||
@Field({ nullable: true })
|
||||
placeId?: string;
|
||||
@Field(() => AddressComponent, { nullable: true })
|
||||
address: AddressComponent;
|
||||
@Field(() => LocComponent, { nullable: true })
|
||||
loc?: LocComponent;
|
||||
@Field({ nullable: true })
|
||||
formattedAddress?: string;
|
||||
}
|
||||
|
||||
@InputType('PlaceComponentOptionnalWithAddressInput')
|
||||
@ObjectType()
|
||||
export class PlaceComponentOptionnalWithAddress {
|
||||
@Field({ nullable: true })
|
||||
placeId?: string;
|
||||
@Field(() => AddressStrictComponent)
|
||||
address: AddressStrictComponent;
|
||||
@Field(() => LocComponent, { nullable: true })
|
||||
loc?: LocComponent;
|
||||
@Field({ nullable: true })
|
||||
formattedAddress?: string;
|
||||
}
|
||||
|
||||
@InputType('GeolocSearchInput')
|
||||
@ObjectType()
|
||||
@ArgsType()
|
||||
export class GeolocSearchComponent {
|
||||
@Field()
|
||||
longitude: number;
|
||||
@Field()
|
||||
latitude: number;
|
||||
|
||||
@Max(10000)
|
||||
@Field(() => Int, { nullable: true })
|
||||
radius = 5000;
|
||||
}
|
||||
|
||||
@InputType('GeolocAddressSearchInput')
|
||||
@ObjectType()
|
||||
export class GeolocAddressSearchComponent {
|
||||
@Field()
|
||||
formattedAddress: string;
|
||||
|
||||
@Max(10000)
|
||||
@Field(() => Int, { nullable: true })
|
||||
radius = 5000;
|
||||
}
|
||||
|
||||
@InputType('GeolocPlaceSearchInput')
|
||||
@ObjectType()
|
||||
export class GeolocPlaceSearchComponent {
|
||||
@Field()
|
||||
placeId: string;
|
||||
|
||||
@Max(10000)
|
||||
@Field(() => Int, { nullable: true })
|
||||
radius = 5000;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class GeolocDistComponent {
|
||||
@Field({ nullable: true })
|
||||
calculated?: number;
|
||||
@Field(() => LocComponent, { nullable: true })
|
||||
location?: LocComponent;
|
||||
}
|
||||
|
||||
@InputType('PlaceContactInformationInput')
|
||||
@ObjectType()
|
||||
export class PlaceContactInformation {
|
||||
@Field()
|
||||
phoneNumber: string;
|
||||
|
||||
@Field()
|
||||
email: string;
|
||||
|
||||
@Field(() => ScheduleComponent)
|
||||
openingHours: ScheduleComponent;
|
||||
}
|
||||
24
lib/seed/interfaces/components.notifications.ts
Normal file
24
lib/seed/interfaces/components.notifications.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
export enum EngineNotificationEnum {
|
||||
'emailCodeSignIn' = 'emailCodeSignIn',
|
||||
'magicLink' = 'magicLink',
|
||||
'resetPassword' = 'resetPassword',
|
||||
'adminAccountAddOne' = 'adminAccountAddOne',
|
||||
}
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
46
lib/seed/interfaces/components.texts.ts
Normal file
46
lib/seed/interfaces/components.texts.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { InputType, Int, Field, ArgsType, ObjectType, Args } from 'type-graphql';
|
||||
|
||||
import { Max, IsDate, IsMilitaryTime, IsNotEmpty } from 'class-validator';
|
||||
|
||||
import { registerEnumType } from 'type-graphql';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { IsRefExist } from '@seed/engine/decorators/db.guard';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
|
||||
@ObjectType()
|
||||
@InputType('GenericValueLabelComponentInput')
|
||||
export class GenericValueLabelComponent {
|
||||
@Field()
|
||||
value: string;
|
||||
@Field()
|
||||
label: TranslatableComponent;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('GenericTitleSubtitleComponentInput')
|
||||
export class GenericTitleSubtitleComponent {
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
title: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
subtitle: TranslatableComponent;
|
||||
}
|
||||
318
lib/seed/interfaces/components.ts
Normal file
318
lib/seed/interfaces/components.ts
Normal file
@ -0,0 +1,318 @@
|
||||
import { InputType, Int, Field, ArgsType, ObjectType, Args } from 'type-graphql';
|
||||
|
||||
import { Max, IsNotEmpty } from 'class-validator';
|
||||
|
||||
import { registerEnumType } from 'type-graphql';
|
||||
import { AccountTypeEnum } from '@src/accounts/account.components';
|
||||
import { TranslatableComponent } from '@src/__components/components';
|
||||
import { ModelCollectionEnum } from '@src/__indexes/__collections';
|
||||
import { IsRefExist } from '@seed/engine/decorators/db.guard';
|
||||
|
||||
/*
|
||||
███████╗███╗ ██╗██╗ ██╗███╗ ███╗
|
||||
██╔════╝████╗ ██║██║ ██║████╗ ████║
|
||||
█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║
|
||||
██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║
|
||||
███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
export enum SettingsType {
|
||||
emails = 'emails',
|
||||
ac = 'ac',
|
||||
env = 'env',
|
||||
instagram = 'instagram',
|
||||
errors = 'errors',
|
||||
others = 'others',
|
||||
}
|
||||
registerEnumType(SettingsType, {
|
||||
name: 'SettingsType',
|
||||
});
|
||||
|
||||
export enum AccountGenderEnum {
|
||||
m = 'm',
|
||||
f = 'f',
|
||||
other = 'other',
|
||||
}
|
||||
registerEnumType(AccountGenderEnum, {
|
||||
name: 'AccountGenderEnum',
|
||||
});
|
||||
|
||||
export enum deviceOs {
|
||||
ios = 'ios',
|
||||
android = 'android',
|
||||
}
|
||||
registerEnumType(deviceOs, {
|
||||
name: 'deviceOs',
|
||||
});
|
||||
|
||||
/*
|
||||
* Permissions
|
||||
*/
|
||||
|
||||
export enum PermissionType {
|
||||
r = 'r',
|
||||
w = 'w',
|
||||
d = 'd',
|
||||
}
|
||||
registerEnumType(PermissionType, {
|
||||
name: 'PermissionType',
|
||||
});
|
||||
|
||||
/*
|
||||
* Date stuff
|
||||
*/
|
||||
|
||||
export enum RangeType {
|
||||
strict = 'strict', // IS << A - B >> IE
|
||||
intersect = 'intersect', // IS << A - IE << B ||
|
||||
intersectLarge = 'intersectLarge',
|
||||
included = 'included', // IS << A - B >> IE
|
||||
}
|
||||
registerEnumType(RangeType, {
|
||||
name: 'RangeType',
|
||||
});
|
||||
|
||||
export enum DateTabType {
|
||||
today = 'today',
|
||||
tomorrow = 'tomorrow',
|
||||
upcomming = 'upcomming',
|
||||
past = 'past',
|
||||
}
|
||||
registerEnumType(DateTabType, {
|
||||
name: 'DateTabType',
|
||||
});
|
||||
|
||||
export enum SizeTypeEnum {
|
||||
cm = 'cm',
|
||||
inch = 'inch',
|
||||
}
|
||||
registerEnumType(SizeTypeEnum, {
|
||||
name: 'SizeTypeEnum',
|
||||
});
|
||||
|
||||
export enum WeightTypeEnum {
|
||||
lbs = 'lbs',
|
||||
g = 'g',
|
||||
}
|
||||
registerEnumType(WeightTypeEnum, {
|
||||
name: 'WeightTypeEnum',
|
||||
});
|
||||
|
||||
export enum QualityEnum {
|
||||
verygood = 'verygood',
|
||||
good = 'good',
|
||||
neutral = 'neutral',
|
||||
poor = 'poor',
|
||||
verypoor = 'verypoor',
|
||||
}
|
||||
registerEnumType(QualityEnum, {
|
||||
name: 'QualityEnum',
|
||||
});
|
||||
|
||||
export enum StatusEnum {
|
||||
draft = 'draft',
|
||||
processing = 'processing',
|
||||
validated = 'validated',
|
||||
cancelled = 'cancelled',
|
||||
expired = 'expired',
|
||||
|
||||
}
|
||||
registerEnumType(StatusEnum, {
|
||||
name: 'StatusEnum',
|
||||
});
|
||||
|
||||
// export enum IntervalEnum {
|
||||
// '15' = '15',
|
||||
// '30' = '30',
|
||||
// '45' = '45',
|
||||
// '60' = '60',
|
||||
// '90' = '90',
|
||||
// '120' = '120',
|
||||
// }
|
||||
// registerEnumType(IntervalEnum, {
|
||||
// name: 'IntervalEnum',
|
||||
// });
|
||||
|
||||
/*
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗███████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ ███████╗
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ ███████║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
||||
*/
|
||||
|
||||
@ObjectType()
|
||||
export class EnginePathComponent {
|
||||
@Field(() => ModelCollectionEnum)
|
||||
@IsNotEmpty()
|
||||
ressourceModel: ModelCollectionEnum | string;
|
||||
|
||||
@Field()
|
||||
@IsNotEmpty()
|
||||
// @IsRefExist()
|
||||
ressourceId: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* AUTH
|
||||
*
|
||||
*/
|
||||
|
||||
@ObjectType()
|
||||
export class FirebaseTokenResult {
|
||||
@Field()
|
||||
localId: string;
|
||||
@Field({ nullable: true })
|
||||
email?: string;
|
||||
@Field({ nullable: true })
|
||||
displayName?: string;
|
||||
@Field()
|
||||
idToken: string;
|
||||
@Field({ nullable: true })
|
||||
registered?: boolean;
|
||||
@Field()
|
||||
refreshToken: string;
|
||||
@Field()
|
||||
expiresIn: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* MISC
|
||||
*/
|
||||
@InputType('PositionInInput')
|
||||
export class PositionInInterface {
|
||||
@Field()
|
||||
_id: string;
|
||||
|
||||
@Field(() => Int)
|
||||
order: number;
|
||||
}
|
||||
[];
|
||||
|
||||
// @ObjectType()
|
||||
// @InputType('TranslatableInput')
|
||||
// export class TranslatableComponent {
|
||||
// @Field()
|
||||
// en: string;
|
||||
// }
|
||||
|
||||
/*
|
||||
* RESULTS
|
||||
*/
|
||||
|
||||
@ObjectType('IdResult')
|
||||
export class IdResult {
|
||||
@Field()
|
||||
_id: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SimpleResult {
|
||||
@Field()
|
||||
message: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* DEVICES
|
||||
*/
|
||||
|
||||
@ObjectType()
|
||||
@InputType('DeviceInput')
|
||||
export class DeviceComponent {
|
||||
@Field()
|
||||
uid: string;
|
||||
@Field(() => deviceOs)
|
||||
os: deviceOs;
|
||||
|
||||
@Field()
|
||||
pushToken: string;
|
||||
@Field()
|
||||
deviceName: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* IMAGES
|
||||
*/
|
||||
|
||||
@ObjectType()
|
||||
@InputType('ImageInput')
|
||||
export default class ImageComponent {
|
||||
@Field({ nullable: true })
|
||||
title?: string;
|
||||
@Field({ nullable: true })
|
||||
fileType?: string;
|
||||
|
||||
@Field()
|
||||
large: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
medium?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
small?: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('NewPermissionInputC')
|
||||
export class NewPermissionInput {
|
||||
@Field(() => PermissionType)
|
||||
permissionType: PermissionType;
|
||||
|
||||
@Field(() => AccountTypeEnum)
|
||||
permission: AccountTypeEnum;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('SEOInput')
|
||||
export class SEOField {
|
||||
@Field(() => TranslatableComponent)
|
||||
title: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent)
|
||||
description: TranslatableComponent;
|
||||
@Field(() => TranslatableComponent, { nullable: true })
|
||||
keywords?: TranslatableComponent;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('SEOSimpleInput')
|
||||
export class SEOSimpleField {
|
||||
@Field()
|
||||
title: string;
|
||||
@Field()
|
||||
description: string;
|
||||
@Field({ nullable: true })
|
||||
keywords?: string;
|
||||
@Field(() => ImageComponent, { nullable: true })
|
||||
thumbnail?: ImageComponent;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('SizeComponentInput')
|
||||
export class SizeComponent {
|
||||
@Field()
|
||||
unit: SizeTypeEnum;
|
||||
@Field()
|
||||
value: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@InputType('WeightComponentInput')
|
||||
export class WeightComponent {
|
||||
@Field()
|
||||
unit: WeightTypeEnum;
|
||||
@Field()
|
||||
value: number;
|
||||
}
|
||||
@ObjectType()
|
||||
@InputType('CustomTagsInput')
|
||||
export class CustomTagsInterface {
|
||||
@Field()
|
||||
key: string;
|
||||
|
||||
@Field()
|
||||
value: string;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user