diff --git a/fluxer_api/src/api/admin/controllers/CodesAdminController.ts b/fluxer_api/src/api/admin/controllers/CodesAdminController.ts index 5d4a7d548..7dddfefc7 100644 --- a/fluxer_api/src/api/admin/controllers/CodesAdminController.ts +++ b/fluxer_api/src/api/admin/controllers/CodesAdminController.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {Config} from '@app/api/Config'; +import {SYSTEM_USER_ID} from '@app/api/constants/Core'; import {requireAdminACL} from '@app/api/middleware/AdminMiddleware'; import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware'; import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware'; @@ -42,6 +43,7 @@ export function CodesAdminController(app: HonoApp) { count, durationType: duration_type, durationQuantity: duration_quantity, + createdByUserId: Config.instance.selfHosted ? ctx.get('adminUserId') : SYSTEM_USER_ID, }); await adminService.auditService.createAuditLog({ adminUserId: ctx.get('adminUserId'), diff --git a/fluxer_api/src/api/admin/services/AdminCodeGenerationService.ts b/fluxer_api/src/api/admin/services/AdminCodeGenerationService.ts index 720785ddc..f0d3715c8 100644 --- a/fluxer_api/src/api/admin/services/AdminCodeGenerationService.ts +++ b/fluxer_api/src/api/admin/services/AdminCodeGenerationService.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {SYSTEM_USER_ID} from '@app/api/constants/Core'; +import type {UserID} from '@app/api/BrandedTypes'; import type {GiftCodeDurationType, GiftCodeRow} from '@app/api/database/types/PaymentTypes'; import {mapGiftCodeDurationToMonths} from '@app/api/models/GiftCode'; import type {IUserRepository} from '@app/api/user/IUserRepository'; @@ -15,13 +15,14 @@ interface GenerateGiftCodesOptions { count: number; durationType: GiftCodeDurationType; durationQuantity: number; + createdByUserId: UserID; } export class AdminCodeGenerationService { constructor(private readonly userRepository: IUserRepository) {} async generateGiftCodes(options: GenerateGiftCodesOptions): Promise> { - const {count, durationType, durationQuantity} = this.validateOptions(options); + const {count, durationType, durationQuantity, createdByUserId} = this.validateOptions(options); const durationMonths = mapGiftCodeDurationToMonths(durationType, durationQuantity); const codes: Array = []; for (let i = 0; i < count; i += 1) { @@ -32,7 +33,7 @@ export class AdminCodeGenerationService { duration_type: durationType, duration_quantity: durationQuantity, created_at: new Date(), - created_by_user_id: SYSTEM_USER_ID, + created_by_user_id: createdByUserId, redeemed_at: null, redeemed_by_user_id: null, stripe_payment_intent_id: null, diff --git a/fluxer_api/src/api/stripe/tests/SelfHostedPremiumRoutes.test.ts b/fluxer_api/src/api/stripe/tests/SelfHostedPremiumRoutes.test.ts index 4b0d9ae1e..b0915bfec 100644 --- a/fluxer_api/src/api/stripe/tests/SelfHostedPremiumRoutes.test.ts +++ b/fluxer_api/src/api/stripe/tests/SelfHostedPremiumRoutes.test.ts @@ -284,6 +284,22 @@ describe('self-hosted premium routes', () => { expect(gifts).toEqual([]); }); + test('credits admin generated gift codes to the issuing admin', async () => { + const admin = await setUserACLs(harness, await createTestAccount(harness), [ + AdminACLs.AUTHENTICATE, + AdminACLs.GIFT_CODES_GENERATE, + ]); + const {codes} = await createBuilder<{codes: Array}>(harness, admin.token) + .post('/admin/gift-codes') + .body({count: 1, duration_type: 'months', duration_quantity: 1}) + .execute(); + const code = codes[0]?.split('/').pop(); + const gift = await createBuilderWithoutAuth<{created_by: {id: string} | null}>(harness) + .get(`/gifts/${code}`) + .execute(); + expect(gift.created_by?.id).toBe(admin.userId); + }); + test('keeps purchase and hosted-only routes unavailable', async () => { const account = await createTestAccount(harness); await expectRoutesNotFound(harness, account.token, [