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