diff --git a/packages/api/src/entity/feature.ts b/packages/api/src/entity/feature.ts index 77ca572e7..cf7603478 100644 --- a/packages/api/src/entity/feature.ts +++ b/packages/api/src/entity/feature.ts @@ -5,11 +5,13 @@ import { JoinColumn, ManyToOne, PrimaryGeneratedColumn, + Unique, UpdateDateColumn, } from 'typeorm' import { User } from './user' @Entity({ name: 'features' }) +@Unique(['user', 'name']) export class Feature { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/resolvers/features/index.ts b/packages/api/src/resolvers/features/index.ts index 88bd99bfb..73f9036b0 100644 --- a/packages/api/src/resolvers/features/index.ts +++ b/packages/api/src/resolvers/features/index.ts @@ -1,4 +1,3 @@ -import { authorized } from '../../utils/helpers' import { MutationOptInFeatureArgs, OptInFeatureError, @@ -10,6 +9,7 @@ import { optInFeature, signFeatureToken, } from '../../services/features' +import { authorized } from '../../utils/helpers' export const optInFeatureResolver = authorized< OptInFeatureSuccess, @@ -33,19 +33,19 @@ export const optInFeatureResolver = authorized< } } - const optIn = await optInFeature(featureName, claims.uid) - if (!optIn) { + const optedInFeature = await optInFeature(featureName, claims.uid) + if (!optedInFeature) { return { errorCodes: [OptInFeatureErrorCode.NotFound], } } - log.info('Opted in to a feature', optIn) + log.info('Opted in to a feature', optedInFeature) - const token = signFeatureToken(optIn, claims.uid) + const token = signFeatureToken(optedInFeature, claims.uid) return { feature: { - ...optIn, + ...optedInFeature, token, }, } diff --git a/packages/api/src/services/features.ts b/packages/api/src/services/features.ts index c6ca41c7f..7d3158d70 100644 --- a/packages/api/src/services/features.ts +++ b/packages/api/src/services/features.ts @@ -42,7 +42,7 @@ const optInUltraRealisticVoice = async (uid: string): Promise => { const MAX_USERS = 1500 // opt in to feature for the first 1500 users - const newFeatures = (await AppDataSource.query( + const optedInFeatures = (await AppDataSource.query( `insert into omnivore.features (user_id, name, granted_at) select $1, $2, $3 from omnivore.features where name = $2 and granted_at is not null @@ -54,19 +54,30 @@ const optInUltraRealisticVoice = async (uid: string): Promise => { )) as Feature[] // if no new features were created then user has exceeded max users - if (newFeatures.length === 0) { + if (optedInFeatures.length === 0) { logger.info('exceeded max users') - return getRepository(Feature).save({ + // create/update an opt-in record with null grantedAt + const optInRecord = { user: { id: uid }, name: FeatureName.UltraRealisticVoice, grantedAt: null, - }) + } + const result = await getRepository(Feature).upsert(optInRecord, [ + 'user', + 'name', + ]) + if (result.generatedMaps.length === 0) { + throw new Error('failed to update opt-in record') + } + + logger.info('opt-in record updated', result.generatedMaps) + return { ...optInRecord, ...(result.generatedMaps[0] as Feature) } } - logger.info('opted in', { uid, feature: newFeatures[0] }) + logger.info('opted in', { uid, feature: optedInFeatures[0] }) - return newFeatures[0] + return optedInFeatures[0] } export const signFeatureToken = ( @@ -76,7 +87,7 @@ export const signFeatureToken = ( }, userId: string ): string => { - logger.info('signing feature token', { grantedAt: feature.grantedAt }) + logger.info('signing feature token', feature) return jwt.sign( {