feat: add role via system to rejoining visionaries (#1198)

Co-authored-by: Hampus <hampus@fluxer.app>
This commit is contained in:
Jiralite
2026-06-30 01:24:16 +02:00
committed by GitHub
co-authored by Hampus
parent 8e1e9a67de
commit 4545fe018e
7 changed files with 43 additions and 4 deletions
+1
View File
@@ -370,6 +370,7 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
selfHosted: master.instance.self_hosted,
autoJoinInviteCode: master.instance.auto_join_invite_code,
visionariesGuildId: master.instance.visionaries_guild_id,
visionariesGuildVisionaryRoleId: master.instance.visionaries_guild_visionary_role_id,
branding: {
productName: master.instance.branding.product_name,
iconUrl: master.instance.branding.icon_url,
+1
View File
@@ -281,6 +281,7 @@ export interface APIConfig {
selfHosted: boolean;
autoJoinInviteCode?: string;
visionariesGuildId?: string;
visionariesGuildVisionaryRoleId?: string;
branding: {
productName: string;
iconUrl?: string;
@@ -4,8 +4,9 @@ import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import {MissingAccessError} from '@fluxer/errors/src/domains/core/MissingAccessError';
import {StripeError} from '@fluxer/errors/src/domains/payment/StripeError';
import type {UserID} from '../../BrandedTypes';
import {createGuildID} from '../../BrandedTypes';
import {createGuildID, createRoleID} from '../../BrandedTypes';
import {Config} from '../../Config';
import {SYSTEM_USER_ID} from '../../constants/Core';
import type {GiftCodeDurationType} from '../../database/types/PaymentTypes';
import type {UserRow} from '../../database/types/UserTypes';
import type {IGuildRepositoryAggregate} from '../../guild/repositories/IGuildRepositoryAggregate';
@@ -286,7 +287,12 @@ export class StripePremiumService {
if (!Config.instance.visionariesGuildId) {
throw new StripeError('Visionaries guild id not configured');
}
if (!Config.instance.visionariesGuildVisionaryRoleId) {
throw new StripeError('Visionaries guild visionary role id not configured');
}
const visionariesGuildId = createGuildID(BigInt(Config.instance.visionariesGuildId));
const visionaryRoleId = createRoleID(BigInt(Config.instance.visionariesGuildVisionaryRoleId));
const requestCache = createRequestCache();
const existingMember = await this.guildRepository.getMember(visionariesGuildId, userId);
if (!existingMember) {
await this.guildService.members.addUserToGuild({
@@ -294,10 +300,25 @@ export class StripePremiumService {
guildId: visionariesGuildId,
sendJoinMessage: true,
skipBanCheck: true,
requestCache: createRequestCache(),
requestCache,
});
Logger.debug({userId, guildId: visionariesGuildId}, 'Added visionary user to visionaries guild');
}
try {
await this.guildService.members.systemAddMemberRole({
targetId: userId,
guildId: visionariesGuildId,
roleId: visionaryRoleId,
initiatorId: SYSTEM_USER_ID,
requestCache,
});
} catch (error) {
Logger.error(
{userId, guildId: visionariesGuildId, roleId: visionaryRoleId, error},
'Failed to add visionary role to a rejoining visionary.',
);
}
}
private async dispatchUser(user: User): Promise<void> {
@@ -5,26 +5,31 @@ import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {Config} from '../../Config';
import {createGuild} from '../../guild/tests/GuildTestUtils';
import {createGuild, createRole, getMember} from '../../guild/tests/GuildTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {createBuilder} from '../../test/TestRequestBuilder';
describe('StripePremiumService', () => {
let harness: ApiTestHarness;
let originalVisionariesGuildId: string | undefined;
let originalVisionariesGuildVisionaryRoleId: string | undefined;
beforeAll(async () => {
harness = await createApiTestHarness();
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
originalVisionariesGuildVisionaryRoleId = Config.instance.visionariesGuildVisionaryRoleId ?? undefined;
});
afterAll(async () => {
await harness.shutdown();
Config.instance.visionariesGuildId = originalVisionariesGuildId;
Config.instance.visionariesGuildVisionaryRoleId = originalVisionariesGuildVisionaryRoleId;
});
beforeEach(async () => {
await harness.resetData();
const owner = await createTestAccount(harness);
const visionariesGuild = await createGuild(harness, owner.token, 'Visionaries Test Guild');
const visionaryRole = await createRole(harness, owner.token, visionariesGuild.id, {name: 'Visionary'});
Config.instance.visionariesGuildId = visionariesGuild.id;
Config.instance.visionariesGuildVisionaryRoleId = visionaryRole.id;
});
describe('POST /premium/visionary/rejoin', () => {
test('allows visionary users to rejoin guild', async () => {
@@ -37,6 +42,8 @@ describe('StripePremiumService', () => {
})
.execute();
await createBuilder(harness, account.token).post('/premium/visionary/rejoin').expect(204).execute();
const member = await getMember(harness, account.token, Config.instance.visionariesGuildId!, account.userId);
expect(member.roles).toContain(Config.instance.visionariesGuildVisionaryRoleId);
});
test('rejects users without visionary access', async () => {
const account = await createTestAccount(harness);
@@ -5,26 +5,31 @@ import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {Config} from '../../Config';
import {createGuild} from '../../guild/tests/GuildTestUtils';
import {createGuild, createRole, getMember} from '../../guild/tests/GuildTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {createBuilder} from '../../test/TestRequestBuilder';
describe('Stripe Webhook Edge Cases', () => {
let harness: ApiTestHarness;
let originalVisionariesGuildId: string | undefined;
let originalVisionariesGuildVisionaryRoleId: string | undefined;
beforeAll(async () => {
harness = await createApiTestHarness();
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
originalVisionariesGuildVisionaryRoleId = Config.instance.visionariesGuildVisionaryRoleId ?? undefined;
});
afterAll(async () => {
await harness.shutdown();
Config.instance.visionariesGuildId = originalVisionariesGuildId;
Config.instance.visionariesGuildVisionaryRoleId = originalVisionariesGuildVisionaryRoleId;
});
beforeEach(async () => {
await harness.resetData();
const owner = await createTestAccount(harness);
const visionariesGuild = await createGuild(harness, owner.token, 'Visionaries Webhook Test Guild');
const visionaryRole = await createRole(harness, owner.token, visionariesGuild.id, {name: 'Visionary'});
Config.instance.visionariesGuildId = visionariesGuild.id;
Config.instance.visionariesGuildVisionaryRoleId = visionaryRole.id;
});
describe('Premium stacking - consecutive grants extending duration', () => {
test('stacks multiple monthly subscriptions end-to-end', async () => {
@@ -366,6 +371,8 @@ describe('Stripe Webhook Edge Cases', () => {
.execute();
expect(receiverAfter.premium_type).toBe(UserPremiumTypes.LIFETIME);
expect(receiverAfter.premium_lifetime_sequence).toBe(0);
const member = await getMember(harness, receiver.token, Config.instance.visionariesGuildId!, receiver.userId);
expect(member.roles).toContain(Config.instance.visionariesGuildVisionaryRoleId);
});
test('redeeming 1-month gift code grants subscription premium', async () => {
const gifter = await createTestAccount(harness);
+1
View File
@@ -316,6 +316,7 @@ export interface MasterConfig {
self_hosted: boolean;
auto_join_invite_code?: string;
visionaries_guild_id?: string;
visionaries_guild_visionary_role_id?: string;
branding: InstanceBrandingConfig;
setup: {
configured: boolean;
@@ -321,6 +321,7 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
FLUXER_SELF_HOSTED: {path: ['instance', 'self_hosted'], parse: parseEnvValue},
FLUXER_AUTO_JOIN_INVITE_CODE: {path: ['instance', 'auto_join_invite_code']},
FLUXER_VISIONARIES_GUILD_ID: {path: ['instance', 'visionaries_guild_id']},
FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID: {path: ['instance', 'visionaries_guild_visionary_role_id']},
FLUXER_APP_PRODUCT_NAME: {path: ['instance', 'branding', 'product_name']},
FLUXER_APP_ICON_URL: {path: ['instance', 'branding', 'icon_url']},
FLUXER_APP_SYMBOL_URL: {path: ['instance', 'branding', 'symbol_url']},