mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat: add role via system to rejoining visionaries (#1198)
Co-authored-by: Hampus <[email protected]>
This commit is contained in:
@@ -370,6 +370,7 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
|||||||
selfHosted: master.instance.self_hosted,
|
selfHosted: master.instance.self_hosted,
|
||||||
autoJoinInviteCode: master.instance.auto_join_invite_code,
|
autoJoinInviteCode: master.instance.auto_join_invite_code,
|
||||||
visionariesGuildId: master.instance.visionaries_guild_id,
|
visionariesGuildId: master.instance.visionaries_guild_id,
|
||||||
|
visionariesGuildVisionaryRoleId: master.instance.visionaries_guild_visionary_role_id,
|
||||||
branding: {
|
branding: {
|
||||||
productName: master.instance.branding.product_name,
|
productName: master.instance.branding.product_name,
|
||||||
iconUrl: master.instance.branding.icon_url,
|
iconUrl: master.instance.branding.icon_url,
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ export interface APIConfig {
|
|||||||
selfHosted: boolean;
|
selfHosted: boolean;
|
||||||
autoJoinInviteCode?: string;
|
autoJoinInviteCode?: string;
|
||||||
visionariesGuildId?: string;
|
visionariesGuildId?: string;
|
||||||
|
visionariesGuildVisionaryRoleId?: string;
|
||||||
branding: {
|
branding: {
|
||||||
productName: string;
|
productName: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
|
|||||||
import {MissingAccessError} from '@fluxer/errors/src/domains/core/MissingAccessError';
|
import {MissingAccessError} from '@fluxer/errors/src/domains/core/MissingAccessError';
|
||||||
import {StripeError} from '@fluxer/errors/src/domains/payment/StripeError';
|
import {StripeError} from '@fluxer/errors/src/domains/payment/StripeError';
|
||||||
import type {UserID} from '../../BrandedTypes';
|
import type {UserID} from '../../BrandedTypes';
|
||||||
import {createGuildID} from '../../BrandedTypes';
|
import {createGuildID, createRoleID} from '../../BrandedTypes';
|
||||||
import {Config} from '../../Config';
|
import {Config} from '../../Config';
|
||||||
|
import {SYSTEM_USER_ID} from '../../constants/Core';
|
||||||
import type {GiftCodeDurationType} from '../../database/types/PaymentTypes';
|
import type {GiftCodeDurationType} from '../../database/types/PaymentTypes';
|
||||||
import type {UserRow} from '../../database/types/UserTypes';
|
import type {UserRow} from '../../database/types/UserTypes';
|
||||||
import type {IGuildRepositoryAggregate} from '../../guild/repositories/IGuildRepositoryAggregate';
|
import type {IGuildRepositoryAggregate} from '../../guild/repositories/IGuildRepositoryAggregate';
|
||||||
@@ -286,7 +287,12 @@ export class StripePremiumService {
|
|||||||
if (!Config.instance.visionariesGuildId) {
|
if (!Config.instance.visionariesGuildId) {
|
||||||
throw new StripeError('Visionaries guild id not configured');
|
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 visionariesGuildId = createGuildID(BigInt(Config.instance.visionariesGuildId));
|
||||||
|
const visionaryRoleId = createRoleID(BigInt(Config.instance.visionariesGuildVisionaryRoleId));
|
||||||
|
const requestCache = createRequestCache();
|
||||||
const existingMember = await this.guildRepository.getMember(visionariesGuildId, userId);
|
const existingMember = await this.guildRepository.getMember(visionariesGuildId, userId);
|
||||||
if (!existingMember) {
|
if (!existingMember) {
|
||||||
await this.guildService.members.addUserToGuild({
|
await this.guildService.members.addUserToGuild({
|
||||||
@@ -294,10 +300,25 @@ export class StripePremiumService {
|
|||||||
guildId: visionariesGuildId,
|
guildId: visionariesGuildId,
|
||||||
sendJoinMessage: true,
|
sendJoinMessage: true,
|
||||||
skipBanCheck: true,
|
skipBanCheck: true,
|
||||||
requestCache: createRequestCache(),
|
requestCache,
|
||||||
});
|
});
|
||||||
Logger.debug({userId, guildId: visionariesGuildId}, 'Added visionary user to visionaries guild');
|
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> {
|
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 {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
|
||||||
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
||||||
import {Config} from '../../Config';
|
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 {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
||||||
import {createBuilder} from '../../test/TestRequestBuilder';
|
import {createBuilder} from '../../test/TestRequestBuilder';
|
||||||
|
|
||||||
describe('StripePremiumService', () => {
|
describe('StripePremiumService', () => {
|
||||||
let harness: ApiTestHarness;
|
let harness: ApiTestHarness;
|
||||||
let originalVisionariesGuildId: string | undefined;
|
let originalVisionariesGuildId: string | undefined;
|
||||||
|
let originalVisionariesGuildVisionaryRoleId: string | undefined;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
harness = await createApiTestHarness();
|
harness = await createApiTestHarness();
|
||||||
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
|
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
|
||||||
|
originalVisionariesGuildVisionaryRoleId = Config.instance.visionariesGuildVisionaryRoleId ?? undefined;
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await harness.shutdown();
|
await harness.shutdown();
|
||||||
Config.instance.visionariesGuildId = originalVisionariesGuildId;
|
Config.instance.visionariesGuildId = originalVisionariesGuildId;
|
||||||
|
Config.instance.visionariesGuildVisionaryRoleId = originalVisionariesGuildVisionaryRoleId;
|
||||||
});
|
});
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await harness.resetData();
|
await harness.resetData();
|
||||||
const owner = await createTestAccount(harness);
|
const owner = await createTestAccount(harness);
|
||||||
const visionariesGuild = await createGuild(harness, owner.token, 'Visionaries Test Guild');
|
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.visionariesGuildId = visionariesGuild.id;
|
||||||
|
Config.instance.visionariesGuildVisionaryRoleId = visionaryRole.id;
|
||||||
});
|
});
|
||||||
describe('POST /premium/visionary/rejoin', () => {
|
describe('POST /premium/visionary/rejoin', () => {
|
||||||
test('allows visionary users to rejoin guild', async () => {
|
test('allows visionary users to rejoin guild', async () => {
|
||||||
@@ -37,6 +42,8 @@ describe('StripePremiumService', () => {
|
|||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
await createBuilder(harness, account.token).post('/premium/visionary/rejoin').expect(204).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 () => {
|
test('rejects users without visionary access', async () => {
|
||||||
const account = await createTestAccount(harness);
|
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 {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
|
||||||
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
|
||||||
import {Config} from '../../Config';
|
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 {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
|
||||||
import {createBuilder} from '../../test/TestRequestBuilder';
|
import {createBuilder} from '../../test/TestRequestBuilder';
|
||||||
|
|
||||||
describe('Stripe Webhook Edge Cases', () => {
|
describe('Stripe Webhook Edge Cases', () => {
|
||||||
let harness: ApiTestHarness;
|
let harness: ApiTestHarness;
|
||||||
let originalVisionariesGuildId: string | undefined;
|
let originalVisionariesGuildId: string | undefined;
|
||||||
|
let originalVisionariesGuildVisionaryRoleId: string | undefined;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
harness = await createApiTestHarness();
|
harness = await createApiTestHarness();
|
||||||
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
|
originalVisionariesGuildId = Config.instance.visionariesGuildId ?? undefined;
|
||||||
|
originalVisionariesGuildVisionaryRoleId = Config.instance.visionariesGuildVisionaryRoleId ?? undefined;
|
||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await harness.shutdown();
|
await harness.shutdown();
|
||||||
Config.instance.visionariesGuildId = originalVisionariesGuildId;
|
Config.instance.visionariesGuildId = originalVisionariesGuildId;
|
||||||
|
Config.instance.visionariesGuildVisionaryRoleId = originalVisionariesGuildVisionaryRoleId;
|
||||||
});
|
});
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await harness.resetData();
|
await harness.resetData();
|
||||||
const owner = await createTestAccount(harness);
|
const owner = await createTestAccount(harness);
|
||||||
const visionariesGuild = await createGuild(harness, owner.token, 'Visionaries Webhook Test Guild');
|
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.visionariesGuildId = visionariesGuild.id;
|
||||||
|
Config.instance.visionariesGuildVisionaryRoleId = visionaryRole.id;
|
||||||
});
|
});
|
||||||
describe('Premium stacking - consecutive grants extending duration', () => {
|
describe('Premium stacking - consecutive grants extending duration', () => {
|
||||||
test('stacks multiple monthly subscriptions end-to-end', async () => {
|
test('stacks multiple monthly subscriptions end-to-end', async () => {
|
||||||
@@ -366,6 +371,8 @@ describe('Stripe Webhook Edge Cases', () => {
|
|||||||
.execute();
|
.execute();
|
||||||
expect(receiverAfter.premium_type).toBe(UserPremiumTypes.LIFETIME);
|
expect(receiverAfter.premium_type).toBe(UserPremiumTypes.LIFETIME);
|
||||||
expect(receiverAfter.premium_lifetime_sequence).toBe(0);
|
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 () => {
|
test('redeeming 1-month gift code grants subscription premium', async () => {
|
||||||
const gifter = await createTestAccount(harness);
|
const gifter = await createTestAccount(harness);
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ export interface MasterConfig {
|
|||||||
self_hosted: boolean;
|
self_hosted: boolean;
|
||||||
auto_join_invite_code?: string;
|
auto_join_invite_code?: string;
|
||||||
visionaries_guild_id?: string;
|
visionaries_guild_id?: string;
|
||||||
|
visionaries_guild_visionary_role_id?: string;
|
||||||
branding: InstanceBrandingConfig;
|
branding: InstanceBrandingConfig;
|
||||||
setup: {
|
setup: {
|
||||||
configured: boolean;
|
configured: boolean;
|
||||||
|
|||||||
@@ -321,6 +321,7 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
|||||||
FLUXER_SELF_HOSTED: {path: ['instance', 'self_hosted'], parse: parseEnvValue},
|
FLUXER_SELF_HOSTED: {path: ['instance', 'self_hosted'], parse: parseEnvValue},
|
||||||
FLUXER_AUTO_JOIN_INVITE_CODE: {path: ['instance', 'auto_join_invite_code']},
|
FLUXER_AUTO_JOIN_INVITE_CODE: {path: ['instance', 'auto_join_invite_code']},
|
||||||
FLUXER_VISIONARIES_GUILD_ID: {path: ['instance', 'visionaries_guild_id']},
|
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_PRODUCT_NAME: {path: ['instance', 'branding', 'product_name']},
|
||||||
FLUXER_APP_ICON_URL: {path: ['instance', 'branding', 'icon_url']},
|
FLUXER_APP_ICON_URL: {path: ['instance', 'branding', 'icon_url']},
|
||||||
FLUXER_APP_SYMBOL_URL: {path: ['instance', 'branding', 'symbol_url']},
|
FLUXER_APP_SYMBOL_URL: {path: ['instance', 'branding', 'symbol_url']},
|
||||||
|
|||||||
Reference in New Issue
Block a user