diff --git a/fluxer_api/src/rpc/RpcService.ts b/fluxer_api/src/rpc/RpcService.ts index aa52e19b..d394d03f 100644 --- a/fluxer_api/src/rpc/RpcService.ts +++ b/fluxer_api/src/rpc/RpcService.ts @@ -43,7 +43,6 @@ import { } from '~/Constants'; import {mapChannelToResponse, mapMessageToResponse} from '~/channel/ChannelModel'; import type {IChannelRepository} from '~/channel/IChannelRepository'; -import type {UserRow} from '~/database/types/UserTypes'; import {RateLimitError, UnauthorizedError, UnknownGuildError} from '~/Errors'; import {mapFavoriteMemeToResponse} from '~/favorite_meme/FavoriteMemeModel'; import type {IFavoriteMemeRepository} from '~/favorite_meme/IFavoriteMemeRepository'; @@ -88,7 +87,7 @@ import type {RpcRequest, RpcResponse, RpcResponseGuildData, RpcResponseSessionDa import type {IUserRepository} from '~/user/IUserRepository'; import {CustomStatusValidator} from '~/user/services/CustomStatusValidator'; import {getCachedUserPartialResponse} from '~/user/UserCacheHelpers'; -import {mapExpiredPremiumFields, shouldStripExpiredPremium} from '~/user/UserHelpers'; +import {createPremiumClearPatch, shouldStripExpiredPremium} from '~/user/UserHelpers'; import { mapRelationshipToResponse, mapUserGuildSettingsToResponse, @@ -574,10 +573,7 @@ export class RpcService { if (needsPremiumStrip) { try { - const strippedUser = await this.userRepository.patchUpsert( - user.id, - mapExpiredPremiumFields(() => null) as Partial, - ); + const strippedUser = await this.userRepository.patchUpsert(user.id, createPremiumClearPatch()); if (strippedUser) { user = strippedUser; userData.user = strippedUser; diff --git a/fluxer_api/src/user/UserHelpers.ts b/fluxer_api/src/user/UserHelpers.ts index 685ebe66..ab6a6200 100644 --- a/fluxer_api/src/user/UserHelpers.ts +++ b/fluxer_api/src/user/UserHelpers.ts @@ -19,6 +19,7 @@ import {Config} from '~/Config'; import {UserFlags} from '~/Constants'; +import type {UserRow} from '~/database/types/UserTypes'; interface PremiumCheckable { premiumType: number | null; @@ -81,3 +82,7 @@ export function mapExpiredPremiumFields(mapper: (field: PremiumClearField) => } return result; } + +export function createPremiumClearPatch(): Partial { + return mapExpiredPremiumFields(() => null) as Partial; +} diff --git a/fluxer_api/src/user/controllers/UserAccountController.ts b/fluxer_api/src/user/controllers/UserAccountController.ts index 2d9743fe..f8a7aba3 100644 --- a/fluxer_api/src/user/controllers/UserAccountController.ts +++ b/fluxer_api/src/user/controllers/UserAccountController.ts @@ -47,6 +47,7 @@ import { z, } from '~/Schema'; import {getCachedUserPartialResponse, mapUserToPartialResponseWithCache} from '~/user/UserCacheHelpers'; +import {createPremiumClearPatch, shouldStripExpiredPremium} from '~/user/UserHelpers'; import { mapGuildMemberToProfileResponse, mapUserGuildSettingsToResponse, @@ -406,7 +407,32 @@ export const UserAccountController = (app: HonoApp) => { requestCache: ctx.get('requestCache'), }); - const userProfile = mapUserToProfileResponse(profile.user); + let profileUser = profile.user; + let premiumType = profile.premiumType; + let premiumSince = profile.premiumSince; + let premiumLifetimeSequence = profile.premiumLifetimeSequence; + + if (shouldStripExpiredPremium(profileUser)) { + try { + const sanitizedUser = await ctx + .get('userRepository') + .patchUpsert(profileUser.id, createPremiumClearPatch(), profileUser.toRow()); + if (sanitizedUser) { + profileUser = sanitizedUser; + profile.user = sanitizedUser; + premiumType = undefined; + premiumSince = undefined; + premiumLifetimeSequence = undefined; + } + } catch (error) { + Logger.warn( + {userId: profileUser.id.toString(), error}, + 'Failed to sanitize expired premium fields before returning profile', + ); + } + } + + const userProfile = mapUserToProfileResponse(profileUser); const guildMemberProfile = mapGuildMemberToProfileResponse(profile.guildMemberDomain ?? null); const mutualFriends = profile.mutualFriends @@ -423,16 +449,16 @@ export const UserAccountController = (app: HonoApp) => { return ctx.json({ user: await mapUserToPartialResponseWithCache({ - user: profile.user, + user: profileUser, userCacheService: ctx.get('userCacheService'), requestCache: ctx.get('requestCache'), }), user_profile: userProfile, guild_member: profile.guildMember ?? undefined, guild_member_profile: guildMemberProfile ?? undefined, - premium_type: profile.premiumType, - premium_since: profile.premiumSince?.toISOString(), - premium_lifetime_sequence: profile.premiumLifetimeSequence, + premium_type: premiumType, + premium_since: premiumSince?.toISOString(), + premium_lifetime_sequence: premiumLifetimeSequence, mutual_friends: mutualFriends, mutual_guilds: profile.mutualGuilds, }); diff --git a/fluxer_api/src/user/repositories/account/crud/UserDataRepository.ts b/fluxer_api/src/user/repositories/account/crud/UserDataRepository.ts index e00317dc..4cab8dcb 100644 --- a/fluxer_api/src/user/repositories/account/crud/UserDataRepository.ts +++ b/fluxer_api/src/user/repositories/account/crud/UserDataRepository.ts @@ -20,10 +20,8 @@ import {createUserID, type UserID} from '~/BrandedTypes'; import {buildPatchFromData, Db, executeVersionedUpdate, fetchMany, fetchOne} from '~/database/Cassandra'; import {EMPTY_USER_ROW, USER_COLUMNS, type UserRow} from '~/database/CassandraTypes'; -import {Logger} from '~/Logger'; import {User} from '~/Models'; import {Users} from '~/Tables'; -import {shouldStripExpiredPremium} from '~/user/UserHelpers'; const FETCH_USERS_BY_IDS_CQL = Users.selectCql({ where: Users.where.in('user_id', 'user_ids'), @@ -75,21 +73,7 @@ export class UserDataRepository { return null; } - const user = new User(userRow); - - if (shouldStripExpiredPremium(user)) { - try { - await this.readRepairExpiredPremium(user); - const repairedRow = await fetchOne(FETCH_USER_BY_ID_CQL, { - user_id: userId, - }); - return repairedRow ? new User(repairedRow) : null; - } catch (error) { - Logger.warn({userId: user.id.toString(), error}, 'Failed to repair expired premium fields while reading user'); - } - } - - return user; + return new User(userRow); } async findUniqueAssert(userId: UserID): Promise { @@ -168,18 +152,4 @@ export class UserDataRepository { await this.patchUser(userId, patch); } - - private async readRepairExpiredPremium(user: User): Promise { - await this.patchUser(user.id, createPremiumClearPatch()); - } -} - -function createPremiumClearPatch(): UserPatch { - return { - premium_type: Db.clear(), - premium_since: Db.clear(), - premium_until: Db.clear(), - premium_will_cancel: Db.clear(), - premium_billing_cycle: Db.clear(), - }; }