diff --git a/admin/scripts/sort.sh b/admin/scripts/sort.sh index e5c5c41c6..d24307d7c 100755 --- a/admin/scripts/sort.sh +++ b/admin/scripts/sort.sh @@ -2,24 +2,23 @@ ROOT_DIR=$(dirname "$0")/.. -tmp=$(mktemp) exit_code=0 for locale_file in $ROOT_DIR/src/locales/*.json do - jq -f $(dirname "$0")/sort_filter.jq $locale_file > "$tmp" + jq -M 'to_entries | sort_by(.key) | from_entries' "$locale_file" > tmp.json + if [ "$*" == "--fix" ] then - mv "$tmp" $locale_file + mv tmp.json "$locale_file" else - if diff -q "$tmp" $locale_file > /dev/null ; + if ! diff -q tmp.json "$locale_file" > /dev/null ; then - : # all good - else - exit_code=$? - echo "$(basename -- $locale_file) is not sorted by keys" + exit_code=1 + echo "$(basename -- "$locale_file") is not sorted by keys" fi fi done +rm -f tmp.json exit $exit_code diff --git a/admin/src/components/ContributionMessages/ContributionMessagesFormular.spec.js b/admin/src/components/ContributionMessages/ContributionMessagesFormular.spec.js index b7f01f8b8..f19459ce9 100644 --- a/admin/src/components/ContributionMessages/ContributionMessagesFormular.spec.js +++ b/admin/src/components/ContributionMessages/ContributionMessagesFormular.spec.js @@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils' import ContributionMessagesFormular from './ContributionMessagesFormular' import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup' import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage' +import { adminUpdateContribution } from '@/graphql/adminUpdateContribution' const localVue = global.localVue @@ -12,6 +13,7 @@ describe('ContributionMessagesFormular', () => { const propsData = { contributionId: 42, + contributionMemo: 'It is a test memo', } const mocks = { @@ -52,9 +54,10 @@ describe('ContributionMessagesFormular', () => { await wrapper.find('form').trigger('reset') }) - it('form has empty text', () => { + it('form has empty text and memo reset to contribution memo input', () => { expect(wrapper.vm.form).toEqual({ text: '', + memo: 'It is a test memo', }) }) }) @@ -134,6 +137,32 @@ describe('ContributionMessagesFormular', () => { }) }) + describe('update contribution memo from moderator for user created contributions', () => { + beforeEach(async () => { + await wrapper.setData({ + form: { + memo: 'changed memo', + }, + chatOrMemo: 1, + }) + await wrapper.find('button[data-test="submit-dialog"]').trigger('click') + }) + + it('adminUpdateContribution was called with contributionId and updated memo', () => { + expect(apolloMutateMock).toBeCalledWith({ + mutation: adminUpdateContribution, + variables: { + id: 42, + memo: 'changed memo', + }, + }) + }) + + it('toasts an success message', () => { + expect(toastSuccessSpy).toBeCalledWith('message.request') + }) + }) + describe('send contribution message with error', () => { beforeEach(async () => { apolloMutateMock.mockRejectedValue({ message: 'OUCH!' }) diff --git a/admin/src/components/ContributionMessages/ContributionMessagesFormular.vue b/admin/src/components/ContributionMessages/ContributionMessagesFormular.vue index 1286104a4..1e395c183 100644 --- a/admin/src/components/ContributionMessages/ContributionMessagesFormular.vue +++ b/admin/src/components/ContributionMessages/ContributionMessagesFormular.vue @@ -2,12 +2,24 @@
- + + + + + + + + {{ $t('form.cancel') }} @@ -17,7 +29,16 @@ type="button" variant="warning" class="text-black" - :disabled="disabled" + @click.prevent="enableMemo()" + data-test="submit-memo" + > + {{ $t('moderator.memo-modify') }} + + @@ -43,6 +64,7 @@ diff --git a/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.vue b/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.vue index 4492fa88f..5abbb0a33 100644 --- a/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.vue +++ b/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.vue @@ -1,5 +1,5 @@ + @@ -164,6 +173,9 @@ export default { updateStatus(id) { this.$emit('update-status', id) }, + reloadContribution(id) { + this.$emit('reload-contribution', id) + }, }, } diff --git a/admin/src/graphql/adminListContributions.js b/admin/src/graphql/adminListContributions.js index 9d814b95d..e11ebfa05 100644 --- a/admin/src/graphql/adminListContributions.js +++ b/admin/src/graphql/adminListContributions.js @@ -30,6 +30,8 @@ export const adminListContributions = gql` contributionDate confirmedAt confirmedBy + updatedAt + updatedBy status messagesCount deniedAt diff --git a/admin/src/graphql/adminUpdateContribution.js b/admin/src/graphql/adminUpdateContribution.js index c52a0cbc4..db2f09072 100644 --- a/admin/src/graphql/adminUpdateContribution.js +++ b/admin/src/graphql/adminUpdateContribution.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const adminUpdateContribution = gql` - mutation ($id: Int!, $amount: Decimal!, $memo: String!, $creationDate: String!) { + mutation ($id: Int!, $amount: Decimal, $memo: String, $creationDate: String) { adminUpdateContribution(id: $id, amount: $amount, memo: $memo, creationDate: $creationDate) { amount date diff --git a/admin/src/graphql/getContribution.js b/admin/src/graphql/getContribution.js new file mode 100644 index 000000000..34e260299 --- /dev/null +++ b/admin/src/graphql/getContribution.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag' + +export const getContribution = gql` + query ($id: Int!) { + contribution(id: $id) { + id + firstName + lastName + amount + memo + createdAt + contributionDate + confirmedAt + confirmedBy + updatedAt + updatedBy + status + messagesCount + deniedAt + deniedBy + deletedAt + deletedBy + moderatorId + userId + } + } +` diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 33ef36053..264029cc6 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -1,4 +1,5 @@ { + "GDD": "GDD", "all_emails": "Alle Nutzer", "back": "zurück", "change_user_role": "Nutzerrolle ändern", @@ -42,6 +43,7 @@ "createdAt": "Angelegt", "creation": "Schöpfung", "creationList": "Schöpfungsliste", + "creation_for_month": "Schöpfung für Monat", "creation_form": { "creation_for": "Aktives Grundeinkommen für", "enter_text": "Text eintragen", @@ -58,16 +60,15 @@ "toasted_update": "`Offene Schöpfung {value} GDD) für {email} wurde geändert und liegt zur Bestätigung bereit", "update_creation": "Schöpfung aktualisieren" }, - "creation_for_month": "Schöpfung für Monat", "delete": "Löschen", + "delete_user": "Nutzer löschen", "deleted": "gelöscht", "deleted_user": "Alle gelöschten Nutzer", - "delete_user": "Nutzer löschen", "deny": "Ablehnen", + "e_mail": "E-Mail", "enabled": "aktiviert", "error": "Fehler", "expired": "abgelaufen", - "e_mail": "E-Mail", "federation": { "createdAt": "Erstellt am", "gradidoInstances": "Gradido Instanzen", @@ -88,7 +89,6 @@ "cancel": "Abbrechen", "submit": "Senden" }, - "GDD": "GDD", "help": { "help": "Hilfe", "transactionlist": { @@ -109,9 +109,13 @@ "request": "Die Anfrage wurde gesendet." }, "moderator": { + "chat": "Chat", "history": "Die Daten wurden geändert. Dies sind die alten Daten.", "moderator": "Moderator", "notice": "Moderator Notiz", + "memo": "Memo", + "memo-modify": "Memo bearbeiten", + "memo-modified": "Memo vom Moderator bearbeitet", "request": "Diese Nachricht ist nur für die Moderatoren sichtbar!" }, "name": "Name", @@ -123,9 +127,9 @@ "statistic": "Statistik", "user_search": "Nutzersuche" }, - "not_open_creations": "Keine offenen Schöpfungen", "no_hashtag": "#Hashtags verbergen", "no_hashtag_tooltip": "Zeigt nur Beiträge ohne Hashtag im Text", + "not_open_creations": "Keine offenen Schöpfungen", "open": "offen", "open_creations": "Offene Schöpfungen", "overlay": { @@ -196,7 +200,6 @@ "title": "Alle geschöpften Transaktionen für den Nutzer" }, "undelete_user": "Nutzer wiederherstellen", - "unregistered_emails": "Nur unregistrierte Nutzer", "unregister_mail": { "button": "Registrierungs-Email bestätigen, jetzt senden", "error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}", @@ -206,6 +209,7 @@ "text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({email}) gesendet.", "text_true": " Die Email wurde bestätigt." }, + "unregistered_emails": "Nur unregistrierte Nutzer", "userRole": { "notChangeYourSelf": "Als Admin/Moderator kannst du nicht selber deine Rolle ändern.", "selectLabel": "Rolle:", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index 6c8b36f15..dbd831bb9 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -1,4 +1,5 @@ { + "GDD": "GDD", "all_emails": "All users", "back": "back", "change_user_role": "Change user role", @@ -42,6 +43,7 @@ "createdAt": "Created at", "creation": "Creation", "creationList": "Creation list", + "creation_for_month": "Creation for month", "creation_form": { "creation_for": "Active Basic Income for", "enter_text": "Enter text", @@ -58,16 +60,15 @@ "toasted_update": "Open creation {value} GDD) for {email} has been changed and is ready for confirmation.", "update_creation": "Creation update" }, - "creation_for_month": "Creation for month", "delete": "Delete", + "delete_user": "Delete user", "deleted": "deleted", "deleted_user": "All deleted user", - "delete_user": "Delete user", "deny": "Reject", + "e_mail": "E-mail", "enabled": "enabled", "error": "Error", "expired": "expired", - "e_mail": "E-mail", "federation": { "createdAt": "Created At ", "gradidoInstances": "Gradido Instances", @@ -88,7 +89,6 @@ "cancel": "Cancel", "submit": "Send" }, - "GDD": "GDD", "help": { "help": "Help", "transactionlist": { @@ -109,9 +109,13 @@ "request": "Request has been sent." }, "moderator": { + "chat": "Chat", "history": "The data has been changed. This is the old data.", "moderator": "Moderator", "notice": "Moderator note", + "memo": "Memo", + "memo-modify": "Modify Memo", + "memo-modified": "Memo edited by moderator", "request": "This message is only visible to the moderators!" }, "name": "Name", @@ -123,9 +127,9 @@ "statistic": "Statistic", "user_search": "User search" }, - "not_open_creations": "No open creations", "no_hashtag": "Hide #hashtags", "no_hashtag_tooltip": "Shows only contributions without hashtag in text", + "not_open_creations": "No open creations", "open": "open", "open_creations": "Open creations", "overlay": { @@ -196,7 +200,6 @@ "title": "All creation-transactions for the user" }, "undelete_user": "Undelete User", - "unregistered_emails": "Only unregistered users", "unregister_mail": { "button": "Confirm registration email, send now", "error": "Error sending the confirmation link to the user: {message}", @@ -206,6 +209,7 @@ "text_false": "The last email was sent to the member ({email}) on {date}.", "text_true": "The email was confirmed." }, + "unregistered_emails": "Only unregistered users", "userRole": { "notChangeYourSelf": "As an admin/moderator, you cannot change your own role.", "selectLabel": "Role:", diff --git a/admin/src/pages/CreationConfirm.spec.js b/admin/src/pages/CreationConfirm.spec.js index 36e2479aa..4842c8b3b 100644 --- a/admin/src/pages/CreationConfirm.spec.js +++ b/admin/src/pages/CreationConfirm.spec.js @@ -4,6 +4,7 @@ import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { denyContribution } from '../graphql/denyContribution' import { adminListContributions } from '../graphql/adminListContributions' import { confirmContribution } from '../graphql/confirmContribution' +import { getContribution } from '../graphql/getContribution' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import VueApollo from 'vue-apollo' import { createMockClient } from 'mock-apollo-client' @@ -61,6 +62,8 @@ const defaultData = () => { contributionDate: new Date(), deletedBy: null, deletedAt: null, + updatedAt: null, + updatedBy: null, createdAt: new Date(), }, { @@ -83,6 +86,8 @@ const defaultData = () => { contributionDate: new Date(), deletedBy: null, deletedAt: null, + updatedAt: null, + updatedBy: null, createdAt: new Date(), }, ], @@ -96,6 +101,7 @@ describe('CreationConfirm', () => { const adminDeleteContributionMock = jest.fn() const adminDenyContributionMock = jest.fn() const confirmContributionMock = jest.fn() + const getContributionMock = jest.fn() mockClient.setRequestHandler( adminListContributions, @@ -121,6 +127,8 @@ describe('CreationConfirm', () => { confirmContributionMock.mockResolvedValue({ data: { confirmContribution: true } }), ) + mockClient.setRequestHandler(getContribution, getContributionMock.mockResolvedValue({ data: {} })) + const Wrapper = () => { return mount(CreationConfirm, { localVue, mocks, apolloProvider }) } @@ -141,7 +149,7 @@ describe('CreationConfirm', () => { }) }) - describe('server response is succes', () => { + describe('server response is success', () => { it('has a DIV element with the class.creation-confirm', () => { expect(wrapper.find('div.creation-confirm').exists()).toBeTruthy() }) @@ -219,7 +227,7 @@ describe('CreationConfirm', () => { expect(wrapper.find('#overlay').isVisible()).toBeTruthy() }) - describe('with succes', () => { + describe('with success', () => { describe('cancel confirmation', () => { beforeEach(async () => { await wrapper.find('#overlay').findAll('button').at(0).trigger('click') @@ -278,7 +286,7 @@ describe('CreationConfirm', () => { expect(wrapper.find('#overlay').isVisible()).toBeTruthy() }) - describe('with succes', () => { + describe('with success', () => { describe('cancel deny', () => { beforeEach(async () => { await wrapper.find('#overlay').findAll('button').at(0).trigger('click') @@ -510,6 +518,20 @@ describe('CreationConfirm', () => { }) }) + describe('reload contribution', () => { + beforeEach(async () => { + await wrapper + .findComponent({ name: 'OpenCreationsTable' }) + .vm.$emit('reload-contribution', 1) + }) + + it('reloaded contribution', () => { + expect(getContributionMock).toBeCalledWith({ + id: 1, + }) + }) + }) + describe('unknown variant', () => { beforeEach(async () => { await wrapper.setData({ variant: 'unknown' }) diff --git a/admin/src/pages/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue index bd4c58983..3ca382c43 100644 --- a/admin/src/pages/CreationConfirm.vue +++ b/admin/src/pages/CreationConfirm.vue @@ -3,7 +3,7 @@
@@ -49,6 +49,7 @@ :fields="fields" @show-overlay="showOverlay" @update-status="updateStatus" + @reload-contribution="reloadContribution" @update-contributions="$apollo.queries.ListAllContributions.refetch()" /> @@ -95,6 +96,7 @@ import { adminListContributions } from '../graphql/adminListContributions' import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { confirmContribution } from '../graphql/confirmContribution' import { denyContribution } from '../graphql/denyContribution' +import { getContribution } from '../graphql/getContribution' const FILTER_TAB_MAP = [ ['IN_PROGRESS', 'PENDING'], @@ -131,8 +133,21 @@ export default { }, }, methods: { - swapNoHashtag() { - this.query() + reloadContribution(id) { + this.$apollo + .query({ query: getContribution, variables: { id } }) + .then((result) => { + const contribution = result.data.contribution + this.$set( + this.items, + this.items.findIndex((obj) => obj.id === contribution.id), + contribution, + ) + }) + .catch((error) => { + this.overlay = false + this.toastError(error.message) + }) }, deleteCreation() { this.$apollo diff --git a/admin/src/pages/Overview.spec.js b/admin/src/pages/Overview.spec.js index b9ad77cdd..d5265f0e2 100644 --- a/admin/src/pages/Overview.spec.js +++ b/admin/src/pages/Overview.spec.js @@ -53,6 +53,8 @@ const defaultData = () => { contributionDate: new Date(), deletedBy: null, deletedAt: null, + updatedAt: null, + updatedBy: null, createdAt: new Date(), }, { @@ -75,6 +77,8 @@ const defaultData = () => { contributionDate: new Date(), deletedBy: null, deletedAt: null, + updatedAt: null, + updatedBy: null, createdAt: new Date(), }, ], diff --git a/backend/package.json b/backend/package.json index b25a7dedb..8324932a3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,7 +8,7 @@ "license": "Apache-2.0", "private": false, "scripts": { - "build": "tsc --build && mkdir -p build/src/emails/templates/ && cp -r src/emails/templates/* build/src/emails/templates/ && mkdir -p build/src/locales/ && cp -r src/locales/*.json build/src/locales/", + "build": "tsc --build && mkdirp build/src/emails/templates/ && ncp src/emails/templates build/src/emails/templates && mkdirp build/src/locales/ && ncp src/locales build/src/locales", "clean": "tsc --build --clean", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", "dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css --exec ts-node -r tsconfig-paths/register src/index.ts", @@ -80,7 +80,9 @@ "ts-jest": "^27.0.5", "ts-node": "^10.0.0", "tsconfig-paths": "^3.14.0", - "typescript": "^4.3.4" + "typescript": "^4.3.4", + "mkdirp": "^3.0.1", + "ncp": "^2.0.0" }, "nodemonConfig": { "ignore": [ diff --git a/backend/scripts/sort.sh b/backend/scripts/sort.sh index e5c5c41c6..d24307d7c 100755 --- a/backend/scripts/sort.sh +++ b/backend/scripts/sort.sh @@ -2,24 +2,23 @@ ROOT_DIR=$(dirname "$0")/.. -tmp=$(mktemp) exit_code=0 for locale_file in $ROOT_DIR/src/locales/*.json do - jq -f $(dirname "$0")/sort_filter.jq $locale_file > "$tmp" + jq -M 'to_entries | sort_by(.key) | from_entries' "$locale_file" > tmp.json + if [ "$*" == "--fix" ] then - mv "$tmp" $locale_file + mv tmp.json "$locale_file" else - if diff -q "$tmp" $locale_file > /dev/null ; + if ! diff -q tmp.json "$locale_file" > /dev/null ; then - : # all good - else - exit_code=$? - echo "$(basename -- $locale_file) is not sorted by keys" + exit_code=1 + echo "$(basename -- "$locale_file") is not sorted by keys" fi fi done +rm -f tmp.json exit $exit_code diff --git a/backend/src/auth/MODERATOR_RIGHTS.ts b/backend/src/auth/MODERATOR_RIGHTS.ts index 1ff689de6..61edad466 100644 --- a/backend/src/auth/MODERATOR_RIGHTS.ts +++ b/backend/src/auth/MODERATOR_RIGHTS.ts @@ -13,6 +13,7 @@ export const MODERATOR_RIGHTS = [ RIGHTS.DELETE_CONTRIBUTION_LINK, RIGHTS.UPDATE_CONTRIBUTION_LINK, RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE, + RIGHTS.MODERATOR_UPDATE_CONTRIBUTION_MEMO, RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES, RIGHTS.DENY_CONTRIBUTION, RIGHTS.ADMIN_OPEN_CREATIONS, diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 85ac3e3e7..0f6a4c00c 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -50,6 +50,7 @@ export enum RIGHTS { DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK', UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK', ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', + MODERATOR_UPDATE_CONTRIBUTION_MEMO = 'MODERATOR_UPDATE_CONTRIBUTION_MEMO', DENY_CONTRIBUTION = 'DENY_CONTRIBUTION', ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS', ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES', diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 7ad0271ea..f96cb4fe4 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,7 +12,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0074-insert_communityuuid in_existing_users', + DB_VERSION: '0076-add_updated_by_contribution', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/backend/src/data/Contribution.logic.ts b/backend/src/data/Contribution.logic.ts new file mode 100644 index 000000000..43b15bf3b --- /dev/null +++ b/backend/src/data/Contribution.logic.ts @@ -0,0 +1,52 @@ +import { Contribution } from '@entity/Contribution' +import { Decimal } from 'decimal.js-light' + +import { + getUserCreation, + updateCreations, + validateContribution, +} from '@/graphql/resolver/util/creations' +import { LogError } from '@/server/LogError' + +export class ContributionLogic { + // how much gradido can be still created + private availableCreationSums?: Decimal[] + public constructor(private self: Contribution) {} + + /** + * retrieve from db and return available creation sums array + * @param clientTimezoneOffset + * @param putThisBack if true, amount from this contribution will be added back to the availableCreationSums array, + * as if this creation wasn't part of it, used for update contribution + * @returns + */ + public async getAvailableCreationSums( + clientTimezoneOffset: number, + putThisBack = false, + ): Promise { + // TODO: move code from getUserCreation and updateCreations inside this function/class + this.availableCreationSums = await getUserCreation(this.self.userId, clientTimezoneOffset) + if (putThisBack) { + this.availableCreationSums = updateCreations( + this.availableCreationSums, + this.self, + clientTimezoneOffset, + ) + } + return this.availableCreationSums + } + + public checkAvailableCreationSumsNotExceeded( + amount: Decimal, + creationDate: Date, + clientTimezoneOffset: number, + ): void { + if (!this.availableCreationSums) { + throw new LogError( + 'missing available creation sums, please call getAvailableCreationSums first', + ) + } + // all possible cases not to be true are thrown in this function + validateContribution(this.availableCreationSums, amount, creationDate, clientTimezoneOffset) + } +} diff --git a/backend/src/data/ContributionMessage.builder.ts b/backend/src/data/ContributionMessage.builder.ts new file mode 100644 index 000000000..6d07bb81e --- /dev/null +++ b/backend/src/data/ContributionMessage.builder.ts @@ -0,0 +1,88 @@ +import { Contribution } from '@entity/Contribution' +import { ContributionMessage } from '@entity/ContributionMessage' +import { User } from '@entity/User' + +import { ContributionMessageType } from '@/graphql/enum/ContributionMessageType' + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class ContributionMessageBuilder { + private contributionMessage: ContributionMessage + + // https://refactoring.guru/design-patterns/builder/typescript/example + /** + * A fresh builder instance should contain a blank product object, which is + * used in further assembly. + */ + constructor() { + this.reset() + } + + public reset(): void { + this.contributionMessage = ContributionMessage.create() + } + + /** + * Concrete Builders are supposed to provide their own methods for + * retrieving results. That's because various types of builders may create + * entirely different products that don't follow the same interface. + * Therefore, such methods cannot be declared in the base Builder interface + * (at least in a statically typed programming language). + * + * Usually, after returning the end result to the client, a builder instance + * is expected to be ready to start producing another product. That's why + * it's a usual practice to call the reset method at the end of the + * `getProduct` method body. However, this behavior is not mandatory, and + * you can make your builders wait for an explicit reset call from the + * client code before disposing of the previous result. + */ + public build(): ContributionMessage { + const result = this.contributionMessage + this.reset() + return result + } + + public setParentContribution(contribution: Contribution): this { + this.contributionMessage.contributionId = contribution.id + this.contributionMessage.createdAt = contribution.updatedAt + ? contribution.updatedAt + : contribution.createdAt + return this + } + + /** + * set contribution message type to history and create message from contribution + * @param contribution + * @returns ContributionMessageBuilder for chaining function calls + */ + public setHistoryType(contribution: Contribution): this { + const changeMessage = `${contribution.contributionDate.toString()} + --- + ${contribution.memo} + --- + ${contribution.amount.toString()}` + this.contributionMessage.message = changeMessage + this.contributionMessage.type = ContributionMessageType.HISTORY + return this + } + + public setUser(user: User): this { + this.contributionMessage.user = user + this.contributionMessage.userId = user.id + return this + } + + public setUserId(userId: number): this { + this.contributionMessage.userId = userId + return this + } + + public setType(type: ContributionMessageType): this { + this.contributionMessage.type = type + return this + } + + public setIsModerator(value: boolean): this { + this.contributionMessage.isModerator = value + return this + } +} diff --git a/backend/src/data/UserLogic.ts b/backend/src/data/UserLogic.ts new file mode 100644 index 000000000..fbef2e609 --- /dev/null +++ b/backend/src/data/UserLogic.ts @@ -0,0 +1,11 @@ +import { User } from '@entity/User' +import { UserRole } from '@entity/UserRole' + +import { RoleNames } from '@enum/RoleNames' + +export class UserLogic { + public constructor(private self: User) {} + public isRole(role: RoleNames): boolean { + return this.self.userRoles.some((value: UserRole) => value.role === role.toString()) + } +} diff --git a/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap b/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap index f05291eab..00ec365f0 100644 --- a/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap +++ b/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap @@ -160,8 +160,7 @@ If the validity of the link has already expired, you can have a new link sent to
\\"facebook\\"\\"Telegram\\"\\"Twitter\\"\\"youtube\\"
-
If you have any further questions, please contact our support.
-
support@gradido.net
\\"Gradido +
If you have any further questions, please contact our support.
support@gradido.net\\"Gradido
Privacy Policy
Gradido-Akademie
Institut für Wirtschaftsbionik
Pfarrweg 2
74653 Künzelsau
Deutschland


@@ -330,8 +329,7 @@ exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTra
\\"facebook\\"\\"Telegram\\"\\"Twitter\\"\\"youtube\\"
-
If you have any further questions, please contact our support.
-
support@gradido.net
\\"Gradido +
If you have any further questions, please contact our support.
support@gradido.net\\"Gradido
Privacy Policy
Gradido-Akademie
Institut für Wirtschaftsbionik
Pfarrweg 2
74653 Künzelsau
Deutschland


@@ -497,8 +495,174 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
\\"facebook\\"\\"Telegram\\"\\"Twitter\\"\\"youtube\\"
-
If you have any further questions, please contact our support.
-
support@gradido.net
\\"Gradido +
If you have any further questions, please contact our support.
support@gradido.net\\"Gradido +
Privacy Policy +
Gradido-Akademie
Institut für Wirtschaftsbionik
Pfarrweg 2
74653 Künzelsau
Deutschland


+
+
+ +
+ +" +`; + +exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has the correct html as snapshot 1`] = ` +" + + + + + + + + +
+
+
\\"Gradido
+
+
+

Your common good contribution has been changed

+
+

Hello Peter Lustig,

+

your common good contribution 'My contribution.' has just been changed by Bibi Bloxberg and now reads as 'This is a better contribution memo.'

+
+
+

Contribution details

+
To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.
To account +
Or copy the link into your browser window.
http://localhost/community/contributions +
Please do not reply to this email.
+
+
+

Kind regards,
your Gradido team +

+
+
+