Merge branch 'master' into dlt_inspector_as_submodule

This commit is contained in:
einhornimmond 2025-12-02 12:55:42 +01:00 committed by GitHub
commit c45bbb7945
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 1139 additions and 471 deletions

View File

@ -4,8 +4,20 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v2.7.1](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.1)
#### [v2.7.2](https://github.com/gradido/gradido/compare/v2.7.2...v2.7.2)
#### [v2.7.2](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.2)
> 2 December 2025
- feat(frontend): success message on create contribution like on send [`#3583`](https://github.com/gradido/gradido/pull/3583)
- refactor(backend): rewrite seeding in database [`#3586`](https://github.com/gradido/gradido/pull/3586)
- fix(other): complete email tests and move localization complete into core [`#3585`](https://github.com/gradido/gradido/pull/3585)
- fix(workflow): editor warnings [`#3584`](https://github.com/gradido/gradido/pull/3584)
- fix(other): fix code which lead to biome linting errors [`#3582`](https://github.com/gradido/gradido/pull/3582)
- refactor(backend): moved email to core [`#3579`](https://github.com/gradido/gradido/pull/3579)
- refactor(database): stabilize entity loading across runtimes by introducing deferred relation resolution [`#3578`](https://github.com/gradido/gradido/pull/3578)
- chore(release): v2.7.1 [`#3577`](https://github.com/gradido/gradido/pull/3577)
- feat(frontend): new startpage images [`#3576`](https://github.com/gradido/gradido/pull/3576)
- feat(frontend): update login subtitle [`#3574`](https://github.com/gradido/gradido/pull/3574)
- feat(frontend): update copy symbol and change link order [`#3575`](https://github.com/gradido/gradido/pull/3575)

View File

@ -3,7 +3,7 @@
"description": "Administration Interface for Gradido",
"main": "index.js",
"author": "Gradido Academy - https://www.gradido.net",
"version": "2.7.1",
"version": "2.7.2",
"license": "Apache-2.0",
"scripts": {
"dev": "vite",

View File

@ -1,6 +1,6 @@
{
"name": "backend",
"version": "2.7.1",
"version": "2.7.2",
"private": false,
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -1,27 +1,7 @@
import { registerEnumType } from 'type-graphql'
import { ContributionCycleType } from 'database'
// lowercase values are not implemented yet
export enum ContributionCycleType {
ONCE = 'ONCE',
HOUR = 'hour',
TWO_HOURS = 'two_hours',
FOUR_HOURS = 'four_hours',
EIGHT_HOURS = 'eight_hours',
HALF_DAY = 'half_day',
DAILY = 'DAILY',
TWO_DAYS = 'two_days',
THREE_DAYS = 'three_days',
FOUR_DAYS = 'four_days',
FIVE_DAYS = 'five_days',
SIX_DAYS = 'six_days',
WEEK = 'week',
TWO_WEEKS = 'two_weeks',
MONTH = 'month',
TWO_MONTH = 'two_month',
QUARTER = 'quarter',
HALF_YEAR = 'half_year',
YEAR = 'year',
}
export { ContributionCycleType }
registerEnumType(ContributionCycleType, {
name: 'ContributionCycleType', // this one is mandatory

View File

@ -1,12 +1,7 @@
import { registerEnumType } from 'type-graphql'
import { ContributionStatus } from 'database'
export enum ContributionStatus {
PENDING = 'PENDING',
DELETED = 'DELETED',
IN_PROGRESS = 'IN_PROGRESS',
DENIED = 'DENIED',
CONFIRMED = 'CONFIRMED',
}
export { ContributionStatus }
registerEnumType(ContributionStatus, {
name: 'ContributionStatus',

View File

@ -1,10 +1,7 @@
import { registerEnumType } from 'type-graphql'
import { ContributionType } from 'database'
export enum ContributionType {
ADMIN = 'ADMIN',
USER = 'USER',
LINK = 'LINK',
}
export { ContributionType }
registerEnumType(ContributionType, {
name: 'ContributionType',

View File

@ -1,6 +1,8 @@
import { registerEnumType } from 'type-graphql'
import { PendingTransactionState } from 'shared'
export { PendingTransactionState }
registerEnumType(PendingTransactionState, {
name: 'PendingTransactionState', // this one is mandatory
description: 'State of the PendingTransaction', // this one is optional

View File

@ -1,13 +1,7 @@
import { registerEnumType } from 'type-graphql'
import { RoleNames } from 'database'
export enum RoleNames {
UNAUTHORIZED = 'UNAUTHORIZED',
USER = 'USER',
MODERATOR = 'MODERATOR',
MODERATOR_AI = 'MODERATOR_AI',
ADMIN = 'ADMIN',
DLT_CONNECTOR = 'DLT_CONNECTOR_ROLE',
}
export { RoleNames }
registerEnumType(RoleNames, {
name: 'RoleNames', // this one is mandatory

View File

@ -2139,14 +2139,6 @@ describe('ContributionResolver', () => {
})
})
it('stores the EMAIL_CONFIRMATION event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventType.EMAIL_CONFIRMATION,
}),
)
})
describe('confirm same contribution again', () => {
it('throws an error', async () => {
jest.clearAllMocks()

View File

@ -2256,7 +2256,7 @@ describe('UserResolver', () => {
relations: ['user'],
})
const activationLink = `${
CONFIG.EMAIL_LINK_VERIFICATION
CONFIG.EMAIL_LINK_SETPASSWORD
}${userContact.emailVerificationCode.toString()}`
expect(sendAccountActivationEmail).toBeCalledWith({
firstName: 'Bibi',

View File

@ -1,3 +1,6 @@
export { ContributionLinkInterface } from 'database'
/*
export interface ContributionLinkInterface {
amount: number
name: string
@ -5,3 +8,4 @@ export interface ContributionLinkInterface {
validFrom?: Date
validTo?: Date
}
*/

View File

@ -1,5 +1,6 @@
import { ContributionLinkInterface } from './ContributionLinkInterface'
export { contributionLinks } from 'database'
/*
export const contributionLinks: ContributionLinkInterface[] = [
{
name: 'Dokumenta 2017',
@ -16,3 +17,4 @@ export const contributionLinks: ContributionLinkInterface[] = [
validTo: new Date(2022, 8, 25),
},
]
*/

View File

@ -1,3 +1,5 @@
export { CreationInterface } from 'database'
/*
export interface CreationInterface {
email: string
amount: number
@ -7,3 +9,4 @@ export interface CreationInterface {
// number of months to move the confirmed creation to the past
moveCreationDate?: number
}
*/

View File

@ -1,3 +1,7 @@
export { creations } from 'database'
/*
import { nMonthsBefore } from '@/seeds/factory/creation'
import { CreationInterface } from './CreationInterface'
@ -153,3 +157,4 @@ export const creations: CreationInterface[] = [
confirmed: true,
},
]
*/

View File

@ -1,32 +1,15 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import {
contributionLinkFactory as contributionLinkFactoryDb,
ContributionLinkInterface
} from 'database'
import { ContributionLink } from '@model/ContributionLink'
import { ContributionLinkInterface } from '@/seeds/contributionLink/ContributionLinkInterface'
import { createContributionLink, login } from '@/seeds/graphql/mutations'
export { ContributionLinkInterface }
export const contributionLinkFactory = async (
client: ApolloServerTestClient,
export async function contributionLinkFactory (
_client: any,
contributionLink: ContributionLinkInterface,
): Promise<ContributionLink> => {
const { mutate } = client
// login as admin
await mutate({
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
const variables = {
amount: contributionLink.amount,
memo: contributionLink.memo,
name: contributionLink.name,
cycle: 'ONCE',
maxPerCycle: 1,
maxAmountPerMonth: 200,
validFrom: contributionLink.validFrom ? contributionLink.validFrom.toISOString() : undefined,
validTo: contributionLink.validTo ? contributionLink.validTo.toISOString() : undefined,
}
const result = await mutate({ mutation: createContributionLink, variables })
return result.data.createContributionLink
): Promise<ContributionLink> {
return new ContributionLink(await contributionLinkFactoryDb(contributionLink))
}

View File

@ -1,58 +1,15 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Contribution, Transaction } from 'database'
import {
Contribution,
creationFactory as creationFactoryDb,
CreationInterface,
nMonthsBefore
} from 'database'
import { findUserByEmail } from '@/graphql/resolver/UserResolver'
import { CreationInterface } from '@/seeds/creation/CreationInterface'
import { confirmContribution, createContribution, login } from '@/seeds/graphql/mutations'
export const nMonthsBefore = (date: Date, months = 1): string => {
return new Date(date.getFullYear(), date.getMonth() - months, 1).toISOString()
}
export { CreationInterface, nMonthsBefore }
export const creationFactory = async (
client: ApolloServerTestClient,
_client: any,
creation: CreationInterface,
): Promise<Contribution> => {
const { mutate } = client
await mutate({
mutation: login,
variables: { email: creation.email, password: 'Aa12345_' },
})
const {
data: { createContribution: contribution },
} = await mutate({ mutation: createContribution, variables: { ...creation } })
if (creation.confirmed) {
const user = await findUserByEmail(creation.email) // userContact.user
await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
await mutate({ mutation: confirmContribution, variables: { id: contribution.id } })
const confirmedContribution = await Contribution.findOneOrFail({
where: { id: contribution.id },
})
if (creation.moveCreationDate) {
const transaction = await Transaction.findOneOrFail({
where: { userId: user.id, creationDate: new Date(creation.contributionDate) },
order: { balanceDate: 'DESC' },
})
if (transaction.decay.equals(0) && transaction.creationDate) {
confirmedContribution.contributionDate = new Date(
nMonthsBefore(transaction.creationDate, creation.moveCreationDate),
)
transaction.creationDate = new Date(
nMonthsBefore(transaction.creationDate, creation.moveCreationDate),
)
transaction.balanceDate = new Date(
nMonthsBefore(transaction.balanceDate, creation.moveCreationDate),
)
await transaction.save()
await confirmedContribution.save()
}
}
return confirmedContribution
} else {
return contribution
}
return creationFactoryDb(creation)
}

View File

@ -1,46 +1,13 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { TransactionLink } from 'database'
import {
transactionLinkFactory as transactionLinkFactoryDb,
TransactionLinkInterface
} from 'database'
import { transactionLinkExpireDate } from '@/graphql/resolver/TransactionLinkResolver'
import { createTransactionLink, login } from '@/seeds/graphql/mutations'
import { TransactionLinkInterface } from '@/seeds/transactionLink/TransactionLinkInterface'
export { TransactionLinkInterface }
export const transactionLinkFactory = async (
client: ApolloServerTestClient,
export async function transactionLinkFactory (
_client: any,
transactionLink: TransactionLinkInterface,
): Promise<void> => {
const { mutate } = client
// login
await mutate({
mutation: login,
variables: { email: transactionLink.email, password: 'Aa12345_' },
})
const variables = {
amount: transactionLink.amount,
memo: transactionLink.memo,
}
// get the transaction links's id
const {
data: {
createTransactionLink: { id },
},
} = await mutate({ mutation: createTransactionLink, variables })
if (transactionLink.createdAt || transactionLink.deletedAt) {
const dbTransactionLink = await TransactionLink.findOneOrFail({ where: { id } })
if (transactionLink.createdAt) {
dbTransactionLink.createdAt = transactionLink.createdAt
dbTransactionLink.validUntil = transactionLinkExpireDate(transactionLink.createdAt)
await dbTransactionLink.save()
}
if (transactionLink.deletedAt) {
dbTransactionLink.deletedAt = new Date(dbTransactionLink.createdAt.getTime() + 1000)
await dbTransactionLink.save()
}
}
): Promise<void> {
await transactionLinkFactoryDb(transactionLink)
}

View File

@ -1,77 +1,35 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { User } from 'database'
import { User, userFactory as userFactoryDb, userFactoryBulk as userFactoryBulkDb, Community } from 'database'
import { RoleNames } from '@enum/RoleNames'
import { setUserRole } from '@/graphql/resolver/util/modifyUserRole'
import { writeHomeCommunityEntry } from '@/seeds/community'
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { UserInterface } from '@/seeds/users/UserInterface'
import { encryptPassword } from '@/password/PasswordEncryptor'
export const userFactory = async (
client: ApolloServerTestClient,
_client: any,
user: UserInterface,
): Promise<User> => {
const { mutate } = client
const homeCom = await writeHomeCommunityEntry()
// console.log('call createUser with', JSON.stringify(user, null, 2))
const response = await mutate({ mutation: createUser, variables: user })
if (!response?.data?.createUser) {
// console.log(JSON.stringify(response, null, 2))
throw new Error('createUser mutation returned unexpected response')
}
const {
data: {
createUser: { id },
},
} = response
// get user from database
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] })
const emailContact = dbUser.emailContact
const dbUser = await userFactoryDb(user, homeCom)
if (user.emailChecked) {
await mutate({
mutation: setPassword,
variables: { password: 'Aa12345_', code: emailContact.emailVerificationCode },
})
}
// get last changes of user from database
dbUser = await User.findOneOrFail({ where: { id }, relations: { userRoles: true, emailContact: true } })
if (user.createdAt || user.deletedAt || user.role) {
if (user.createdAt) {
dbUser.createdAt = user.createdAt
// make sure emailContact is also updated for e2e test, prevent failing when time between seeding and test run is < 1 minute
dbUser.emailContact.createdAt = user.createdAt
dbUser.emailContact.updatedAt = user.createdAt
await dbUser.emailContact.save()
}
if (user.deletedAt) {
dbUser.deletedAt = user.deletedAt
}
const userRole = user.role as RoleNames
if (userRole && (userRole === RoleNames.ADMIN || userRole === RoleNames.MODERATOR)) {
await setUserRole(dbUser, user.role)
}
const passwortHash = await encryptPassword(dbUser, 'Aa12345_')
dbUser.password = passwortHash
await dbUser.save()
}
try {
if (homeCom.communityUuid) {
dbUser.communityUuid = homeCom.communityUuid
await User.save(dbUser)
}
} catch (_err) {
// no homeCommunity exists
}
// get last changes of user from database
dbUser = await User.findOneOrFail({
where: { id },
withDeleted: true,
relations: ['emailContact', 'userRoles'],
})
return dbUser
}
export async function userFactoryBulk(users: UserInterface[], homeCommunity?: Community | null) {
if (!homeCommunity) {
homeCommunity = await writeHomeCommunityEntry()
}
const dbUsers = await userFactoryBulkDb(users, homeCommunity)
for (const dbUser of dbUsers) {
if (dbUser.emailContact.emailChecked) {
const passwortHash = await encryptPassword(dbUser, 'Aa12345_')
dbUser.password = passwortHash
await dbUser.save()
}
}
return dbUsers
}

View File

@ -1,10 +1,13 @@
import { createTestClient } from 'apollo-server-testing'
import { entities } from 'database'
import { datatype, internet, name } from 'faker'
import {
AppDatabase,
User,
UserInterface,
creationFactoryBulk,
transactionLinkFactoryBulk
} from 'database'
import { internet, name } from 'faker'
import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { createServer } from '@/server/createServer'
import { initLogging } from '@/server/logger'
import { getLogger } from 'log4js'
@ -12,95 +15,87 @@ import { writeHomeCommunityEntry } from './community'
import { contributionLinks } from './contributionLink/index'
import { creations } from './creation/index'
import { contributionLinkFactory } from './factory/contributionLink'
import { creationFactory } from './factory/creation'
import { transactionLinkFactory } from './factory/transactionLink'
import { userFactory } from './factory/user'
import { userFactoryBulk } from './factory/user'
import { transactionLinks } from './transactionLink/index'
import { users } from './users/index'
CORE_CONFIG.EMAIL = false
const RANDOM_USER_COUNT = 100
const logger = getLogger('seed')
const context = {
token: '',
setHeaders: {
push: (value: { key: string; value: string }): void => {
context.token = value.value
},
forEach: (): void => {
// do nothing
},
},
clientTimezoneOffset: 0,
}
export const cleanDB = async () => {
// this only works as long we do not have foreign key constraints
for (const entity of entities) {
if (entity.name !== 'Migration') {
await resetEntity(entity)
}
}
}
const resetEntity = async (entity: any) => {
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((e: any) => e.id)
await entity.delete(ids)
}
}
const run = async () => {
initLogging()
const server = await createServer(getLogger('apollo'), context)
const seedClient = createTestClient(server.apollo)
const { con } = server
await cleanDB()
logger.info('##seed## clean database successful...')
const db = AppDatabase.getInstance()
await db.init()
await clearDatabase(db)
logger.info('clean database successful...')
logger.info(`crypto worker enabled: ${CONFIG.USE_CRYPTO_WORKER}`)
// seed home community
await writeHomeCommunityEntry()
const homeCommunity = await writeHomeCommunityEntry()
// seed the standard users
for (const user of users) {
await userFactory(seedClient, user)
// put into map for later direct access
const userCreationIndexedByEmail = new Map<string, User>()
const defaultUsers = await userFactoryBulk(users, homeCommunity)
for (const dbUser of defaultUsers) {
userCreationIndexedByEmail.set(dbUser.emailContact.email, dbUser)
}
logger.info('##seed## seeding all standard users successful...')
logger.info('seeding all standard users successful...')
// seed 100 random users
for (let i = 0; i < 100; i++) {
await userFactory(seedClient, {
const randomUsers = new Array<UserInterface>(RANDOM_USER_COUNT)
for (let i = 0; i < RANDOM_USER_COUNT; i++) {
randomUsers[i] = {
firstName: name.firstName(),
lastName: name.lastName(),
email: internet.email(),
language: datatype.boolean() ? 'en' : 'de',
})
logger.info(`##seed## seed ${i}. random user`)
language: Math.random() < 0.5 ? 'en' : 'de',
}
}
logger.info('##seed## seeding all random users successful...')
await userFactoryBulk(randomUsers, homeCommunity)
logger.info('seeding all random users successful...')
// create GDD
for (const creation of creations) {
await creationFactory(seedClient, creation)
}
logger.info('##seed## seeding all creations successful...')
const moderatorUser = userCreationIndexedByEmail.get('peter@lustig.de')!
await creationFactoryBulk(creations, userCreationIndexedByEmail, moderatorUser)
logger.info('seeding all creations successful...')
// create Transaction Links
for (const transactionLink of transactionLinks) {
await transactionLinkFactory(seedClient, transactionLink)
}
logger.info('##seed## seeding all transactionLinks successful...')
const movedTransactionLinks = transactionLinks.map(transactionLink => {
let createdAt = new Date(new Date().getTime() + 1000)
if (transactionLink.createdAt) {
createdAt = transactionLink.createdAt
}
return {
...transactionLink,
createdAt: createdAt,
}
})
await transactionLinkFactoryBulk(movedTransactionLinks, userCreationIndexedByEmail)
logger.info('seeding all transactionLinks successful...')
// create Contribution Links
for (const contributionLink of contributionLinks) {
await contributionLinkFactory(seedClient, contributionLink)
await contributionLinkFactory(null, contributionLink)
}
logger.info('##seed## seeding all contributionLinks successful...')
logger.info('seeding all contributionLinks successful...')
await con.destroy()
await db.destroy()
}
async function clearDatabase(db: AppDatabase) {
await db.getDataSource().transaction(async trx => {
await trx.query(`SET FOREIGN_KEY_CHECKS = 0`)
await trx.query(`TRUNCATE TABLE contributions`)
await trx.query(`TRUNCATE TABLE contribution_links`)
await trx.query(`TRUNCATE TABLE users`)
await trx.query(`TRUNCATE TABLE user_contacts`)
await trx.query(`TRUNCATE TABLE user_roles`)
await trx.query(`TRUNCATE TABLE transactions`)
await trx.query(`TRUNCATE TABLE transaction_links`)
await trx.query(`TRUNCATE TABLE communities`)
await trx.query(`SET FOREIGN_KEY_CHECKS = 1`)
})
}
run().catch((err) => {

View File

@ -1,3 +1,5 @@
export { TransactionLinkInterface } from 'database'
/*
export interface TransactionLinkInterface {
email: string
amount: number
@ -8,3 +10,4 @@ export interface TransactionLinkInterface {
// redeemedBy?: number
deletedAt?: boolean
}
*/

View File

@ -1,3 +1,5 @@
export { transactionLinks } from 'database'
/*
import { TransactionLinkInterface } from './TransactionLinkInterface'
export const transactionLinks: TransactionLinkInterface[] = [
@ -53,3 +55,4 @@ bei Gradidio sei dabei!`,
deletedAt: true,
},
]
*/

View File

@ -1,3 +1,5 @@
export { UserInterface } from 'database'
/*
export interface UserInterface {
alias?: string
email?: string
@ -11,3 +13,4 @@ export interface UserInterface {
publisherId?: number
role?: string
}
*/

View File

@ -1,3 +1,5 @@
export { bibiBloxberg } from 'database'
/*
import { UserInterface } from './UserInterface'
export const bibiBloxberg: UserInterface = {
@ -12,3 +14,4 @@ export const bibiBloxberg: UserInterface = {
// move user createdAt before transaction link
createdAt: new Date(2021, 9, 17),
}
*/

View File

@ -1,3 +1,5 @@
export { bobBaumeister } from 'database'
/*
import { UserInterface } from './UserInterface'
export const bobBaumeister: UserInterface = {
@ -9,3 +11,4 @@ export const bobBaumeister: UserInterface = {
emailChecked: true,
language: 'de',
}
*/

View File

@ -1,3 +1,6 @@
export { garrickOllivander } from 'database'
/*
import { UserInterface } from './UserInterface'
export const garrickOllivander: UserInterface = {
@ -10,3 +13,4 @@ export const garrickOllivander: UserInterface = {
emailChecked: false,
language: 'en',
}
*/

View File

@ -1,3 +1,5 @@
export { peterLustig } from 'database'
/*
import { RoleNames } from '@enum/RoleNames'
import { UserInterface } from './UserInterface'
@ -12,3 +14,4 @@ export const peterLustig: UserInterface = {
language: 'de',
role: RoleNames.ADMIN,
}
*/

View File

@ -1,3 +1,6 @@
export { raeuberHotzenplotz } from 'database'
/*
import { UserInterface } from './UserInterface'
export const raeuberHotzenplotz: UserInterface = {
@ -8,3 +11,4 @@ export const raeuberHotzenplotz: UserInterface = {
emailChecked: true,
language: 'de',
}
*/

View File

@ -1,3 +1,5 @@
export { stephenHawking } from 'database'
/*
import { UserInterface } from './UserInterface'
export const stephenHawking: UserInterface = {
@ -10,3 +12,4 @@ export const stephenHawking: UserInterface = {
deletedAt: new Date('2018-03-14T09:17:52'),
language: 'en',
}
*/

View File

@ -14,7 +14,6 @@ import { GRADIDO_REALM, LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { AppDatabase } from 'database'
import { context as serverContext } from './context'
import { cors } from './cors'
import { i18n } from './localization'
import { plugins } from './plugins'
import { jwks, openidConfiguration } from '@/openIDConnect'
// TODO implement
@ -73,9 +72,6 @@ export const createServer = async (
app.use(json())
// bodyparser urlencoded for elopage
app.use(urlencoded({ extended: true }))
// i18n
app.use(i18n.init)
// Elopage Webhook

View File

@ -1,33 +0,0 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import i18n from 'i18n'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.localization`)
i18n.configure({
locales: ['en', 'de'],
defaultLocale: 'en',
retryInDefaultLocale: false,
staticCatalog: {
en: { general: { decimalSeparator: "." } },
de: { general: { decimalSeparator: "," } },
},
// autoReload: true, // if this is activated the seeding hangs at the very end
updateFiles: false,
objectNotation: true,
logDebugFn: (msg) => logger.debug(msg),
logWarnFn: (msg) => logger.info(msg),
logErrorFn: (msg) => logger.error(msg),
// this api is needed for email-template pug files
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn
},
register: global,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
})
export { i18n }

View File

@ -61,7 +61,7 @@
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [ /* List of folders to include type definitions from. */
"@types",
"../@types",
"node_modules/@types",
"../node_modules/@types",
],
@ -87,7 +87,9 @@
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"../core/src/types"
"../core/src/types",
"src",
"test"
],
"ts-node": {
"swc": true

View File

@ -245,7 +245,7 @@
"@types/mysql": "^2.15.27",
"@types/node": "^18.7.14",
"await-semaphore": "^0.1.3",
"crypto-random-bigint": "^2.1.1",
"random-bigint": "^0.0.1",
"ts-node": "^10.9.2",
"typescript": "^4.9.5",
},
@ -634,6 +634,8 @@
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.10.1", "", {}, "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg=="],
"@cacheable/memoize": ["@cacheable/memoize@2.0.3", "", { "dependencies": { "@cacheable/utils": "^2.0.3" } }, "sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw=="],
"@cacheable/memory": ["@cacheable/memory@2.0.3", "", { "dependencies": { "@cacheable/memoize": "^2.0.3", "@cacheable/utils": "^2.0.3", "@keyv/bigmap": "^1.0.2", "hookified": "^1.12.1", "keyv": "^5.5.3" } }, "sha512-R3UKy/CKOyb1LZG/VRCTMcpiMDyLH7SH3JrraRdK6kf3GweWCOU3sgvE13W3TiDRbxnDKylzKJvhUAvWl9LQOA=="],
@ -1628,6 +1630,8 @@
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
@ -1712,6 +1716,8 @@
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
@ -1768,8 +1774,6 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crypto-random-bigint": ["crypto-random-bigint@2.1.1", "", { "dependencies": { "uint-rng": "^1.2.1" } }, "sha512-96+FDrenXybkpnLML/60S8NcG32KgJ5Y8yvNNCYPW02r+ssoXFR5XKenuIQcHLWumnGj8UPqUUHBzXNrDGkDmQ=="],
"css-functions-list": ["css-functions-list@3.2.3", "", {}, "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA=="],
"css-select": ["css-select@4.3.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" } }, "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ=="],
@ -3070,7 +3074,45 @@
"safety-catch": ["safety-catch@1.0.2", "", {}, "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA=="],
"sass": ["sass@1.93.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg=="],
"sass": ["sass@1.93.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg=="],
"sass-embedded": ["sass-embedded@1.93.3", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.93.3", "sass-embedded-android-arm": "1.93.3", "sass-embedded-android-arm64": "1.93.3", "sass-embedded-android-riscv64": "1.93.3", "sass-embedded-android-x64": "1.93.3", "sass-embedded-darwin-arm64": "1.93.3", "sass-embedded-darwin-x64": "1.93.3", "sass-embedded-linux-arm": "1.93.3", "sass-embedded-linux-arm64": "1.93.3", "sass-embedded-linux-musl-arm": "1.93.3", "sass-embedded-linux-musl-arm64": "1.93.3", "sass-embedded-linux-musl-riscv64": "1.93.3", "sass-embedded-linux-musl-x64": "1.93.3", "sass-embedded-linux-riscv64": "1.93.3", "sass-embedded-linux-x64": "1.93.3", "sass-embedded-unknown-all": "1.93.3", "sass-embedded-win32-arm64": "1.93.3", "sass-embedded-win32-x64": "1.93.3" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw=="],
"sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.93.3", "", { "dependencies": { "sass": "1.93.3" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.93.3", "", { "os": "android", "cpu": "arm" }, "sha512-8xOw9bywfOD6Wv24BgCmgjkk6tMrsOTTHcb28KDxeJtFtoxiUyMbxo0vChpPAfp2Hyg2tFFKS60s0s4JYk+Raw=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.93.3", "", { "os": "android", "cpu": "arm64" }, "sha512-uqUl3Kt1IqdGVAcAdbmC+NwuUJy8tM+2ZnB7/zrt6WxWVShVCRdFnWR9LT8HJr7eJN7AU8kSXxaVX/gedanPsg=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.93.3", "", { "os": "android", "cpu": "none" }, "sha512-2jNJDmo+3qLocjWqYbXiBDnfgwrUeZgZFHJIwAefU7Fn66Ot7rsXl+XPwlokaCbTpj7eMFIqsRAZ/uDueXNCJg=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.93.3", "", { "os": "android", "cpu": "x64" }, "sha512-y0RoAU6ZenQFcjM9PjQd3cRqRTjqwSbtWLL/p68y2oFyh0QGN0+LQ826fc0ZvU/AbqCsAizkqjzOn6cRZJxTTQ=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.93.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7zb/hpdMOdKteK17BOyyypemglVURd1Hdz6QGsggy60aUFfptTLQftLRg8r/xh1RbQAUKWFbYTNaM47J9yPxYg=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.93.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ek1Vp8ZDQEe327Lz0b7h3hjvWH3u9XjJiQzveq74RPpJQ2q6d9LfWpjiRRohM4qK6o4XOHw1X10OMWPXJtdtWg=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.93.3", "", { "os": "linux", "cpu": "arm" }, "sha512-yeiv2y+dp8B4wNpd3+JsHYD0mvpXSfov7IGyQ1tMIR40qv+ROkRqYiqQvAOXf76Qwh4Y9OaYZtLpnsPjfeq6mA=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.93.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RBrHWgfd8Dd8w4fbmdRVXRrhh8oBAPyeWDTKAWw8ZEmuXfVl4ytjDuyxaVilh6rR1xTRTNpbaA/YWApBlLrrNw=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.93.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fU0fwAwbp7sBE3h5DVU5UPzvaLg7a4yONfFWkkcCp6ZrOiPuGRHXXYriWQ0TUnWy4wE+svsVuWhwWgvlb/tkKg=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.93.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-PS829l+eUng+9W4PFclXGb4uA2+965NHV3/Sa5U7qTywjeeUUYTZg70dJHSqvhrBEfCc2XJABeW3adLJbyQYkw=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.93.3", "", { "os": "linux", "cpu": "none" }, "sha512-cK1oBY+FWQquaIGEeQ5H74KTO8cWsSWwXb/WaildOO9U6wmUypTgUYKQ0o5o/29nZbWWlM1PHuwVYTSnT23Jjg=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.93.3", "", { "os": "linux", "cpu": "x64" }, "sha512-A7wkrsHu2/I4Zpa0NMuPGkWDVV7QGGytxGyUq3opSXgAexHo/vBPlGoDXoRlSdex0cV+aTMRPjoGIfdmNlHwyg=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.93.3", "", { "os": "linux", "cpu": "none" }, "sha512-vWkW1+HTF5qcaHa6hO80gx/QfB6GGjJUP0xLbnAoY4pwEnw5ulGv6RM8qYr8IDhWfVt/KH+lhJ2ZFxnJareisQ=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.93.3", "", { "os": "linux", "cpu": "x64" }, "sha512-k6uFxs+e5jSuk1Y0niCwuq42F9ZC5UEP7P+RIOurIm8w/5QFa0+YqeW+BPWEW5M1FqVOsNZH3qGn4ahqvAEjPA=="],
"sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.93.3", "", { "dependencies": { "sass": "1.93.3" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-o5wj2rLpXH0C+GJKt/VpWp6AnMsCCbfFmnMAttcrsa+U3yrs/guhZ3x55KAqqUsE8F47e3frbsDL+1OuQM5DAA=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.93.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-0dOfT9moy9YmBolodwYYXtLwNr4jL4HQC9rBfv6mVrD7ud8ue2kDbn+GVzj1hEJxvEexVSmDCf7MHUTLcGs9xQ=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.93.3", "", { "os": "win32", "cpu": "x64" }, "sha512-wHFVfxiS9hU/sNk7KReD+lJWRp3R0SLQEX4zfOnRP2zlvI2X4IQR5aZr9GNcuMP6TmNpX0nQPZTegS8+h9RrEg=="],
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
@ -3246,6 +3288,10 @@
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
"synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="],
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
@ -3278,8 +3324,6 @@
"tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="],
"tiny-webcrypto": ["tiny-webcrypto@1.0.3", "", {}, "sha512-LQQdNMAgz9BXNT2SKbYh3eCb+fLV0p7JB7MwUjzY6IOlQLGIadfnFqRpshERsS5Dl2OM/hs0+4I/XmSrF+RBbw=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
@ -3392,8 +3436,6 @@
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"uint-rng": ["uint-rng@1.2.1", "", { "dependencies": { "tiny-webcrypto": "^1.0.2" } }, "sha512-swhDg5H+3DX2sIvnYA7VMBMXV/t8mPxvh49CjCDkwFmj/3OZIDOQwJANBgM1MPSUBrUHNIlXmU7/GcL7m4907g=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
@ -3450,6 +3492,8 @@
"validator": ["validator@13.15.15", "", {}, "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A=="],
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"vee-validate": ["vee-validate@4.15.1", "", { "dependencies": { "@vue/devtools-api": "^7.5.2", "type-fest": "^4.8.3" }, "peerDependencies": { "vue": "^3.4.26" } }, "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg=="],
@ -4112,6 +4156,8 @@
"safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"sass-embedded/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"schema-utils/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"seek-bzip/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],

View File

@ -1,6 +1,6 @@
{
"name": "config-schema",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido Config for validate config",
"main": "./build/index.js",
"types": "./src/index.ts",

View File

@ -1,6 +1,6 @@
{
"name": "core",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
"main": "./build/index.js",
"types": "./src/index.ts",
@ -22,8 +22,8 @@
"typecheck": "tsc --noEmit",
"lint": "biome check --error-on-warnings .",
"lint:fix": "biome check --error-on-warnings . --write",
"locales": "scripts/sort.sh src/emails/locales",
"locales:fix": "scripts/sort.sh src/emails/locales --fix",
"locales": "scripts/sort.sh src/locales",
"locales:fix": "scripts/sort.sh src/locales --fix",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
},
"dependencies": {

View File

@ -1,6 +1,6 @@
import { createTransport } from 'nodemailer'
import { CONFIG } from '../config'
import { i18n } from './localization'
import { i18n } from '../locales/localization'
import { getLogger } from '../../../config-schema/test/testSetup.bun'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { sendEmailTranslated } from './sendEmailTranslated'
@ -41,7 +41,7 @@ const spySetLocale = jest.spyOn(i18n, 'setLocale')
const spyTranslate = jest.spyOn(i18n, '__')
describe('sendEmailTranslated', () => {
let result: Record<string, unknown> | boolean | null
let result: Record<string, unknown> | boolean | Error | null
describe('config email is false', () => {
beforeEach(async () => {

View File

@ -1,6 +1,6 @@
import path from 'path'
import Email from 'email-templates'
import { i18n } from './localization'
import { i18n } from '../locales/localization'
import { createTransport } from 'nodemailer'
import { CONFIG } from '../config'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
@ -113,7 +113,6 @@ export const sendEmailTranslated = async ({
})
.catch((error: unknown) => {
logger.error('Error sending notification email', error)
return error
})
return resultSend

View File

@ -47,6 +47,10 @@ describe('sendEmailVariants', () => {
const contributionFrontendLink =
'https://gradido.net/contributions/own-contributions/1#contributionListItem-1'
afterEach(() => {
sendEmailTranslatedSpy.mockClear()
})
describe('sendAddedContributionMessageEmail', () => {
beforeAll(async () => {
result = await sendAddedContributionMessageEmail({
@ -162,7 +166,7 @@ describe('sendEmailVariants', () => {
})
})
/*
describe('sendAccountMultiRegistrationEmail', () => {
beforeAll(async () => {
@ -181,20 +185,22 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'accountMultiRegistration',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
}),
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -235,24 +241,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionConfirmed',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionAmount: '23.54',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
contributionFrontendLink,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -292,24 +300,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionChangedByModerator',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionMemoUpdated: 'This is a better contribution memo.',
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -348,23 +358,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionDenied',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
},
}),
})
})
})
describe('result', () => {
it('has expected result', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -403,23 +415,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionDeleted',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -456,23 +470,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'resetPassword',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
resetLink: 'http://localhost/reset-password/3762660021544901417',
timeDurationObject: { hours: 23, minutes: 30 },
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -512,10 +528,10 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'transactionLinkRedeemed',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
senderEmail: 'bibi@bloxberg.de',
@ -523,14 +539,16 @@ describe('sendEmailVariants', () => {
transactionAmount: '17.65',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -570,10 +588,10 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'transactionReceived',
locals: {
locals: expect.objectContaining({
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
language: 'en',
memo: 'Du bist schon lustiger ;)',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
@ -581,14 +599,16 @@ describe('sendEmailVariants', () => {
transactionAmount: '37.40',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
}),
})
})
})
describe('result', () => {
it('is the expected object', () => {
expect(result).toMatchObject({
// bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -605,5 +625,4 @@ describe('sendEmailVariants', () => {
})
})
})
*/
})

View File

@ -1,13 +1,6 @@
import { registerEnumType } from 'type-graphql'
export enum TransactionTypeId {
CREATION = 1,
SEND = 2,
RECEIVE = 3,
// This is a virtual property, never occurring on the database
DECAY = 4,
LINK_SUMMARY = 5,
}
import { TransactionTypeId } from 'database'
export { TransactionTypeId }
registerEnumType(TransactionTypeId, {
name: 'TransactionTypeId', // this one is mandatory

View File

@ -1,6 +1,10 @@
import en from './locales/en.json'
import de from './locales/de.json'
import en from './en.json'
import de from './de.json'
import { I18n } from 'i18n'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.localization`)
function flatten(obj: any, prefix: string = ''): any {
const result: any = {}
@ -18,6 +22,9 @@ export const i18n = new I18n({
locales: ['en', 'de'],
defaultLocale: 'en',
staticCatalog: { en: flatten(en), de: flatten(de) },
logDebugFn: (msg) => logger.debug(msg),
logWarnFn: (msg) => logger.info(msg),
logErrorFn: (msg) => logger.error(msg),
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn

View File

@ -1,7 +1,8 @@
import { promisify } from 'util'
import { Decimal } from 'decimal.js-light'
import i18n from 'i18n'
import { i18n } from '../locales/localization'
export { fullName } from 'shared'
export const objectValuesToArray = (obj: Record<string, string>): string[] =>
Object.keys(obj).map((key) => obj[key])
@ -14,11 +15,7 @@ export const decimalSeparatorByLanguage = (a: Decimal, language: string): string
return result
}
export const fullName = (firstName: string, lastName: string): string =>
[firstName, lastName].filter(Boolean).join(' ')
// Function to reset an interface by chatGPT
export function resetInterface<T extends Record<string, any>>(obj: T): T {
// Iterate over all properties of the object
for (const key in obj) {

View File

@ -49,8 +49,12 @@
// "paths": { }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
/* List of folders to include type definitions from. */
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
// "types": ["bun-types"], /* Type declaration files to be included in compilation. */
"typeRoots": [
"./node_modules/@types",
"../node_modules/@types",
"../@types"
],
// "types": ["bun-types", "../@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'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */

View File

@ -1,6 +1,6 @@
{
"name": "database",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido Database Tool to execute database migrations",
"main": "./build/index.js",
"types": "./src/index.ts",
@ -38,7 +38,7 @@
"@types/mysql": "^2.15.27",
"@types/node": "^18.7.14",
"await-semaphore": "^0.1.3",
"crypto-random-bigint": "^2.1.1",
"random-bigint": "^0.0.1",
"ts-node": "^10.9.2",
"typescript": "^4.9.5"
},

View File

@ -0,0 +1,22 @@
// lowercase values are not implemented yet
export enum ContributionCycleType {
ONCE = 'ONCE',
HOUR = 'hour',
TWO_HOURS = 'two_hours',
FOUR_HOURS = 'four_hours',
EIGHT_HOURS = 'eight_hours',
HALF_DAY = 'half_day',
DAILY = 'DAILY',
TWO_DAYS = 'two_days',
THREE_DAYS = 'three_days',
FOUR_DAYS = 'four_days',
FIVE_DAYS = 'five_days',
SIX_DAYS = 'six_days',
WEEK = 'week',
TWO_WEEKS = 'two_weeks',
MONTH = 'month',
TWO_MONTH = 'two_month',
QUARTER = 'quarter',
HALF_YEAR = 'half_year',
YEAR = 'year',
}

View File

@ -0,0 +1,7 @@
export enum ContributionStatus {
PENDING = 'PENDING',
DELETED = 'DELETED',
IN_PROGRESS = 'IN_PROGRESS',
DENIED = 'DENIED',
CONFIRMED = 'CONFIRMED',
}

View File

@ -0,0 +1,5 @@
export enum ContributionType {
ADMIN = 'ADMIN',
USER = 'USER',
LINK = 'LINK',
}

View File

@ -0,0 +1,8 @@
export enum RoleNames {
UNAUTHORIZED = 'UNAUTHORIZED',
USER = 'USER',
MODERATOR = 'MODERATOR',
MODERATOR_AI = 'MODERATOR_AI',
ADMIN = 'ADMIN',
DLT_CONNECTOR = 'DLT_CONNECTOR_ROLE',
}

View File

@ -0,0 +1,8 @@
export enum TransactionTypeId {
CREATION = 1,
SEND = 2,
RECEIVE = 3,
// This is a virtual property, never occurring on the database
DECAY = 4,
LINK_SUMMARY = 5,
}

View File

@ -1 +1,6 @@
export * from './CommunityHandshakeStateType'
export * from './CommunityHandshakeStateType'
export * from './ContributionType'
export * from './ContributionStatus'
export * from './TransactionTypeId'
export * from './ContributionCycleType'
export * from './RoleNames'

View File

@ -4,6 +4,7 @@ export { latestDbVersion }
export * from './entity'
export * from './logging'
export * from './queries'
export * from './seeds'
export * from './util'
export * from './enum'
export { AppDatabase } from './AppDatabase'

View File

@ -0,0 +1,7 @@
export interface ContributionLinkInterface {
amount: number
name: string
memo: string
validFrom?: Date
validTo?: Date
}

View File

@ -0,0 +1,19 @@
import { ContributionLinkInterface } from './ContributionLinkInterface'
export type { ContributionLinkInterface }
export const contributionLinks: ContributionLinkInterface[] = [
{
name: 'Dokumenta 2017',
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2017',
amount: 200,
validFrom: new Date(2017, 3, 8),
validTo: new Date(2017, 6, 16),
},
{
name: 'Dokumenta 2022',
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2022',
amount: 200,
validFrom: new Date(2022, 5, 18),
validTo: new Date(2022, 8, 25),
},
]

View File

@ -0,0 +1,9 @@
export interface CreationInterface {
email: string
amount: number
memo: string
contributionDate: string
confirmed?: boolean
// number of months to move the confirmed creation to the past
moveCreationDate?: number
}

View File

@ -0,0 +1,156 @@
import { nMonthsBefore } from '../factory/creation'
import { CreationInterface } from './CreationInterface'
export type { CreationInterface }
const bobsSendings = [
{
amount: 10,
memo: 'Herzlich Willkommen bei Gradido!',
},
{
amount: 10,
memo: 'für deine Hilfe, Betty',
},
{
amount: 23.37,
memo: 'für deine Hilfe, David',
},
{
amount: 47,
memo: 'für deine Hilfe, Frau Holle',
},
{
amount: 1.02,
memo: 'für deine Hilfe, Herr Müller',
},
{
amount: 5.67,
memo: 'für deine Hilfe, Maier',
},
{
amount: 72.93,
memo: 'für deine Hilfe, Elsbeth',
},
{
amount: 5.6,
memo: 'für deine Hilfe, Daniel',
},
{
amount: 8.87,
memo: 'für deine Hilfe, Yoda',
},
{
amount: 7.56,
memo: 'für deine Hilfe, Sabine',
},
{
amount: 7.89,
memo: 'für deine Hilfe, Karl',
},
{
amount: 8.9,
memo: 'für deine Hilfe, Darth Vader',
},
{
amount: 56.79,
memo: 'für deine Hilfe, Luci',
},
{
amount: 3.45,
memo: 'für deine Hilfe, Hanne',
},
{
amount: 8.74,
memo: 'für deine Hilfe, Luise',
},
{
amount: 7.85,
memo: 'für deine Hilfe, Annegred',
},
{
amount: 32.7,
memo: 'für deine Hilfe, Prinz von Zamunda',
},
{
amount: 44.2,
memo: 'für deine Hilfe, Charly Brown',
},
{
amount: 38.17,
memo: 'für deine Hilfe, Michael',
},
{
amount: 5.72,
memo: 'für deine Hilfe, Kaja',
},
{
amount: 3.99,
memo: 'für deine Hilfe, Maja',
},
{
amount: 4.5,
memo: 'für deine Hilfe, Martha',
},
{
amount: 8.3,
memo: 'für deine Hilfe, Ursula',
},
{
amount: 2.9,
memo: 'für deine Hilfe, Urs',
},
{
amount: 4.6,
memo: 'für deine Hilfe, Mecedes',
},
{
amount: 74.1,
memo: 'für deine Hilfe, Heidi',
},
{
amount: 4.5,
memo: 'für deine Hilfe, Peter',
},
{
amount: 5.8,
memo: 'für deine Hilfe, Fräulein Rottenmeier',
},
]
const bobsTransactions: CreationInterface[] = []
bobsSendings.forEach((sending) => {
bobsTransactions.push({
email: 'bob@baumeister.de',
amount: sending.amount,
memo: sending.memo,
contributionDate: nMonthsBefore(new Date()),
confirmed: true,
})
})
export const creations: CreationInterface[] = [
{
email: 'bibi@bloxberg.de',
amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!',
contributionDate: nMonthsBefore(new Date()),
confirmed: true,
moveCreationDate: 12,
},
{
email: 'bibi@bloxberg.de',
amount: 1000,
memo: '#Hexen',
contributionDate: nMonthsBefore(new Date()),
confirmed: true,
},
...bobsTransactions,
{
email: 'raeuber@hotzenplotz.de',
amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!',
contributionDate: nMonthsBefore(new Date()),
confirmed: true,
},
]

View File

@ -0,0 +1,31 @@
import Decimal from 'decimal.js-light'
import { ContributionLink } from '../../entity'
import { ContributionLinkInterface } from '../contributionLink/ContributionLinkInterface'
import { transactionLinkCode } from './transactionLink'
import { ContributionCycleType } from '../../enum'
export function contributionLinkFactory(contributionLink: ContributionLinkInterface): Promise<ContributionLink> {
return createContributionLink(contributionLink)
}
export function createContributionLink(contributionLinkData: ContributionLinkInterface): Promise<ContributionLink> {
const contributionLink = new ContributionLink()
contributionLink.amount = new Decimal(contributionLinkData.amount)
contributionLink.name = contributionLinkData.name
contributionLink.memo = contributionLinkData.memo
contributionLink.createdAt = new Date()
contributionLink.code = transactionLinkCode(new Date())
contributionLink.cycle = ContributionCycleType.ONCE
if (contributionLinkData.validFrom) {
contributionLink.validFrom = contributionLinkData.validFrom
}
if (contributionLinkData.validTo) {
contributionLink.validTo = contributionLinkData.validTo
}
contributionLink.maxAmountPerMonth = new Decimal(200)
contributionLink.maxPerCycle = 1
return contributionLink.save()
}

View File

@ -0,0 +1,134 @@
import { Contribution, Transaction, User } from '../../entity'
import { Decimal } from 'decimal.js-light'
import { CreationInterface } from '../creation/CreationInterface'
import { ContributionType, ContributionStatus, TransactionTypeId } from '../../enum'
import { findUserByIdentifier } from '../../queries'
import { createTransaction } from './transaction'
import { AppDatabase } from '../../AppDatabase'
export function nMonthsBefore(date: Date, months = 1): string {
return new Date(date.getFullYear(), date.getMonth() - months, 1).toISOString()
}
export async function creationFactory(
creation: CreationInterface,
user?: User | null,
moderatorUser?: User | null,
): Promise<Contribution> {
if (!user) {
user = await findUserByIdentifier(creation.email)
}
if (!user) {
throw new Error(`User ${creation.email} not found`)
}
let contribution = await createContribution(creation, user)
if (creation.confirmed) {
if (!moderatorUser) {
moderatorUser = await findUserByIdentifier('peter@lustig.de')
}
if (!moderatorUser) {
throw new Error('Moderator user not found')
}
await confirmTransaction(creation, contribution, moderatorUser)
}
return contribution
}
export async function creationFactoryBulk(
creations: CreationInterface[],
userCreationIndexedByEmail: Map<string, User>,
moderatorUser: User,
): Promise<Contribution[]> {
const lastTransaction = await Transaction.findOne({ order: { id: 'DESC' }, select: ['id'], where: {} })
let transactionId = lastTransaction ? lastTransaction.id + 1 : 1
const dbContributions: Contribution[] = []
const dbTransactions: Transaction[] = []
for (const creation of creations) {
const user = userCreationIndexedByEmail.get(creation.email)
if (!user) {
throw new Error(`User ${creation.email} not found`)
}
let contribution = await createContribution(creation, user, false)
if (creation.confirmed) {
const { contribution: _, transaction } = await confirmTransaction(
creation,
contribution,
moderatorUser,
transactionId,
false
)
dbTransactions.push(transaction)
transactionId++
}
dbContributions.push(contribution)
}
const dataSource = AppDatabase.getInstance().getDataSource()
await dataSource.transaction(async (transaction) => {
await dataSource.getRepository(Contribution).insert(dbContributions)
await dataSource.getRepository(Transaction).insert(dbTransactions)
})
return dbContributions
}
export async function createContribution(creation: CreationInterface, user: User, store: boolean = true): Promise<Contribution> {
const contribution = new Contribution()
contribution.user = user
contribution.userId = user.id
contribution.amount = new Decimal(creation.amount)
contribution.createdAt = new Date()
contribution.contributionDate = getContributionDate(creation)
contribution.memo = creation.memo
contribution.contributionType = ContributionType.USER
contribution.contributionStatus = ContributionStatus.PENDING
return store ? contribution.save() : contribution
}
export async function confirmTransaction(
creation: CreationInterface,
contribution: Contribution,
moderatorUser: User,
transactionId?: number,
store: boolean = true
): Promise<{ contribution: Contribution, transaction: Transaction }> {
const balanceDate = getBalanceDate(creation)
const transaction = await createTransaction(
contribution.amount,
contribution.memo,
contribution.user,
moderatorUser,
TransactionTypeId.CREATION,
balanceDate,
contribution.contributionDate,
transactionId,
store,
)
contribution.confirmedAt = balanceDate
contribution.confirmedBy = moderatorUser.id
contribution.transactionId = transaction.id
contribution.transaction = transaction
contribution.contributionStatus = ContributionStatus.CONFIRMED
if (store) {
await contribution.save()
await transaction.save()
}
return { contribution, transaction }
}
function getContributionDate(creation: CreationInterface): Date {
if (creation.moveCreationDate) {
return new Date(nMonthsBefore(new Date(creation.contributionDate), creation.moveCreationDate))
}
return new Date(creation.contributionDate)
}
function getBalanceDate(creation: CreationInterface): Date {
const now = new Date()
if (creation.moveCreationDate) {
return new Date(nMonthsBefore(now, creation.moveCreationDate))
}
return now
}

View File

@ -0,0 +1,6 @@
export * from './contributionLink'
export * from './creation'
export * from './pendingTransaction'
export * from './transaction'
export * from './transactionLink'
export * from './user'

View File

@ -0,0 +1,59 @@
import Decimal from 'decimal.js-light'
import { User, Transaction } from '../../entity'
import { TransactionTypeId } from '../../enum'
import { fullName } from 'shared'
import { getLastTransaction } from '../../queries'
import { calculateDecay, Decay } from 'shared'
export async function createTransaction(
amount: Decimal,
memo: string,
user: User,
linkedUser: User,
type: TransactionTypeId,
balanceDate: Date,
creationDate?: Date,
id?: number,
store: boolean = true,
): Promise<Transaction> {
const lastTransaction = await getLastTransaction(user.id)
// balance and decay calculation
let newBalance = new Decimal(0)
let decay: Decay | null = null
if (lastTransaction) {
decay = calculateDecay(
lastTransaction.balance,
lastTransaction.balanceDate,
balanceDate,
)
newBalance = decay.balance
}
newBalance = newBalance.add(amount.toString())
const transaction = new Transaction()
if (id) {
transaction.id = id
}
transaction.typeId = type
transaction.memo = memo
transaction.userId = user.id
transaction.userGradidoID = user.gradidoID
transaction.userName = fullName(user.firstName, user.lastName)
transaction.userCommunityUuid = user.communityUuid
transaction.linkedUserId = linkedUser.id
transaction.linkedUserGradidoID = linkedUser.gradidoID
transaction.linkedUserName = fullName(linkedUser.firstName, linkedUser.lastName)
transaction.linkedUserCommunityUuid = linkedUser.communityUuid
transaction.previous = lastTransaction ? lastTransaction.id : null
transaction.amount = amount
if (creationDate) {
transaction.creationDate = creationDate
}
transaction.balance = newBalance
transaction.balanceDate = balanceDate
transaction.decay = decay ? decay.decay : new Decimal(0)
transaction.decayStart = decay ? decay.start : null
return store ? transaction.save() : transaction
}

View File

@ -0,0 +1,77 @@
import { TransactionLinkInterface } from '../transactionLink/TransactionLinkInterface'
import { TransactionLink, User } from '../../entity'
import { Decimal } from 'decimal.js-light'
import { findUserByIdentifier } from '../../queries'
import { compoundInterest } from 'shared'
import { randomBytes } from 'node:crypto'
import { AppDatabase } from '../../AppDatabase'
export async function transactionLinkFactory(
transactionLinkData: TransactionLinkInterface,
userId?: number,
): Promise<TransactionLink> {
if (!userId) {
const user = await findUserByIdentifier(transactionLinkData.email)
if (!user) {
throw new Error(`User ${transactionLinkData.email} not found`)
}
userId = user.id
}
return createTransactionLink(transactionLinkData, userId)
}
export async function transactionLinkFactoryBulk(
transactionLinks: TransactionLinkInterface[],
userCreationIndexedByEmail: Map<string, User>
): Promise<TransactionLink[]> {
const dbTransactionLinks: TransactionLink[] = []
for (const transactionLink of transactionLinks) {
const user = userCreationIndexedByEmail.get(transactionLink.email)
if (!user) {
throw new Error(`User ${transactionLink.email} not found`)
}
dbTransactionLinks.push(await createTransactionLink(transactionLink, user.id, false))
}
const dataSource = AppDatabase.getInstance().getDataSource()
await dataSource.getRepository(TransactionLink).insert(dbTransactionLinks)
return dbTransactionLinks
}
export async function createTransactionLink(transactionLinkData: TransactionLinkInterface, userId: number, store: boolean = true): Promise<TransactionLink> {
const holdAvailableAmount = compoundInterest(new Decimal(transactionLinkData.amount.toString()), CODE_VALID_DAYS_DURATION * 24 * 60 * 60)
let createdAt = transactionLinkData.createdAt || new Date()
const validUntil = transactionLinkExpireDate(createdAt)
const transactionLink = new TransactionLink()
transactionLink.userId = userId
transactionLink.amount = new Decimal(transactionLinkData.amount)
transactionLink.memo = transactionLinkData.memo
transactionLink.holdAvailableAmount = holdAvailableAmount
transactionLink.code = transactionLinkCode(createdAt)
transactionLink.createdAt = createdAt
transactionLink.validUntil = validUntil
if (transactionLinkData.deletedAt) {
transactionLink.deletedAt = new Date(createdAt.getTime() + 1000)
}
return store ? transactionLink.save() : transactionLink
}
////// Transaction Link BUSINESS LOGIC //////
// TODO: move business logic to shared
export const CODE_VALID_DAYS_DURATION = 14
export const transactionLinkExpireDate = (date: Date): Date => {
const validUntil = new Date(date)
return new Date(validUntil.setDate(date.getDate() + CODE_VALID_DAYS_DURATION))
}
export const transactionLinkCode = (date: Date): string => {
const time = date.getTime().toString(16)
return (
randomBytes(12)
.toString('hex')
.substring(0, 24 - time.length) + time
)
}

View File

@ -1,16 +1,77 @@
import { UserInterface } from '../users/UserInterface'
import { User, UserContact } from '../../entity'
import { User, UserContact, UserRole } from '../../entity'
import { v4 } from 'uuid'
import { UserContactType, OptInType, PasswordEncryptionType } from 'shared'
import { getHomeCommunity } from '../../queries/communities'
import random from 'crypto-random-bigint'
import random from 'random-bigint'
import { Community } from '../../entity'
import { AppDatabase } from '../..'
import { RoleNames } from '../../enum/RoleNames'
export const userFactory = async (user: UserInterface): Promise<User> => {
let dbUserContact = new UserContact()
export async function userFactory(user: UserInterface, homeCommunity?: Community | null): Promise<User> {
// TODO: improve with cascade
let dbUser = await createUser(user, homeCommunity)
let dbUserContact = await createUserContact(user, dbUser.id)
dbUser.emailId = dbUserContact.id
dbUser.emailContact = dbUserContact
dbUser = await dbUser.save()
dbUserContact.email = user.email ?? ''
dbUserContact.type = UserContactType.USER_CONTACT_EMAIL
const userRole = user.role as RoleNames
if (userRole && (userRole === RoleNames.ADMIN || userRole === RoleNames.MODERATOR)) {
dbUser.userRoles = [await createUserRole(dbUser.id, userRole)]
}
return dbUser
}
// only use in non-parallel environment (seeding for example)
export async function userFactoryBulk(users: UserInterface[], homeCommunity?: Community | null): Promise<User[]> {
const dbUsers: User[] = []
const dbUserContacts: UserContact[] = []
const dbUserRoles: UserRole[] = []
const lastUser = await User.findOne({ order: { id: 'DESC' }, select: ['id'], where: {} })
const lastUserContact = await UserContact.findOne({ order: { id: 'DESC' }, select: ['id'], where: {} })
let userId = lastUser ? lastUser.id + 1 : 1
let emailId = lastUserContact ? lastUserContact.id + 1 : 1
// console.log(`start with userId: ${userId} and emailId: ${emailId}`)
for(const user of users) {
const dbUser = await createUser(user, homeCommunity, false)
dbUser.id = userId
dbUser.emailId = emailId
const dbUserContact = await createUserContact(user, userId, false)
dbUserContact.id = emailId
dbUserContact.userId = userId
dbUser.emailContact = dbUserContact
dbUsers.push(dbUser)
dbUserContacts.push(dbUserContact)
const userRole = user.role as RoleNames
if (userRole && (userRole === RoleNames.ADMIN || userRole === RoleNames.MODERATOR)) {
dbUserRoles.push(await createUserRole(dbUser.id, userRole, false))
}
userId++
emailId++
}
const dataSource = AppDatabase.getInstance().getDataSource()
await dataSource.transaction(async transaction => {
// typeorm change my data what I don't want
// because of manuel id assignment
const dbUsersCopy = dbUsers.map(user => ({ ...user }))
const dbUserContactsCopy = dbUserContacts.map(userContact => ({ ...userContact }))
const dbUserRolesCopy = dbUserRoles.map(userRole => ({ ...userRole }))
await Promise.all([
transaction.getRepository(User).insert(dbUsersCopy),
transaction.getRepository(UserContact).insert(dbUserContactsCopy),
transaction.getRepository(UserRole).insert(dbUserRolesCopy)
])
})
return dbUsers
}
export async function createUser(user: UserInterface, homeCommunity?: Community | null, store: boolean = true): Promise<User> {
let dbUser = new User()
dbUser.firstName = user.firstName ?? ''
dbUser.lastName = user.lastName ?? ''
@ -21,25 +82,50 @@ export const userFactory = async (user: UserInterface): Promise<User> => {
dbUser.createdAt = user.createdAt ?? new Date()
dbUser.deletedAt = user.deletedAt ?? null
dbUser.publisherId = user.publisherId ?? 0
dbUser.humhubAllowed = true
dbUser.gradidoID = v4()
if (user.emailChecked) {
dbUserContact.emailVerificationCode = random(64).toString()
dbUserContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
dbUserContact.emailChecked = true
dbUser.password = random(64)
// dbUser.password =
dbUser.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
}
const homeCommunity = await getHomeCommunity()
if (!homeCommunity) {
homeCommunity = await getHomeCommunity()
}
if (homeCommunity) {
dbUser.community = homeCommunity
dbUser.communityUuid = homeCommunity.communityUuid!
}
// TODO: improve with cascade
dbUser = await dbUser.save()
dbUserContact.userId = dbUser.id
dbUserContact = await dbUserContact.save()
dbUser.emailId = dbUserContact.id
dbUser.emailContact = dbUserContact
return dbUser.save()
return store ? dbUser.save() : dbUser
}
export async function createUserContact(user: UserInterface, userId?: number, store: boolean = true): Promise<UserContact> {
let dbUserContact = new UserContact()
dbUserContact.email = user.email ?? ''
dbUserContact.type = UserContactType.USER_CONTACT_EMAIL
if (user.createdAt) {
dbUserContact.createdAt = user.createdAt
dbUserContact.updatedAt = user.createdAt
}
if (user.emailChecked) {
dbUserContact.emailVerificationCode = random(64).toString()
dbUserContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
dbUserContact.emailChecked = true
}
if (userId) {
dbUserContact.userId = userId
}
return store ? dbUserContact.save() : dbUserContact
}
export async function createUserRole(userId: number, role: RoleNames, store: boolean = true): Promise<UserRole> {
let dbUserRole = new UserRole()
dbUserRole.userId = userId
dbUserRole.role = role
return store ? dbUserRole.save() : dbUserRole
}

View File

@ -0,0 +1,5 @@
export * from './contributionLink'
export * from './creation'
export * from './factory'
export * from './transactionLink'
export * from './users'

View File

@ -0,0 +1,10 @@
export interface TransactionLinkInterface {
email: string
amount: number
memo: string
createdAt?: Date
// TODO: for testing
// redeemedAt?: Date
// redeemedBy?: number
deletedAt?: boolean
}

View File

@ -0,0 +1,56 @@
import { TransactionLinkInterface } from './TransactionLinkInterface'
export type { TransactionLinkInterface }
export const transactionLinks: TransactionLinkInterface[] = [
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: 'Leider wollte niemand meine Gradidos haben :(',
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
// TODO: for testing
// memo: `Yeah, eingelöst!`,
// redeemedAt: new Date(2022, 2, 2),
// redeemedBy: not null,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: 'Da habe ich mich wohl etwas übernommen.',
deletedAt: true,
},
]

View File

@ -9,4 +9,6 @@ export const bibiBloxberg: UserInterface = {
emailChecked: true,
language: 'de',
publisherId: 1234,
// move user createdAt before transaction link
createdAt: new Date(2021, 9, 17),
}

View File

@ -4,8 +4,19 @@ import { garrickOllivander } from './garrick-ollivander'
import { peterLustig } from './peter-lustig'
import { raeuberHotzenplotz } from './raeuber-hotzenplotz'
import { stephenHawking } from './stephen-hawking'
import { UserInterface } from './UserInterface'
export const users = [
export {
type UserInterface,
bibiBloxberg,
bobBaumeister,
garrickOllivander,
peterLustig,
raeuberHotzenplotz,
stephenHawking
}
export const users: UserInterface[] = [
peterLustig,
bibiBloxberg,
bobBaumeister,

View File

@ -1,4 +1,5 @@
import { UserInterface } from './UserInterface'
import { RoleNames } from '../../enum'
export const peterLustig: UserInterface = {
email: 'peter@lustig.de',
@ -7,5 +8,6 @@ export const peterLustig: UserInterface = {
// description: 'Latzhose und Nickelbrille',
createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true,
language: 'de'
language: 'de',
role: RoleNames.ADMIN,
}

View File

@ -50,7 +50,11 @@
//"@/*": ["src/*"], /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
//},
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */
"typeRoots": [
"./node_modules/@types",
"../node_modules/@types",
"../@types"
],
// "types": ["node"], /* 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'. */

View File

@ -1,6 +1,6 @@
{
"name": "dht-node",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido dht-node module",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/",

View File

@ -52,9 +52,9 @@
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [ /* List of folders to include type definitions from. */
"src/dht_node/@types",
"node_modules/@types",
"../node_modules/@types"
"../node_modules/@types",
"../@types"
],
// "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'. */

View File

@ -1,6 +1,6 @@
{
"name": "federation",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/federation",

View File

@ -64,6 +64,7 @@
"typeRoots": [ /* List of folders to include type definitions from. */
"node_modules/@types",
"../node_modules/@types",
"../@types"
],
// "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. */
@ -87,6 +88,8 @@
},
"include": [
"../core/src/types",
"src",
"test"
],
"ts-node": {
"swc": true

View File

@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "2.7.1",
"version": "2.7.2",
"private": true,
"scripts": {
"dev": "concurrently \"yarn watch-scss\" \"vite\"",

View File

@ -1,15 +1,28 @@
<template>
<transition name="fade-out" @after-leave="resetForm">
<div>
<div v-if="showForm">
<contribution-form
v-if="maxForMonths"
:model-value="form"
:max-gdd-last-month="parseFloat(maxForMonths.openCreations[1].amount)"
:max-gdd-this-month="parseFloat(maxForMonths.openCreations[2].amount)"
:max-gdd-last-month="maxGddLastMonth"
:max-gdd-this-month="maxGddThisMonth"
:success-message="$t('contribution.submitted')"
@upsert-contribution="handleCreateContribution"
/>
</div>
</transition>
<div v-else>
<open-creations-amount
:minimal-date="minimalDate"
:max-gdd-last-month="maxGddLastMonth"
:max-gdd-this-month="maxGddThisMonth"
/>
<div class="mb-3"></div>
<success-message
:message="$t('contribution.submitted')"
@on-back="showForm = true"
></success-message>
</div>
</div>
</template>
<script setup>
@ -18,11 +31,12 @@ import { GDD_PER_HOUR } from '@/constants'
import { useQuery, useMutation } from '@vue/apollo-composable'
import { openCreationsAmounts, createContribution } from '@/graphql/contributions.graphql'
import { useAppToast } from '@/composables/useToast'
import { useI18n } from 'vue-i18n'
import { ref } from 'vue'
import { computed, ref } from 'vue'
import SuccessMessage from '@/components/SuccessMessage.vue'
import OpenCreationsAmount from '@/components/Contributions/OpenCreationsAmount.vue'
import { useMinimalContributionDate } from '@/composables/useMinimalContributionDate'
const { toastError, toastSuccess } = useAppToast()
const { t } = useI18n()
const { toastError } = useAppToast()
const { result: maxForMonths, refetch } = useQuery(
openCreationsAmounts,
@ -34,6 +48,10 @@ const { mutate: createContributionMutation } = useMutation(createContribution)
const form = ref(emptyForm())
const showForm = ref(true)
const maxGddLastMonth = computed(() => parseFloat(maxForMonths.value?.openCreations[1].amount))
const maxGddThisMonth = computed(() => parseFloat(maxForMonths.value?.openCreations[2].amount))
const minimalDate = computed(() => useMinimalContributionDate(new Date()))
function emptyForm() {
return {
contributionDate: undefined,
@ -45,18 +63,14 @@ function emptyForm() {
async function handleCreateContribution(contribution) {
try {
form.value = emptyForm()
await createContributionMutation({ ...contribution })
toastSuccess(t('contribution.submitted'))
await refetch()
showForm.value = false
} catch (err) {
toastError(err.message)
}
}
function resetForm() {
refetch()
showForm.value = true
}
</script>
<style scoped>
.fade-out-enter-active,

View File

@ -95,6 +95,7 @@ import LabeledInput from '@/components/Inputs/LabeledInput'
import OpenCreationsAmount from './OpenCreationsAmount.vue'
import { object, date as dateSchema, number, string } from 'yup'
import { GDD_PER_HOUR } from '../../constants'
import { useMinimalContributionDate } from '@/composables/useMinimalContributionDate'
const amountToHours = (amount) => parseFloat(amount / GDD_PER_HOUR).toFixed(2)
const hoursToAmount = (hours) => parseFloat(hours * GDD_PER_HOUR).toFixed(2)
@ -103,6 +104,7 @@ const props = defineProps({
modelValue: { type: Object, required: true },
maxGddLastMonth: { type: Number, required: true },
maxGddThisMonth: { type: Number, required: true },
successMessage: { type: String, required: true },
})
const emit = defineEmits(['upsert-contribution', 'abort'])
@ -125,6 +127,7 @@ const form = reactive({ ...entityDataToForm.value })
const now = ref(new Date()) // checked every minute, updated if day, month or year changed
const disableSmartValidState = ref(false)
const minimalDate = computed(() => useMinimalContributionDate(now.value))
const isThisMonth = computed(() => {
const formContributionDate = new Date(form.contributionDate)
return (
@ -133,12 +136,6 @@ const isThisMonth = computed(() => {
)
})
const minimalDate = computed(() => {
const minimalDate = new Date(now.value)
minimalDate.setMonth(now.value.getMonth() - 1, 1)
return minimalDate
})
// reactive validation schema, because some boundaries depend on form input and existing data
const validationSchema = computed(() => {
const maxAmounts = Number(

View File

@ -1,9 +1,9 @@
<template>
<div class="bg-white app-box-shadow gradido-border-radius p-3">
<div class="p-4" data-test="send-transaction-success-text">
<div class="p-4" data-test="success-message">
{{ $t('form.thx') }}
<hr />
{{ $t('form.send_transaction_success') }}
{{ message }}
</div>
<div class="text-center mt-5">
<BButton variant="primary" @click="$emit('on-back')">
@ -12,8 +12,15 @@
</div>
</div>
</template>
<script>
export default {
name: 'TransactionResultSendSuccess',
}
<script setup>
import { defineProps, defineEmits } from 'vue'
defineProps({
message: {
type: String,
default: '',
},
})
defineEmits(['on-back'])
</script>

View File

@ -0,0 +1,5 @@
export function useMinimalContributionDate(now) {
const minimalDate = new Date(now)
minimalDate.setMonth(now.getMonth() - 1, 1)
return minimalDate
}

View File

@ -28,7 +28,10 @@
></transaction-confirmation-link>
</template>
<template #transactionResultSendSuccess>
<transaction-result-send-success @on-back="onBack"></transaction-result-send-success>
<success-message
:message="$t('form.send_transaction_success')"
@on-back="onBack"
></success-message>
</template>
<template #transactionResultSendError>
<transaction-result-send-error
@ -58,7 +61,7 @@ import GddSend, { TRANSACTION_STEPS } from '@/components/GddSend'
import TransactionForm from '@/components/GddSend/TransactionForm'
import TransactionConfirmationSend from '@/components/GddSend/TransactionConfirmationSend'
import TransactionConfirmationLink from '@/components/GddSend/TransactionConfirmationLink'
import TransactionResultSendSuccess from '@/components/GddSend/TransactionResultSendSuccess'
import SuccessMessage from '@/components/SuccessMessage'
import TransactionResultSendError from '@/components/GddSend/TransactionResultSendError'
import TransactionResultLink from '@/components/GddSend/TransactionResultLink'
import { sendCoins, createTransactionLink } from '@/graphql/mutations.js'

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",
@ -20,7 +20,7 @@
"shared"
],
"scripts": {
"release": "bumpp --no-commit --no-push -r",
"release": "bumpp -r",
"version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md",
"installAll": "bun run install",
"docker": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml up",

View File

@ -1,6 +1,6 @@
{
"name": "shared",
"version": "2.7.1",
"version": "2.7.2",
"description": "Gradido Shared Code, Low-Level Shared Code, without dependencies on other modules",
"main": "./build/index.js",
"types": "./src/index.ts",

View File

@ -3,6 +3,7 @@ export * from './enum'
export * from './const'
export * from './helper'
export * from './logic/decay'
export * from './util'
export * from './jwt/JWT'
export * from './jwt/payloadtypes/AuthenticationJwtPayloadType'
export * from './jwt/payloadtypes/AuthenticationResponseJwtPayloadType'

1
shared/src/util/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './utilities'

View File

@ -0,0 +1,2 @@
export const fullName = (firstName: string, lastName: string): string =>
[firstName, lastName].filter(Boolean).join(' ')