Merge branch 'master' into 2274-feature-concept-manuel-user-registration-for-admins

This commit is contained in:
clauspeterhuebner 2022-10-21 14:00:03 +02:00 committed by GitHub
commit a78f6ab8b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 198 additions and 13 deletions

View File

@ -4,8 +4,16 @@ 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). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.13.1](https://github.com/gradido/gradido/compare/1.13.0...1.13.1)
- Fix: correctly evaluate to EMAIL_TEST_MODE to false [`#2273`](https://github.com/gradido/gradido/pull/2273)
- Refactor: Contribution resolver logs and events [`#2231`](https://github.com/gradido/gradido/pull/2231)
#### [1.13.0](https://github.com/gradido/gradido/compare/1.12.1...1.13.0) #### [1.13.0](https://github.com/gradido/gradido/compare/1.12.1...1.13.0)
> 18 October 2022
- release: Version 1.13.0 [`#2269`](https://github.com/gradido/gradido/pull/2269)
- fix: Linked User Email in Transaction List [`#2268`](https://github.com/gradido/gradido/pull/2268) - fix: Linked User Email in Transaction List [`#2268`](https://github.com/gradido/gradido/pull/2268)
- concept capturing alias [`#2148`](https://github.com/gradido/gradido/pull/2148) - concept capturing alias [`#2148`](https://github.com/gradido/gradido/pull/2148)
- fix: 🍰 Daily Redeem Of Contribution Link [`#2265`](https://github.com/gradido/gradido/pull/2265) - fix: 🍰 Daily Redeem Of Contribution Link [`#2265`](https://github.com/gradido/gradido/pull/2265)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido", "description": "Administraion Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Moriz Wahl", "author": "Moriz Wahl",
"version": "1.13.0", "version": "1.13.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": false, "private": false,
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-backend", "name": "gradido-backend",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",

View File

@ -10,7 +10,7 @@ Decimal.set({
}) })
const constants = { const constants = {
DB_VERSION: '0050-add_messageId_to_event_protocol', DB_VERSION: '0051-add_delete_by_to_contributions',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
@ -67,7 +67,7 @@ const loginServer = {
const email = { const email = {
EMAIL: process.env.EMAIL === 'true' || false, EMAIL: process.env.EMAIL === 'true' || false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || 'false', EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net', EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email', EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email',
EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net',

View File

@ -17,6 +17,7 @@ import {
setUserRole, setUserRole,
deleteUser, deleteUser,
unDeleteUser, unDeleteUser,
createContribution,
adminCreateContribution, adminCreateContribution,
adminCreateContributions, adminCreateContributions,
adminUpdateContribution, adminUpdateContribution,
@ -77,6 +78,7 @@ afterAll(async () => {
let admin: User let admin: User
let user: User let user: User
let creation: Contribution | void let creation: Contribution | void
let result: any
describe('AdminResolver', () => { describe('AdminResolver', () => {
describe('set user role', () => { describe('set user role', () => {
@ -1365,6 +1367,38 @@ describe('AdminResolver', () => {
}) })
}) })
describe('admin deletes own user contribution', () => {
beforeAll(async () => {
await query({
query: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
result = await mutate({
mutation: createContribution,
variables: {
amount: 100.0,
memo: 'Test env contribution',
creationDate: new Date().toString(),
},
})
})
it('throws an error', async () => {
await expect(
mutate({
mutation: adminDeleteContribution,
variables: {
id: result.data.createContribution.id,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Own contribution can not be deleted as admin')],
}),
)
})
})
describe('creation id does exist', () => { describe('creation id does exist', () => {
it('returns true', async () => { it('returns true', async () => {
await expect( await expect(

View File

@ -400,13 +400,24 @@ export class AdminResolver {
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise<boolean> { async adminDeleteContribution(
@Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise<boolean> {
const contribution = await DbContribution.findOne(id) const contribution = await DbContribution.findOne(id)
if (!contribution) { if (!contribution) {
logger.error(`Contribution not found for given id: ${id}`) logger.error(`Contribution not found for given id: ${id}`)
throw new Error('Contribution not found for given id.') throw new Error('Contribution not found for given id.')
} }
const moderator = getUser(context)
if (
contribution.contributionType === ContributionType.USER &&
contribution.userId === moderator.id
) {
throw new Error('Own contribution can not be deleted as admin')
}
contribution.contributionStatus = ContributionStatus.DELETED contribution.contributionStatus = ContributionStatus.DELETED
contribution.deletedBy = moderator.id
await contribution.save() await contribution.save()
const res = await contribution.softRemove() const res = await contribution.softRemove()
return !!res return !!res

View File

@ -91,6 +91,7 @@ export class ContributionResolver {
} }
contribution.contributionStatus = ContributionStatus.DELETED contribution.contributionStatus = ContributionStatus.DELETED
contribution.deletedBy = user.id
contribution.deletedAt = new Date() contribution.deletedAt = new Date()
await contribution.save() await contribution.save()
@ -127,6 +128,7 @@ export class ContributionResolver {
.from(dbContribution, 'c') .from(dbContribution, 'c')
.leftJoinAndSelect('c.messages', 'm') .leftJoinAndSelect('c.messages', 'm')
.where(where) .where(where)
.withDeleted()
.orderBy('c.createdAt', order) .orderBy('c.createdAt', order)
.limit(pageSize) .limit(pageSize)
.offset((currentPage - 1) * pageSize) .offset((currentPage - 1) * pageSize)

View File

@ -26,7 +26,7 @@ describe('sendAddedContributionMessageEmail', () => {
it('calls sendEMail', () => { it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({ expect(sendEMail).toBeCalledWith({
to: `Bibi Bloxberg <bibi@bloxberg.de>`, to: `Bibi Bloxberg <bibi@bloxberg.de>`,
subject: 'Gradido Frage zur Schöpfung', subject: 'Rückfrage zu Deinem Gemeinwohl-Beitrag',
text: text:
expect.stringContaining('Hallo Bibi Bloxberg') && expect.stringContaining('Hallo Bibi Bloxberg') &&
expect.stringContaining('Peter Lustig') && expect.stringContaining('Peter Lustig') &&

View File

@ -29,6 +29,7 @@ describe('sendEMail', () => {
let result: boolean let result: boolean
describe('config email is false', () => { describe('config email is false', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks()
result = await sendEMail({ result = await sendEMail({
to: 'receiver@mail.org', to: 'receiver@mail.org',
cc: 'support@gradido.net', cc: 'support@gradido.net',
@ -48,6 +49,7 @@ describe('sendEMail', () => {
describe('config email is true', () => { describe('config email is true', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks()
CONFIG.EMAIL = true CONFIG.EMAIL = true
result = await sendEMail({ result = await sendEMail({
to: 'receiver@mail.org', to: 'receiver@mail.org',
@ -73,7 +75,7 @@ describe('sendEMail', () => {
it('calls sendMail of transporter', () => { it('calls sendMail of transporter', () => {
expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({ expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${CONFIG.EMAIL_TEST_RECEIVER}`, to: 'receiver@mail.org',
cc: 'support@gradido.net', cc: 'support@gradido.net',
subject: 'Subject', subject: 'Subject',
text: 'Text text text', text: 'Text text text',
@ -84,4 +86,28 @@ describe('sendEMail', () => {
expect(result).toBeTruthy() expect(result).toBeTruthy()
}) })
}) })
describe('with email EMAIL_TEST_MODUS true', () => {
beforeEach(async () => {
jest.clearAllMocks()
CONFIG.EMAIL = true
CONFIG.EMAIL_TEST_MODUS = true
result = await sendEMail({
to: 'receiver@mail.org',
cc: 'support@gradido.net',
subject: 'Subject',
text: 'Text text text',
})
})
it('calls sendMail of transporter with faked to', () => {
expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: CONFIG.EMAIL_TEST_RECEIVER,
cc: 'support@gradido.net',
subject: 'Subject',
text: 'Text text text',
})
})
})
}) })

View File

@ -1,6 +1,6 @@
export const contributionMessageReceived = { export const contributionMessageReceived = {
de: { de: {
subject: 'Gradido Frage zur Schöpfung', subject: 'Rückfrage zu Deinem Gemeinwohl-Beitrag',
text: (data: { text: (data: {
senderFirstName: string senderFirstName: string
senderLastName: string senderLastName: string

View File

@ -0,0 +1,92 @@
import Decimal from 'decimal.js-light'
import {
BaseEntity,
Column,
Entity,
PrimaryGeneratedColumn,
DeleteDateColumn,
JoinColumn,
ManyToOne,
OneToMany,
} from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
import { User } from '../User'
import { ContributionMessage } from '../ContributionMessage'
@Entity('contributions')
export class Contribution extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ unsigned: true, nullable: false, name: 'user_id' })
userId: number
@ManyToOne(() => User, (user) => user.contributions)
@JoinColumn({ name: 'user_id' })
user: User
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
createdAt: Date
@Column({ type: 'datetime', nullable: false, name: 'contribution_date' })
contributionDate: Date
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: false,
transformer: DecimalTransformer,
})
amount: Decimal
@Column({ unsigned: true, nullable: true, name: 'moderator_id' })
moderatorId: number
@Column({ unsigned: true, nullable: true, name: 'contribution_link_id' })
contributionLinkId: number
@Column({ unsigned: true, nullable: true, name: 'confirmed_by' })
confirmedBy: number
@Column({ nullable: true, name: 'confirmed_at' })
confirmedAt: Date
@Column({ unsigned: true, nullable: true, name: 'denied_by' })
deniedBy: number
@Column({ nullable: true, name: 'denied_at' })
deniedAt: Date
@Column({
name: 'contribution_type',
length: 12,
nullable: false,
collation: 'utf8mb4_unicode_ci',
})
contributionType: string
@Column({
name: 'contribution_status',
length: 12,
nullable: false,
collation: 'utf8mb4_unicode_ci',
})
contributionStatus: string
@Column({ unsigned: true, nullable: true, name: 'transaction_id' })
transactionId: number
@DeleteDateColumn({ name: 'deleted_at' })
deletedAt: Date | null
@DeleteDateColumn({ unsigned: true, nullable: true, name: 'deleted_by' })
deletedBy: number
@OneToMany(() => ContributionMessage, (message) => message.contribution)
@JoinColumn({ name: 'contribution_id' })
messages?: ContributionMessage[]
}

View File

@ -1 +1 @@
export { Contribution } from './0047-messages_tables/Contribution' export { Contribution } from './0051-add_delete_by_to_contributions/Contribution'

View File

@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
`ALTER TABLE \`contributions\` ADD COLUMN \`deleted_by\` int(10) unsigned DEFAULT NULL;`,
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`ALTER TABLE \`contributions\` DROP COLUMN \`deleted_by\`;`)
}

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-database", "name": "gradido-database",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido Database Tool to execute database migrations", "description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database", "repository": "https://github.com/gradido/gradido/database",

View File

@ -1,6 +1,6 @@
{ {
"name": "bootstrap-vue-gradido-wallet", "name": "bootstrap-vue-gradido-wallet",
"version": "1.13.0", "version": "1.13.1",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node run/server.js", "start": "node run/server.js",

View File

@ -34,7 +34,7 @@
"contribution": { "contribution": {
"activity": "Tätigkeit", "activity": "Tätigkeit",
"alert": { "alert": {
"answerQuestion": "Bitte beantworte die Nachfrage", "answerQuestion": "Bitte beantworte die Rückfrage!",
"communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.", "communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.",
"confirm": "bestätigt", "confirm": "bestätigt",
"in_progress": "Es gibt eine Rückfrage der Moderatoren.", "in_progress": "Es gibt eine Rückfrage der Moderatoren.",

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido", "name": "gradido",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido", "description": "Gradido",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:gradido/gradido.git", "repository": "git@github.com:gradido/gradido.git",