mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
create federation module as separate Apollo instance with cli params
This commit is contained in:
parent
38f26445b0
commit
8276312cce
@ -53,6 +53,7 @@
|
|||||||
"@model/*": ["src/graphql/model/*"],
|
"@model/*": ["src/graphql/model/*"],
|
||||||
"@repository/*": ["src/typeorm/repository/*"],
|
"@repository/*": ["src/typeorm/repository/*"],
|
||||||
"@test/*": ["test/*"],
|
"@test/*": ["test/*"],
|
||||||
|
|
||||||
/* external */
|
/* external */
|
||||||
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
||||||
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
||||||
|
|||||||
@ -2,20 +2,39 @@
|
|||||||
"name": "gradido-federation",
|
"name": "gradido-federation",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
||||||
"main": "./src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/federation",
|
"repository": "https://github.com/gradido/gradido/federation",
|
||||||
"author": "Claus-Peter Huebner",
|
"author": "Claus-Peter Huebner",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build",
|
"build": "tsc --build",
|
||||||
"clean": "tsc --build --clean",
|
"clean": "tsc --build --clean",
|
||||||
"start": "npm run compile && node build/index.js"
|
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
|
||||||
|
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
|
||||||
|
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/server": "^4.2.2",
|
"@hyperswarm/dht": "^6.3.3",
|
||||||
|
"@types/i18n": "^0.13.6",
|
||||||
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.7",
|
||||||
"@types/node": "^18.11.11",
|
"@types/node": "^18.11.11",
|
||||||
"graphql": "^16.6.0",
|
"apollo-server-express": "^3.11.1",
|
||||||
|
"class-validator": "^0.13.2",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"decimal.js-light": "^2.5.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"graphql": "15.5.1",
|
||||||
|
"i18n": "^0.15.1",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"log4js": "^6.7.1",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.1.1",
|
||||||
|
"type-graphql": "^1.1.1",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
federation/src/auth/CustomJwtPayload.ts
Normal file
5
federation/src/auth/CustomJwtPayload.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { JwtPayload } from 'jsonwebtoken'
|
||||||
|
|
||||||
|
export interface CustomJwtPayload extends JwtPayload {
|
||||||
|
pubKey: Buffer
|
||||||
|
}
|
||||||
12
federation/src/auth/INALIENABLE_RIGHTS.ts
Normal file
12
federation/src/auth/INALIENABLE_RIGHTS.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
|
||||||
|
export const INALIENABLE_RIGHTS = [
|
||||||
|
RIGHTS.LOGIN,
|
||||||
|
RIGHTS.GET_COMMUNITY_INFO,
|
||||||
|
RIGHTS.COMMUNITIES,
|
||||||
|
RIGHTS.CREATE_USER,
|
||||||
|
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||||
|
RIGHTS.SET_PASSWORD,
|
||||||
|
RIGHTS.QUERY_TRANSACTION_LINK,
|
||||||
|
RIGHTS.QUERY_OPT_IN,
|
||||||
|
]
|
||||||
19
federation/src/auth/JWT.ts
Normal file
19
federation/src/auth/JWT.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import CONFIG from '@/config/'
|
||||||
|
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||||
|
|
||||||
|
export const decode = (token: string): CustomJwtPayload | null => {
|
||||||
|
if (!token) throw new Error('401 Unauthorized')
|
||||||
|
try {
|
||||||
|
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const encode = (pubKey: Buffer): string => {
|
||||||
|
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
||||||
|
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||||
|
})
|
||||||
|
return token
|
||||||
|
}
|
||||||
56
federation/src/auth/RIGHTS.ts
Normal file
56
federation/src/auth/RIGHTS.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
export enum RIGHTS {
|
||||||
|
LOGIN = 'LOGIN',
|
||||||
|
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||||
|
BALANCE = 'BALANCE',
|
||||||
|
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
|
||||||
|
COMMUNITIES = 'COMMUNITIES',
|
||||||
|
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||||
|
EXIST_PID = 'EXIST_PID',
|
||||||
|
GET_KLICKTIPP_USER = 'GET_KLICKTIPP_USER',
|
||||||
|
GET_KLICKTIPP_TAG_MAP = 'GET_KLICKTIPP_TAG_MAP',
|
||||||
|
UNSUBSCRIBE_NEWSLETTER = 'UNSUBSCRIBE_NEWSLETTER',
|
||||||
|
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
||||||
|
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||||
|
SEND_COINS = 'SEND_COINS',
|
||||||
|
LOGOUT = 'LOGOUT',
|
||||||
|
CREATE_USER = 'CREATE_USER',
|
||||||
|
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||||
|
SET_PASSWORD = 'SET_PASSWORD',
|
||||||
|
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||||
|
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||||
|
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||||
|
CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK',
|
||||||
|
DELETE_TRANSACTION_LINK = 'DELETE_TRANSACTION_LINK',
|
||||||
|
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||||
|
REDEEM_TRANSACTION_LINK = 'REDEEM_TRANSACTION_LINK',
|
||||||
|
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
|
||||||
|
GDT_BALANCE = 'GDT_BALANCE',
|
||||||
|
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
|
||||||
|
DELETE_CONTRIBUTION = 'DELETE_CONTRIBUTION',
|
||||||
|
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
|
||||||
|
LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS',
|
||||||
|
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',
|
||||||
|
LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS',
|
||||||
|
COMMUNITY_STATISTICS = 'COMMUNITY_STATISTICS',
|
||||||
|
SEARCH_ADMIN_USERS = 'SEARCH_ADMIN_USERS',
|
||||||
|
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
|
||||||
|
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||||
|
// Admin
|
||||||
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
|
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||||
|
DELETE_USER = 'DELETE_USER',
|
||||||
|
UNDELETE_USER = 'UNDELETE_USER',
|
||||||
|
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
||||||
|
ADMIN_CREATE_CONTRIBUTIONS = 'ADMIN_CREATE_CONTRIBUTIONS',
|
||||||
|
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||||
|
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||||
|
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS',
|
||||||
|
CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION',
|
||||||
|
SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL',
|
||||||
|
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
|
||||||
|
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
|
||||||
|
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
||||||
|
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||||
|
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||||
|
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||||
|
}
|
||||||
40
federation/src/auth/ROLES.ts
Normal file
40
federation/src/auth/ROLES.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
import { Role } from './Role'
|
||||||
|
|
||||||
|
export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS)
|
||||||
|
export const ROLE_USER = new Role('user', [
|
||||||
|
...INALIENABLE_RIGHTS,
|
||||||
|
RIGHTS.VERIFY_LOGIN,
|
||||||
|
RIGHTS.BALANCE,
|
||||||
|
RIGHTS.LIST_GDT_ENTRIES,
|
||||||
|
RIGHTS.EXIST_PID,
|
||||||
|
RIGHTS.GET_KLICKTIPP_USER,
|
||||||
|
RIGHTS.GET_KLICKTIPP_TAG_MAP,
|
||||||
|
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||||
|
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||||
|
RIGHTS.TRANSACTION_LIST,
|
||||||
|
RIGHTS.SEND_COINS,
|
||||||
|
RIGHTS.LOGOUT,
|
||||||
|
RIGHTS.UPDATE_USER_INFOS,
|
||||||
|
RIGHTS.HAS_ELOPAGE,
|
||||||
|
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||||
|
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||||
|
RIGHTS.REDEEM_TRANSACTION_LINK,
|
||||||
|
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||||
|
RIGHTS.GDT_BALANCE,
|
||||||
|
RIGHTS.CREATE_CONTRIBUTION,
|
||||||
|
RIGHTS.DELETE_CONTRIBUTION,
|
||||||
|
RIGHTS.LIST_CONTRIBUTIONS,
|
||||||
|
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||||
|
RIGHTS.UPDATE_CONTRIBUTION,
|
||||||
|
RIGHTS.SEARCH_ADMIN_USERS,
|
||||||
|
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||||
|
RIGHTS.COMMUNITY_STATISTICS,
|
||||||
|
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||||
|
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||||
|
])
|
||||||
|
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||||
|
|
||||||
|
// TODO from database
|
||||||
|
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN]
|
||||||
15
federation/src/auth/Role.ts
Normal file
15
federation/src/auth/Role.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
|
||||||
|
export class Role {
|
||||||
|
id: string
|
||||||
|
rights: RIGHTS[]
|
||||||
|
|
||||||
|
constructor(id: string, rights: RIGHTS[]) {
|
||||||
|
this.id = id
|
||||||
|
this.rights = rights
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRight = (right: RIGHTS): boolean => {
|
||||||
|
return this.rights.includes(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||||
|
/*
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -8,6 +8,7 @@ Decimal.set({
|
|||||||
precision: 25,
|
precision: 25,
|
||||||
rounding: Decimal.ROUND_HALF_UP,
|
rounding: Decimal.ROUND_HALF_UP,
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0055-add_communities_table',
|
DB_VERSION: '0055-add_communities_table',
|
||||||
@ -23,7 +24,7 @@ const constants = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = {
|
const server = {
|
||||||
PORT: process.env.PORT || 4000,
|
PORT: process.env.PORT || 5000,
|
||||||
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
||||||
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
||||||
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
||||||
@ -39,7 +40,7 @@ const database = {
|
|||||||
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
|
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
|
||||||
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
|
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
const klicktipp = {
|
const klicktipp = {
|
||||||
KLICKTIPP: process.env.KLICKTIPP === 'true' || false,
|
KLICKTIPP: process.env.KLICKTIPP === 'true' || false,
|
||||||
KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL || 'https://api.klicktipp.com',
|
KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL || 'https://api.klicktipp.com',
|
||||||
@ -48,7 +49,7 @@ const klicktipp = {
|
|||||||
KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE || 'SomeFakeKeyDE',
|
KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE || 'SomeFakeKeyDE',
|
||||||
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN',
|
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN',
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
const community = {
|
const community = {
|
||||||
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
||||||
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
|
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
|
||||||
@ -59,7 +60,7 @@ const community = {
|
|||||||
COMMUNITY_DESCRIPTION:
|
COMMUNITY_DESCRIPTION:
|
||||||
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
const loginServer = {
|
const loginServer = {
|
||||||
LOGIN_APP_SECRET: process.env.LOGIN_APP_SECRET || '21ffbbc616fe',
|
LOGIN_APP_SECRET: process.env.LOGIN_APP_SECRET || '21ffbbc616fe',
|
||||||
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
||||||
@ -103,7 +104,7 @@ const eventProtocol = {
|
|||||||
|
|
||||||
// This is needed by graphql-directive-auth
|
// This is needed by graphql-directive-auth
|
||||||
process.env.APP_SECRET = server.JWT_SECRET
|
process.env.APP_SECRET = server.JWT_SECRET
|
||||||
|
*/
|
||||||
// Check config version
|
// Check config version
|
||||||
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
||||||
if (
|
if (
|
||||||
@ -128,12 +129,12 @@ const CONFIG = {
|
|||||||
...constants,
|
...constants,
|
||||||
...server,
|
...server,
|
||||||
...database,
|
...database,
|
||||||
...klicktipp,
|
//...klicktipp,
|
||||||
...community,
|
...community,
|
||||||
...email,
|
//...email,
|
||||||
...loginServer,
|
//...loginServer,
|
||||||
...webhook,
|
//...webhook,
|
||||||
...eventProtocol,
|
//...eventProtocol,
|
||||||
...federation,
|
...federation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
federation/src/graphql/api/1_0/resolver/TestResolver.ts
Normal file
8
federation/src/graphql/api/1_0/resolver/TestResolver.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
|
export class TestResolver {
|
||||||
|
async test(): Promise<String> {
|
||||||
|
logger.info(`test apiVersion=1_0`)
|
||||||
|
return 'test 1_0'
|
||||||
|
}
|
||||||
|
}
|
||||||
8
federation/src/graphql/api/1_1/resolver/TestResolver.ts
Normal file
8
federation/src/graphql/api/1_1/resolver/TestResolver.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
|
export class TestResolver {
|
||||||
|
async test(): Promise<String> {
|
||||||
|
logger.info(`test apiVersion=1_1`)
|
||||||
|
return 'test 1_1'
|
||||||
|
}
|
||||||
|
}
|
||||||
8
federation/src/graphql/api/2_0/resolver/TestResolver.ts
Normal file
8
federation/src/graphql/api/2_0/resolver/TestResolver.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
|
export class TestResolver {
|
||||||
|
async test(): Promise<String> {
|
||||||
|
logger.info(`test apiVersion=2_0`)
|
||||||
|
return 'test 2_0'
|
||||||
|
}
|
||||||
|
}
|
||||||
5
federation/src/graphql/api/schema.ts
Normal file
5
federation/src/graphql/api/schema.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export const getApiResolvers = (apiVersion: String) => {
|
||||||
|
return path.join(__dirname, `./${apiVersion}/resolver/*Resolver.{ts,js}`)
|
||||||
|
}
|
||||||
10
federation/src/graphql/arg/SearchUsersFilters.ts
Normal file
10
federation/src/graphql/arg/SearchUsersFilters.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Field, InputType } from 'type-graphql'
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export default class SearchUsersFilters {
|
||||||
|
@Field(() => Boolean, { nullable: true, defaultValue: null })
|
||||||
|
byActivated: boolean
|
||||||
|
|
||||||
|
@Field(() => Boolean, { nullable: true, defaultValue: null })
|
||||||
|
byDeleted: boolean
|
||||||
|
}
|
||||||
55
federation/src/graphql/directive/isAuthorized.ts
Normal file
55
federation/src/graphql/directive/isAuthorized.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { AuthChecker } from 'type-graphql'
|
||||||
|
|
||||||
|
import { decode, encode } from '@/auth/JWT'
|
||||||
|
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
||||||
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
|
import { getCustomRepository } from '@dbTools/typeorm'
|
||||||
|
import { UserRepository } from '@repository/User'
|
||||||
|
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||||
|
|
||||||
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
|
|
||||||
|
// is rights an inalienable right?
|
||||||
|
if ((<RIGHTS[]>rights).reduce((acc, right) => acc && INALIENABLE_RIGHTS.includes(right), true))
|
||||||
|
return true
|
||||||
|
|
||||||
|
// Do we have a token?
|
||||||
|
if (!context.token) {
|
||||||
|
throw new Error('401 Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the token
|
||||||
|
const decoded = decode(context.token)
|
||||||
|
if (!decoded) {
|
||||||
|
throw new Error('403.13 - Client certificate revoked')
|
||||||
|
}
|
||||||
|
// Set context pubKey
|
||||||
|
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
||||||
|
|
||||||
|
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
||||||
|
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
||||||
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
try {
|
||||||
|
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||||
|
context.user = user
|
||||||
|
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||||
|
} catch {
|
||||||
|
// in case the database query fails (user deleted)
|
||||||
|
throw new Error('401 Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for correct rights
|
||||||
|
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
||||||
|
if (missingRights.length !== 0) {
|
||||||
|
throw new Error('401 Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
// set new header token
|
||||||
|
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isAuthorized
|
||||||
23
federation/src/graphql/scalar/Decimal.ts
Normal file
23
federation/src/graphql/scalar/Decimal.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { GraphQLScalarType, Kind } from 'graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
|
export default new GraphQLScalarType({
|
||||||
|
name: 'Decimal',
|
||||||
|
description: 'The `Decimal` scalar type to represent currency values',
|
||||||
|
|
||||||
|
serialize(value: Decimal) {
|
||||||
|
return value.toString()
|
||||||
|
},
|
||||||
|
|
||||||
|
parseValue(value) {
|
||||||
|
return new Decimal(value)
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLiteral(ast) {
|
||||||
|
if (ast.kind !== Kind.STRING) {
|
||||||
|
throw new TypeError(`${String(ast)} is not a valid decimal value.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Decimal(ast.value)
|
||||||
|
},
|
||||||
|
})
|
||||||
18
federation/src/graphql/schema.ts
Normal file
18
federation/src/graphql/schema.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GraphQLSchema } from 'graphql'
|
||||||
|
import { buildSchema } from 'type-graphql'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import isAuthorized from './directive/isAuthorized'
|
||||||
|
import DecimalScalar from './scalar/Decimal'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { getApiResolvers } from './api/schema'
|
||||||
|
|
||||||
|
const schema = async (apiVersion: String): Promise<GraphQLSchema> => {
|
||||||
|
return buildSchema({
|
||||||
|
resolvers: [getApiResolvers(apiVersion)],
|
||||||
|
authChecker: isAuthorized,
|
||||||
|
scalarsMap: [{ type: Decimal, scalar: DecimalScalar }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
@ -7,14 +7,19 @@ import { startDHT } from '@/dht_node/index'
|
|||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const { app } = await createServer()
|
// TODO better to use yargs than this fix cli-patter -port 5000 -api 1_0
|
||||||
|
const myArgs = process.argv.slice(2)
|
||||||
|
const port = myArgs[0] === '-port' ? myArgs[1] : CONFIG.PORT
|
||||||
|
const apiVersion = myArgs[2] === '-api' ? myArgs[3] : '1_0'
|
||||||
|
|
||||||
app.listen(CONFIG.PORT, () => {
|
const { app } = await createServer(apiVersion)
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
|
console.log(`Server is running at http://localhost:${port}`)
|
||||||
if (CONFIG.GRAPHIQL) {
|
if (CONFIG.GRAPHIQL) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
console.log(`GraphIQL available at http://localhost:${port}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import { checkDBVersion } from '@/typeorm/DBVersion'
|
|||||||
|
|
||||||
// server
|
// server
|
||||||
import cors from './cors'
|
import cors from './cors'
|
||||||
import serverContext from './context'
|
// import serverContext from './context'
|
||||||
import plugins from './plugins'
|
// import plugins from './plugins'
|
||||||
|
|
||||||
// config
|
// config
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
@ -34,8 +34,9 @@ import { i18n } from './localization'
|
|||||||
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection }
|
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection }
|
||||||
|
|
||||||
const createServer = async (
|
const createServer = async (
|
||||||
|
apiVersion: String,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
context: any = serverContext,
|
// context: any = serverContext,
|
||||||
logger: Logger = apolloLogger,
|
logger: Logger = apolloLogger,
|
||||||
localization: i18n.I18n = i18n,
|
localization: i18n.I18n = i18n,
|
||||||
): Promise<ServerDef> => {
|
): Promise<ServerDef> => {
|
||||||
@ -71,21 +72,18 @@ const createServer = async (
|
|||||||
app.use(localization.init)
|
app.use(localization.init)
|
||||||
|
|
||||||
// Elopage Webhook
|
// Elopage Webhook
|
||||||
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
|
// app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
|
||||||
|
|
||||||
// Apollo Server
|
// Apollo Server
|
||||||
const apollo = new ApolloServer({
|
const apollo = new ApolloServer({
|
||||||
schema: await schema(),
|
schema: await schema(apiVersion),
|
||||||
playground: CONFIG.GRAPHIQL,
|
// playground: CONFIG.GRAPHIQL,
|
||||||
introspection: CONFIG.GRAPHIQL,
|
introspection: CONFIG.GRAPHIQL,
|
||||||
context,
|
// context,
|
||||||
plugins,
|
// plugins,
|
||||||
logger,
|
logger,
|
||||||
})
|
})
|
||||||
apollo.applyMiddleware({ app, path: '/' })
|
apollo.applyMiddleware({ app, path: '/' })
|
||||||
logger.info(
|
|
||||||
`running with PRODUCTION=${CONFIG.PRODUCTION}, sending EMAIL enabled=${CONFIG.EMAIL} and EMAIL_TEST_MODUS=${CONFIG.EMAIL_TEST_MODUS} ...`,
|
|
||||||
)
|
|
||||||
logger.debug('createServer...successful')
|
logger.debug('createServer...successful')
|
||||||
|
|
||||||
return { apollo, app, con }
|
return { apollo, app, con }
|
||||||
|
|||||||
27
federation/src/typeorm/DBVersion.ts
Normal file
27
federation/src/typeorm/DBVersion.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Migration } from '@entity/Migration'
|
||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
|
const getDBVersion = async (): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||||
|
return dbVersion ? dbVersion.fileName : null
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkDBVersion = async (DB_VERSION: string): Promise<boolean> => {
|
||||||
|
const dbVersion = await getDBVersion()
|
||||||
|
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
|
||||||
|
logger.error(
|
||||||
|
`Wrong database version detected - the backend requires '${DB_VERSION}' but found '${
|
||||||
|
dbVersion || 'None'
|
||||||
|
}`,
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export { checkDBVersion, getDBVersion }
|
||||||
34
federation/src/typeorm/connection.ts
Normal file
34
federation/src/typeorm/connection.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// TODO This is super weird - since the entities are defined in another project they have their own globals.
|
||||||
|
// We cannot use our connection here, but must use the external typeorm installation
|
||||||
|
import { Connection, createConnection, FileLogger } from '@dbTools/typeorm'
|
||||||
|
import CONFIG from '@/config'
|
||||||
|
import { entities } from '@entity/index'
|
||||||
|
|
||||||
|
const connection = async (): Promise<Connection | null> => {
|
||||||
|
try {
|
||||||
|
return createConnection({
|
||||||
|
name: 'default',
|
||||||
|
type: 'mysql',
|
||||||
|
host: CONFIG.DB_HOST,
|
||||||
|
port: CONFIG.DB_PORT,
|
||||||
|
username: CONFIG.DB_USER,
|
||||||
|
password: CONFIG.DB_PASSWORD,
|
||||||
|
database: CONFIG.DB_DATABASE,
|
||||||
|
entities,
|
||||||
|
synchronize: false,
|
||||||
|
logging: true,
|
||||||
|
logger: new FileLogger('all', {
|
||||||
|
logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
|
||||||
|
}),
|
||||||
|
extra: {
|
||||||
|
charset: 'utf8mb4_unicode_ci',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connection
|
||||||
67
federation/src/typeorm/repository/User.ts
Normal file
67
federation/src/typeorm/repository/User.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import SearchUsersFilters from '@/graphql/arg/SearchUsersFilters'
|
||||||
|
import { Brackets, EntityRepository, IsNull, Not, Repository } from '@dbTools/typeorm'
|
||||||
|
import { User as DbUser } from '@entity/User'
|
||||||
|
|
||||||
|
@EntityRepository(DbUser)
|
||||||
|
export class UserRepository extends Repository<DbUser> {
|
||||||
|
async findByPubkeyHex(pubkeyHex: string): Promise<DbUser> {
|
||||||
|
const dbUser = await this.createQueryBuilder('user')
|
||||||
|
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
||||||
|
.where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex })
|
||||||
|
.getOneOrFail()
|
||||||
|
/*
|
||||||
|
const dbUser = await this.findOneOrFail(`hex(user.pubKey) = { pubkeyHex }`)
|
||||||
|
const emailContact = await this.query(
|
||||||
|
`SELECT * from user_contacts where id = { dbUser.emailId }`,
|
||||||
|
)
|
||||||
|
dbUser.emailContact = emailContact
|
||||||
|
*/
|
||||||
|
return dbUser
|
||||||
|
}
|
||||||
|
|
||||||
|
async findBySearchCriteriaPagedFiltered(
|
||||||
|
select: string[],
|
||||||
|
searchCriteria: string,
|
||||||
|
filters: SearchUsersFilters,
|
||||||
|
currentPage: number,
|
||||||
|
pageSize: number,
|
||||||
|
): Promise<[DbUser[], number]> {
|
||||||
|
const query = this.createQueryBuilder('user')
|
||||||
|
.select(select)
|
||||||
|
.withDeleted()
|
||||||
|
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
||||||
|
.where(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where(
|
||||||
|
'user.firstName like :name or user.lastName like :lastName or emailContact.email like :email',
|
||||||
|
{
|
||||||
|
name: `%${searchCriteria}%`,
|
||||||
|
lastName: `%${searchCriteria}%`,
|
||||||
|
email: `%${searchCriteria}%`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
/*
|
||||||
|
filterCriteria.forEach((filter) => {
|
||||||
|
query.andWhere(filter)
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
if (filters) {
|
||||||
|
if (filters.byActivated !== null) {
|
||||||
|
query.andWhere('emailContact.emailChecked = :value', { value: filters.byActivated })
|
||||||
|
// filterCriteria.push({ 'emailContact.emailChecked': filters.byActivated })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.byDeleted !== null) {
|
||||||
|
// filterCriteria.push({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||||
|
query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
.take(pageSize)
|
||||||
|
.skip((currentPage - 1) * pageSize)
|
||||||
|
.getManyAndCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,49 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// "rootDirs": ["src"], /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
"outDir": "./build", /* Redirect output structure to the directory. */
|
|
||||||
// "lib": ["es2020"], /* Specify library files to be included in the Compilation. */
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./build", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||||
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
@ -19,8 +58,33 @@
|
|||||||
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
||||||
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
||||||
},
|
},
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
"typeRoots": ["src/dht_node/@types", "node_modules/@types"], /* List of folders to include type definitions from. */
|
"typeRoots": ["src/dht_node/@types", "node_modules/@types"], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
"types": ["node"] /* Type declaration files to be included in compilation. */
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../database/tsconfig.json",
|
||||||
|
// add 'prepend' if you want to include the referenced project in your output file
|
||||||
|
// "prepend": true
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
1596
federation/yarn.lock
1596
federation/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user