mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #1979 from gradido/crud-contribution-link
feat: CRUD for Contribution Links in Admin Resolver
This commit is contained in:
commit
a6ca269352
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -528,7 +528,7 @@ jobs:
|
||||
report_name: Coverage Backend
|
||||
type: lcov
|
||||
result_path: ./backend/coverage/lcov.info
|
||||
min_coverage: 68
|
||||
min_coverage: 70
|
||||
token: ${{ github.token }}
|
||||
|
||||
##########################################################################
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
CONFIG_VERSION=v6.2022-04-21
|
||||
CONFIG_VERSION=v7.2022-06-15
|
||||
|
||||
# Server
|
||||
PORT=4000
|
||||
@ -28,6 +28,7 @@ COMMUNITY_NAME=Gradido Entwicklung
|
||||
COMMUNITY_URL=http://localhost/
|
||||
COMMUNITY_REGISTER_URL=http://localhost/register
|
||||
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=http://localhost/redeem/CL-{code}
|
||||
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
||||
|
||||
# Login Server
|
||||
|
||||
@ -27,6 +27,7 @@ COMMUNITY_NAME=$COMMUNITY_NAME
|
||||
COMMUNITY_URL=$COMMUNITY_URL
|
||||
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
|
||||
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=$COMMUNITY_REDEEM_CONTRIBUTION_URL
|
||||
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||
|
||||
# Login Server
|
||||
|
||||
@ -37,4 +37,8 @@ export enum RIGHTS {
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
|
||||
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
|
||||
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
||||
LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS',
|
||||
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ const constants = {
|
||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v6.2022-04-21',
|
||||
EXPECTED: 'v7.2022-06-15',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
@ -54,6 +54,8 @@ const community = {
|
||||
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
|
||||
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
|
||||
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL || 'http://localhost/redeem/{code}',
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_URL:
|
||||
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
|
||||
COMMUNITY_DESCRIPTION:
|
||||
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||
}
|
||||
|
||||
29
backend/src/graphql/arg/ContributionLinkArgs.ts
Normal file
29
backend/src/graphql/arg/ContributionLinkArgs.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ArgsType()
|
||||
export default class ContributionLinkArgs {
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => String)
|
||||
name: string
|
||||
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => String)
|
||||
cycle: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
validFrom?: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
validTo?: string | null
|
||||
|
||||
@Field(() => Decimal, { nullable: true })
|
||||
maxAmountPerMonth: Decimal | null
|
||||
|
||||
@Field(() => Int)
|
||||
maxPerCycle: number
|
||||
}
|
||||
28
backend/src/graphql/enum/ContributionCycleType.ts
Normal file
28
backend/src/graphql/enum/ContributionCycleType.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum ContributionCycleType {
|
||||
ONCE = 'once',
|
||||
HOUR = 'hour',
|
||||
TWO_HOURS = 'two_hours',
|
||||
FOUR_HOURS = 'four_hours',
|
||||
EIGHT_HOURS = 'eight_hours',
|
||||
HALF_DAY = 'half_day',
|
||||
DAY = 'day',
|
||||
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',
|
||||
}
|
||||
|
||||
registerEnumType(ContributionCycleType, {
|
||||
name: 'ContributionCycleType', // this one is mandatory
|
||||
description: 'Name of the Type of the ContributionCycle', // this one is optional
|
||||
})
|
||||
62
backend/src/graphql/model/ContributionLink.ts
Normal file
62
backend/src/graphql/model/ContributionLink.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { ContributionLink as dbContributionLink } from '@entity/ContributionLink'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
@ObjectType()
|
||||
export class ContributionLink {
|
||||
constructor(contributionLink: dbContributionLink) {
|
||||
this.id = contributionLink.id
|
||||
this.amount = contributionLink.amount
|
||||
this.name = contributionLink.name
|
||||
this.memo = contributionLink.memo
|
||||
this.createdAt = contributionLink.createdAt
|
||||
this.deletedAt = contributionLink.deletedAt
|
||||
this.validFrom = contributionLink.validFrom
|
||||
this.validTo = contributionLink.validTo
|
||||
this.maxAmountPerMonth = contributionLink.maxAmountPerMonth
|
||||
this.cycle = contributionLink.cycle
|
||||
this.maxPerCycle = contributionLink.maxPerCycle
|
||||
this.code = contributionLink.code
|
||||
this.link = CONFIG.COMMUNITY_REDEEM_CONTRIBUTION_URL.replace(/{code}/g, this.code)
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
id: number
|
||||
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => String)
|
||||
name: string
|
||||
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => String)
|
||||
code: string
|
||||
|
||||
@Field(() => String)
|
||||
link: string
|
||||
|
||||
@Field(() => Date)
|
||||
createdAt: Date
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
validFrom: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
validTo: Date | null
|
||||
|
||||
@Field(() => Decimal, { nullable: true })
|
||||
maxAmountPerMonth: Decimal | null
|
||||
|
||||
@Field(() => String)
|
||||
cycle: string
|
||||
|
||||
@Field(() => Int)
|
||||
maxPerCycle: number
|
||||
}
|
||||
11
backend/src/graphql/model/ContributionLinkList.ts
Normal file
11
backend/src/graphql/model/ContributionLinkList.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
|
||||
@ObjectType()
|
||||
export class ContributionLinkList {
|
||||
@Field(() => [ContributionLink])
|
||||
links: ContributionLink[]
|
||||
|
||||
@Field(() => Number)
|
||||
count: number
|
||||
}
|
||||
@ -20,12 +20,16 @@ import {
|
||||
updatePendingCreation,
|
||||
deletePendingCreation,
|
||||
confirmPendingCreation,
|
||||
createContributionLink,
|
||||
deleteContributionLink,
|
||||
updateContributionLink,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import {
|
||||
getPendingCreations,
|
||||
login,
|
||||
searchUsers,
|
||||
listTransactionLinksAdmin,
|
||||
listContributionLinks,
|
||||
} from '@/seeds/graphql/queries'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { User } from '@entity/User'
|
||||
@ -34,6 +38,7 @@ import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { AdminPendingCreation } from '@entity/AdminPendingCreation'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
jest.mock('@/mailer/sendAccountActivationEmail', () => {
|
||||
@ -1593,4 +1598,361 @@ describe('AdminResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Contribution Links', () => {
|
||||
const variables = {
|
||||
amount: new Decimal(200),
|
||||
name: 'Dokumenta 2022',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||
cycle: 'once',
|
||||
validFrom: new Date(2022, 5, 18).toISOString(),
|
||||
validTo: new Date(2022, 7, 14).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
}
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
describe('createContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('listContributionLinks', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContributionLink,
|
||||
variables: {
|
||||
...variables,
|
||||
id: -1,
|
||||
amount: new Decimal(400),
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('without admin rights', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, bibiBloxberg)
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('createContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('listContributionLinks', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContributionLink,
|
||||
variables: {
|
||||
...variables,
|
||||
id: -1,
|
||||
amount: new Decimal(400),
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteContributionLink', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with admin rights', () => {
|
||||
beforeAll(async () => {
|
||||
user = await userFactory(testEnv, peterLustig)
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('createContributionLink', () => {
|
||||
it('returns a contribution link object', async () => {
|
||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
createContributionLink: expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
amount: '200',
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||
createdAt: expect.any(String),
|
||||
name: 'Dokumenta 2022',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||
validFrom: expect.any(String),
|
||||
validTo: expect.any(String),
|
||||
maxAmountPerMonth: '200',
|
||||
cycle: 'once',
|
||||
maxPerCycle: 1,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('has a contribution link stored in db', async () => {
|
||||
const cls = await DbContributionLink.find()
|
||||
expect(cls).toHaveLength(1)
|
||||
expect(cls[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
name: 'Dokumenta 2022',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||
validFrom: new Date('2022-06-18T00:00:00.000Z'),
|
||||
validTo: new Date('2022-08-14T00:00:00.000Z'),
|
||||
cycle: 'once',
|
||||
maxPerCycle: 1,
|
||||
totalMaxCountOfContribution: null,
|
||||
maxAccountBalance: null,
|
||||
minGapHours: null,
|
||||
createdAt: expect.any(Date),
|
||||
deletedAt: null,
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
linkEnabled: true,
|
||||
// amount: '200',
|
||||
// maxAmountPerMonth: '200',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('listContributionLinks', () => {
|
||||
describe('one link in DB', () => {
|
||||
it('returns the link and count 1', async () => {
|
||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listContributionLinks: {
|
||||
links: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '200',
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||
createdAt: expect.any(String),
|
||||
name: 'Dokumenta 2022',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||
validFrom: expect.any(String),
|
||||
validTo: expect.any(String),
|
||||
maxAmountPerMonth: '200',
|
||||
cycle: 'once',
|
||||
maxPerCycle: 1,
|
||||
}),
|
||||
]),
|
||||
count: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateContributionLink', () => {
|
||||
describe('no valid id', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContributionLink,
|
||||
variables: {
|
||||
...variables,
|
||||
id: -1,
|
||||
amount: new Decimal(400),
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid id', () => {
|
||||
let linkId: number
|
||||
beforeAll(async () => {
|
||||
const links = await query({ query: listContributionLinks })
|
||||
linkId = links.data.listContributionLinks.links[0].id
|
||||
})
|
||||
|
||||
it('returns updated contribution link object', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContributionLink,
|
||||
variables: {
|
||||
...variables,
|
||||
id: linkId,
|
||||
amount: new Decimal(400),
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
updateContributionLink: {
|
||||
id: linkId,
|
||||
amount: '400',
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||
createdAt: expect.any(String),
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
validFrom: expect.any(String),
|
||||
validTo: expect.any(String),
|
||||
maxAmountPerMonth: '200',
|
||||
cycle: 'once',
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('updated the DB record', async () => {
|
||||
await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: linkId,
|
||||
name: 'Dokumenta 2023',
|
||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||
// amount: '400',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteContributionLink', () => {
|
||||
describe('no valid id', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid id', () => {
|
||||
let linkId: number
|
||||
beforeAll(async () => {
|
||||
const links = await query({ query: listContributionLinks })
|
||||
linkId = links.data.listContributionLinks.links[0].id
|
||||
})
|
||||
|
||||
it('returns a date string', async () => {
|
||||
await expect(
|
||||
mutate({ mutation: deleteContributionLink, variables: { id: linkId } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
deleteContributionLink: expect.any(String),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('does not list this contribution link anymore', async () => {
|
||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listContributionLinks: {
|
||||
links: [],
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql'
|
||||
import {
|
||||
getCustomRepository,
|
||||
@ -14,12 +15,16 @@ import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
|
||||
import { PendingCreation } from '@model/PendingCreation'
|
||||
import { CreatePendingCreations } from '@model/CreatePendingCreations'
|
||||
import { UpdatePendingCreation } from '@model/UpdatePendingCreation'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
import { ContributionLinkList } from '@model/ContributionLinkList'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { UserRepository } from '@repository/User'
|
||||
import CreatePendingCreationArgs from '@arg/CreatePendingCreationArgs'
|
||||
import UpdatePendingCreationArgs from '@arg/UpdatePendingCreationArgs'
|
||||
import SearchUsersArgs from '@arg/SearchUsersArgs'
|
||||
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
@ -39,6 +44,7 @@ import { Order } from '@enum/Order'
|
||||
import { communityUser } from '@/util/communityUser'
|
||||
import { checkOptInCode, activationLink, printTimeDuration } from './UserResolver'
|
||||
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
// const EMAIL_OPT_IN_REGISTER = 1
|
||||
@ -460,6 +466,99 @@ export class AdminResolver {
|
||||
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION_LINK])
|
||||
@Mutation(() => ContributionLink)
|
||||
async createContributionLink(
|
||||
@Args()
|
||||
{
|
||||
amount,
|
||||
name,
|
||||
memo,
|
||||
cycle,
|
||||
validFrom,
|
||||
validTo,
|
||||
maxAmountPerMonth,
|
||||
maxPerCycle,
|
||||
}: ContributionLinkArgs,
|
||||
): Promise<ContributionLink> {
|
||||
const dbContributionLink = new DbContributionLink()
|
||||
dbContributionLink.amount = amount
|
||||
dbContributionLink.name = name
|
||||
dbContributionLink.memo = memo
|
||||
dbContributionLink.createdAt = new Date()
|
||||
dbContributionLink.code = contributionLinkCode(dbContributionLink.createdAt)
|
||||
dbContributionLink.cycle = cycle
|
||||
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
||||
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
||||
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
||||
dbContributionLink.maxPerCycle = maxPerCycle
|
||||
await dbContributionLink.save()
|
||||
return new ContributionLink(dbContributionLink)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LIST_CONTRIBUTION_LINKS])
|
||||
@Query(() => ContributionLinkList)
|
||||
async listContributionLinks(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionLinkList> {
|
||||
const [links, count] = await DbContributionLink.findAndCount({
|
||||
order: { createdAt: order },
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
return {
|
||||
links: links.map((link: DbContributionLink) => new ContributionLink(link)),
|
||||
count,
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.DELETE_CONTRIBUTION_LINK])
|
||||
@Mutation(() => Date, { nullable: true })
|
||||
async deleteContributionLink(@Arg('id', () => Int) id: number): Promise<Date | null> {
|
||||
const contributionLink = await DbContributionLink.findOne(id)
|
||||
if (!contributionLink) {
|
||||
logger.error(`Contribution Link not found to given id: ${id}`)
|
||||
throw new Error('Contribution Link not found to given id.')
|
||||
}
|
||||
await contributionLink.softRemove()
|
||||
const newContributionLink = await DbContributionLink.findOne({ id }, { withDeleted: true })
|
||||
return newContributionLink ? newContributionLink.deletedAt : null
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.UPDATE_CONTRIBUTION_LINK])
|
||||
@Mutation(() => ContributionLink)
|
||||
async updateContributionLink(
|
||||
@Args()
|
||||
{
|
||||
amount,
|
||||
name,
|
||||
memo,
|
||||
cycle,
|
||||
validFrom,
|
||||
validTo,
|
||||
maxAmountPerMonth,
|
||||
maxPerCycle,
|
||||
}: ContributionLinkArgs,
|
||||
@Arg('id', () => Int) id: number,
|
||||
): Promise<ContributionLink> {
|
||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||
if (!dbContributionLink) {
|
||||
logger.error(`Contribution Link not found to given id: ${id}`)
|
||||
throw new Error('Contribution Link not found to given id.')
|
||||
}
|
||||
dbContributionLink.amount = amount
|
||||
dbContributionLink.name = name
|
||||
dbContributionLink.memo = memo
|
||||
dbContributionLink.cycle = cycle
|
||||
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
||||
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
||||
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
||||
dbContributionLink.maxPerCycle = maxPerCycle
|
||||
await dbContributionLink.save()
|
||||
return new ContributionLink(dbContributionLink)
|
||||
}
|
||||
}
|
||||
|
||||
interface CreationMap {
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export interface ContributionLinkInterface {
|
||||
amount: number
|
||||
name: string
|
||||
memo: string
|
||||
validFrom?: Date
|
||||
validTo?: Date
|
||||
}
|
||||
18
backend/src/seeds/contributionLink/index.ts
Normal file
18
backend/src/seeds/contributionLink/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ContributionLinkInterface } from './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),
|
||||
},
|
||||
]
|
||||
27
backend/src/seeds/factory/contributionLink.ts
Normal file
27
backend/src/seeds/factory/contributionLink.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { createContributionLink } from '@/seeds/graphql/mutations'
|
||||
import { login } from '@/seeds/graphql/queries'
|
||||
import { ContributionLinkInterface } from '@/seeds/contributionLink/ContributionLinkInterface'
|
||||
|
||||
export const contributionLinkFactory = async (
|
||||
client: ApolloServerTestClient,
|
||||
contributionLink: ContributionLinkInterface,
|
||||
): Promise<void> => {
|
||||
const { mutate, query } = client
|
||||
|
||||
// login as admin
|
||||
await query({ query: 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,
|
||||
}
|
||||
|
||||
await mutate({ mutation: createContributionLink, variables })
|
||||
}
|
||||
@ -137,3 +137,85 @@ export const deletePendingCreation = gql`
|
||||
deletePendingCreation(id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
export const createContributionLink = gql`
|
||||
mutation (
|
||||
$amount: Decimal!
|
||||
$name: String!
|
||||
$memo: String!
|
||||
$cycle: String!
|
||||
$validFrom: String
|
||||
$validTo: String
|
||||
$maxAmountPerMonth: Decimal
|
||||
$maxPerCycle: Int! = 1
|
||||
) {
|
||||
createContributionLink(
|
||||
amount: $amount
|
||||
name: $name
|
||||
memo: $memo
|
||||
cycle: $cycle
|
||||
validFrom: $validFrom
|
||||
validTo: $validTo
|
||||
maxAmountPerMonth: $maxAmountPerMonth
|
||||
maxPerCycle: $maxPerCycle
|
||||
) {
|
||||
id
|
||||
amount
|
||||
name
|
||||
memo
|
||||
code
|
||||
link
|
||||
createdAt
|
||||
validFrom
|
||||
validTo
|
||||
maxAmountPerMonth
|
||||
cycle
|
||||
maxPerCycle
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const updateContributionLink = gql`
|
||||
mutation (
|
||||
$amount: Decimal!
|
||||
$name: String!
|
||||
$memo: String!
|
||||
$cycle: String!
|
||||
$validFrom: String
|
||||
$validTo: String
|
||||
$maxAmountPerMonth: Decimal
|
||||
$maxPerCycle: Int! = 1
|
||||
$id: Int!
|
||||
) {
|
||||
updateContributionLink(
|
||||
amount: $amount
|
||||
name: $name
|
||||
memo: $memo
|
||||
cycle: $cycle
|
||||
validFrom: $validFrom
|
||||
validTo: $validTo
|
||||
maxAmountPerMonth: $maxAmountPerMonth
|
||||
maxPerCycle: $maxPerCycle
|
||||
id: $id
|
||||
) {
|
||||
id
|
||||
amount
|
||||
name
|
||||
memo
|
||||
code
|
||||
link
|
||||
createdAt
|
||||
validFrom
|
||||
validTo
|
||||
maxAmountPerMonth
|
||||
cycle
|
||||
maxPerCycle
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const deleteContributionLink = gql`
|
||||
mutation ($id: Int!) {
|
||||
deleteContributionLink(id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
@ -217,3 +217,25 @@ export const listTransactionLinksAdmin = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const listContributionLinks = gql`
|
||||
query ($pageSize: Int = 25, $currentPage: Int = 1, $order: Order) {
|
||||
listContributionLinks(pageSize: $pageSize, currentPage: $currentPage, order: $order) {
|
||||
links {
|
||||
id
|
||||
amount
|
||||
name
|
||||
memo
|
||||
code
|
||||
link
|
||||
createdAt
|
||||
validFrom
|
||||
validTo
|
||||
maxAmountPerMonth
|
||||
cycle
|
||||
maxPerCycle
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -9,9 +9,11 @@ import { name, internet, datatype } from 'faker'
|
||||
import { users } from './users/index'
|
||||
import { creations } from './creation/index'
|
||||
import { transactionLinks } from './transactionLink/index'
|
||||
import { contributionLinks } from './contributionLink/index'
|
||||
import { userFactory } from './factory/user'
|
||||
import { creationFactory } from './factory/creation'
|
||||
import { transactionLinkFactory } from './factory/transactionLink'
|
||||
import { contributionLinkFactory } from './factory/contributionLink'
|
||||
import { entities } from '@entity/index'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
@ -77,6 +79,11 @@ const run = async () => {
|
||||
await transactionLinkFactory(seedClient, transactionLinks[i])
|
||||
}
|
||||
|
||||
// create Contribution Links
|
||||
for (let i = 0; i < contributionLinks.length; i++) {
|
||||
await contributionLinkFactory(seedClient, contributionLinks[i])
|
||||
}
|
||||
|
||||
await con.close()
|
||||
}
|
||||
|
||||
|
||||
@ -22,10 +22,11 @@ COMMUNITY_NAME="Gradido Development Stage1"
|
||||
COMMUNITY_URL=https://stage1.gradido.net/
|
||||
COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
|
||||
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code}
|
||||
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
||||
|
||||
# backend
|
||||
BACKEND_CONFIG_VERSION=v6.2022-04-21
|
||||
BACKEND_CONFIG_VERSION=v7.2022-06-15
|
||||
|
||||
JWT_EXPIRES_IN=30m
|
||||
GDT_API_URL=https://gdt.gradido.net
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user