diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4cc28bf20..ac60cfdf2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -437,7 +437,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 89 + min_coverage: 91 token: ${{ github.token }} ############################################################################## diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 4f144f1e9..6515f9e3b 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -35,6 +35,7 @@ export enum RIGHTS { SEARCH_ADMIN_USERS = 'SEARCH_ADMIN_USERS', CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE', LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES', + OPEN_CREATIONS = 'OPEN_CREATIONS', // Admin SEARCH_USERS = 'SEARCH_USERS', SET_USER_ROLE = 'SET_USER_ROLE', diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index eabaf8e99..2f3b4e081 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -33,6 +33,7 @@ export const ROLE_USER = new Role('user', [ RIGHTS.COMMUNITY_STATISTICS, RIGHTS.CREATE_CONTRIBUTION_MESSAGE, RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES, + RIGHTS.OPEN_CREATIONS, ]) export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights diff --git a/backend/src/graphql/model/OpenCreation.ts b/backend/src/graphql/model/OpenCreation.ts new file mode 100644 index 000000000..9ef08fd4a --- /dev/null +++ b/backend/src/graphql/model/OpenCreation.ts @@ -0,0 +1,14 @@ +import { ObjectType, Field, Int } from 'type-graphql' +import Decimal from 'decimal.js-light' + +@ObjectType() +export class OpenCreation { + @Field(() => Int) + month: number + + @Field(() => Int) + year: number + + @Field(() => Decimal) + amount: Decimal +} diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 6e353f6a7..b4fdcae4f 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -1,13 +1,11 @@ import { ObjectType, Field } from 'type-graphql' import { KlickTipp } from './KlickTipp' import { User as dbUser } from '@entity/User' -import Decimal from 'decimal.js-light' -import { FULL_CREATION_AVAILABLE } from '../resolver/const/const' import { UserContact } from './UserContact' @ObjectType() export class User { - constructor(user: dbUser, creation: Decimal[] = FULL_CREATION_AVAILABLE) { + constructor(user: dbUser) { this.id = user.id this.gradidoID = user.gradidoID this.alias = user.alias @@ -26,7 +24,6 @@ export class User { this.isAdmin = user.isAdmin this.klickTipp = null this.hasElopage = null - this.creation = creation this.hideAmountGDD = user.hideAmountGDD this.hideAmountGDT = user.hideAmountGDT } @@ -34,9 +31,6 @@ export class User { @Field(() => Number) id: number - // `public_key` binary(32) DEFAULT NULL, - // `privkey` binary(80) DEFAULT NULL, - @Field(() => String) gradidoID: string @@ -62,9 +56,6 @@ export class User { @Field(() => Date, { nullable: true }) deletedAt: Date | null - // `password` bigint(20) unsigned DEFAULT 0, - // `email_hash` binary(32) DEFAULT NULL, - @Field(() => Date) createdAt: Date @@ -84,8 +75,6 @@ export class User { @Field(() => Number, { nullable: true }) publisherId: number | null - // `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, - @Field(() => Date, { nullable: true }) isAdmin: Date | null @@ -94,7 +83,4 @@ export class User { @Field(() => Boolean, { nullable: true }) hasElopage: boolean | null - - @Field(() => [Decimal]) - creation: Decimal[] } diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 3794546e2..7771c62ca 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -11,8 +11,9 @@ import { Transaction as DbTransaction } from '@entity/Transaction' import { AdminCreateContributions } from '@model/AdminCreateContributions' import { AdminUpdateContribution } from '@model/AdminUpdateContribution' import { Contribution, ContributionListResult } from '@model/Contribution' -import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import { Decay } from '@model/Decay' +import { OpenCreation } from '@model/OpenCreation' +import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import { TransactionTypeId } from '@enum/TransactionTypeId' import { Order } from '@enum/Order' import { ContributionType } from '@enum/ContributionType' @@ -27,6 +28,7 @@ import { RIGHTS } from '@/auth/RIGHTS' import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { backendLogger as logger } from '@/server/logger' import { + getCreationDates, getUserCreation, getUserCreations, validateContribution, @@ -691,4 +693,23 @@ export class ContributionResolver { ) // return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) } + + @Authorized([RIGHTS.OPEN_CREATIONS]) + @Query(() => [OpenCreation]) + async openCreations( + @Arg('userId', () => Int, { nullable: true }) userId: number | null, + @Ctx() context: Context, + ): Promise { + const id = userId || getUser(context).id + const clientTimezoneOffset = getClientTimezoneOffset(context) + const creationDates = getCreationDates(clientTimezoneOffset) + const creations = await getUserCreation(id, clientTimezoneOffset) + return creationDates.map((date, index) => { + return { + month: date.getMonth(), + year: date.getFullYear(), + amount: creations[index], + } + }) + } } diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index ed1bf3f47..c630c240a 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -58,7 +58,7 @@ import { EventSendConfirmationEmail, EventActivateAccount, } from '@/event/Event' -import { getUserCreation, getUserCreations } from './util/creations' +import { getUserCreations } from './util/creations' import { isValidPassword } from '@/password/EncryptorUtils' import { FULL_CREATION_AVAILABLE } from './const/const' import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor' @@ -114,9 +114,8 @@ export class UserResolver { async verifyLogin(@Ctx() context: Context): Promise { logger.info('verifyLogin...') // TODO refactor and do not have duplicate code with login(see below) - const clientTimezoneOffset = getClientTimezoneOffset(context) const userEntity = getUser(context) - const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset)) + const user = new User(userEntity) // Elopage Status & Stored PublisherId user.hasElopage = await this.hasElopage(context) @@ -132,7 +131,6 @@ export class UserResolver { @Ctx() context: Context, ): Promise { logger.info(`login with ${email}, ***, ${publisherId} ...`) - const clientTimezoneOffset = getClientTimezoneOffset(context) email = email.trim().toLowerCase() const dbUser = await findUserByEmail(email) if (dbUser.deletedAt) { @@ -163,7 +161,7 @@ export class UserResolver { logger.addContext('user', dbUser.id) logger.debug('validation of login credentials successful...') - const user = new User(dbUser, await getUserCreation(dbUser.id, clientTimezoneOffset)) + const user = new User(dbUser) logger.debug(`user= ${JSON.stringify(user, null, 2)}`) i18n.setLocale(user.language) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 54286d2aa..00137eaa1 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -101,15 +101,19 @@ export const getUserCreation = async ( } const getCreationMonths = (timezoneOffset: number): number[] => { + return getCreationDates(timezoneOffset).map((date) => date.getMonth() + 1) +} + +export const getCreationDates = (timezoneOffset: number): Date[] => { const clientNow = new Date() clientNow.setTime(clientNow.getTime() - timezoneOffset * 60 * 1000) logger.info( `getCreationMonths -- offset: ${timezoneOffset} -- clientNow: ${clientNow.toISOString()}`, ) return [ - new Date(clientNow.getFullYear(), clientNow.getMonth() - 2, 1).getMonth() + 1, - new Date(clientNow.getFullYear(), clientNow.getMonth() - 1, 1).getMonth() + 1, - clientNow.getMonth() + 1, + new Date(clientNow.getFullYear(), clientNow.getMonth() - 2, 1), + new Date(clientNow.getFullYear(), clientNow.getMonth() - 1, 1), + clientNow, ] } diff --git a/deployment/bare_metal/setup.md b/deployment/bare_metal/setup.md index 5892cf4fc..627aa4a2d 100644 --- a/deployment/bare_metal/setup.md +++ b/deployment/bare_metal/setup.md @@ -231,3 +231,32 @@ This opens the `crontab` in edit-mode and insert the following entry: ```bash 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null ``` + +## Define Cronjob To start backup script automatically + +At least at production stage we need a daily backup of our database. This can be done by adding a cronjob +to start the existing backup.sh script. + +### On production / stage3 / stage2 + +To check for existing cronjobs for the `gradido` user, please + +Run: + +```bash +crontab -l +``` + +This show all existing entries of the crontab for user `gradido` + +To install/add the cronjob for a daily backup at 3:00am please + +Run: + +```bash +crontab -e +``` +and insert the following line +```bash +0 3 * * * ~/gradido/deployment/bare_metal/backup.sh +``` diff --git a/frontend/src/components/GddSend/TransactionForm.vue b/frontend/src/components/GddSend/TransactionForm.vue index 607a20afc..c54b4c0f9 100644 --- a/frontend/src/components/GddSend/TransactionForm.vue +++ b/frontend/src/components/GddSend/TransactionForm.vue @@ -1,112 +1,115 @@