Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions infrastructure/mongodb/mongo-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: false,
},
fileVersioning: {
enabled: false,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -111,6 +114,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: false,
},
fileVersioning: {
enabled: false,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -164,6 +170,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -218,6 +227,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: true,
Expand Down Expand Up @@ -272,6 +284,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -326,6 +341,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: false,
},
fileVersioning: {
enabled: false,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -380,6 +398,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: false,
Expand Down Expand Up @@ -434,6 +455,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: true,
Expand Down Expand Up @@ -488,6 +512,9 @@ db.tiers.insertMany([
restrictedItemsSharing: {
enabled: true,
},
fileVersioning: {
enabled: true,
},
},
meet: {
enabled: true,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@internxt/eslint-config-internxt": "^1.0.9",
"@internxt/prettier-config": "^1.0.2",
"@types/chance": "^1.1.6",
"@types/deepmerge": "^2.2.3",
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.10.2",
Expand All @@ -49,6 +50,7 @@
"@fastify/rate-limit": "^10.1.1",
"axios": "^1.12.0",
"dayjs": "^1.11.13",
"deepmerge": "^4.3.1",
"dotenv": "^16.4.5",
"fastify": "^5.3.2",
"ioredis": "^5.4.1",
Expand Down
14 changes: 9 additions & 5 deletions src/controller/gateway.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UsersService } from '../services/users.service';
import { NotFoundError } from '../errors/Errors';
import Logger from '../Logger';
import CacheService from '../services/cache.service';
import { Service } from '../core/users/Tier';
import { DriveFeatures, Service } from '../core/users/Tier';
import { User } from '../core/users/User';
import { UserFeaturesOverridesService } from '../services/userFeaturesOverride.service';
import { setupAuth } from '../plugins/auth';
Expand Down Expand Up @@ -35,7 +35,7 @@ export function gatewayController({
},
});

fastify.post<{ Body: { feature: Service; userUuid: string } }>(
fastify.post<{ Body: { feature: Service; userUuid: string; driveFeature?: keyof DriveFeatures } }>(
'/activate',
{
schema: {
Expand All @@ -45,18 +45,22 @@ export function gatewayController({
properties: {
feature: {
type: 'string',
enum: [Service.Antivirus, Service.Backups, Service.Cleaner, Service.Cli, Service.rClone],
enum: [Service.Antivirus, Service.Backups, Service.Cleaner, Service.Cli, Service.rClone, Service.Drive],
},
userUuid: {
type: 'string',
},
driveFeature: {
type: 'string',
enum: ['fileVersioning', 'passwordProtectedSharing', 'restrictedItemsSharing'],
},
},
},
},
},
async (request, response) => {
let user: User;
const { feature, userUuid } = request.body;
const { feature, userUuid, driveFeature } = request.body;

try {
user = await usersService.findUserByUuid(userUuid);
Expand All @@ -65,7 +69,7 @@ export function gatewayController({
throw new NotFoundError(`User with uuid ${userUuid} was not found`);
}

await userFeaturesOverridesService.upsertCustomUserFeatures(user, feature);
await userFeaturesOverridesService.upsertCustomUserFeatures(user, feature, driveFeature);
await cacheService.clearUserTier(userUuid);

return response.status(204).send();
Expand Down
6 changes: 6 additions & 0 deletions src/core/users/MongoDBTiersRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Tier } from './Tier';
export interface TiersRepository {
findByProductId(where: Partial<Tier>): Promise<Tier | null>;
findByTierId(tierId: Tier['id']): Promise<Tier | null>;
getAll(): Promise<Tier[]>;
}

function toDomain(tier: WithId<Omit<Tier, 'id'>>): Tier {
Expand All @@ -22,6 +23,11 @@ export class MongoDBTiersRepository implements TiersRepository {
this.collection = mongo.db('payments').collection<Tier>('tiers');
}

async getAll(): Promise<Tier[]> {
const tiers = await this.collection.find().toArray();
return tiers.map(toDomain);
}

async findByProductId(where: Partial<Tier>): Promise<Tier | null> {
const tier = await this.collection.findOne(where);
return tier ? toDomain(tier) : null;
Expand Down
11 changes: 10 additions & 1 deletion src/core/users/MongoDBUserFeatureOverridesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export class MongoDBUserFeatureOverridesRepository implements UserFeatureOverrid
}

async upsert(userFeatureOverrides: Omit<UserFeatureOverrides, 'id'>): Promise<void> {
const newFeatures = userFeatureOverrides.featuresPerService || {};

const mergedServices: Record<string, unknown> = {};
for (const [serviceKey, serviceValue] of Object.entries(newFeatures)) {
mergedServices[serviceKey] = {
$mergeObjects: [{ $ifNull: [`$featuresPerService.${serviceKey}`, {}] }, serviceValue],
};
}

await this.collection.updateOne(
{
userId: userFeatureOverrides.userId,
Expand All @@ -34,7 +43,7 @@ export class MongoDBUserFeatureOverridesRepository implements UserFeatureOverrid
{
$set: {
featuresPerService: {
$mergeObjects: [{ $ifNull: ['$featuresPerService', {}] }, userFeatureOverrides.featuresPerService || {}],
$mergeObjects: [{ $ifNull: ['$featuresPerService', {}] }, mergedServices],
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions src/core/users/Tier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface DriveFeatures {
restrictedItemsSharing: {
enabled: boolean;
};
fileVersioning: {
enabled: boolean;
};
}

interface MeetFeatures {
Expand Down
21 changes: 16 additions & 5 deletions src/core/users/UserFeatureOverrides.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { Service } from './Tier';
import { User } from './User';

interface DriveFeatureOverride {
enabled: boolean;
passwordProtectedSharing?: { enabled: boolean };
restrictedItemsSharing?: { enabled: boolean };
fileVersioning?: { enabled: boolean };
}

type ServiceFeatureOverride = { enabled: boolean };

export interface UserFeatureOverrides {
userId: User['id'];
featuresPerService: Partial<{
[key in Service]: {
enabled: boolean;
};
}>;
featuresPerService: Partial<
{
[K in Exclude<Service, Service.Drive>]: ServiceFeatureOverride;
} & {
[Service.Drive]: DriveFeatureOverride;
}
>;
}
6 changes: 5 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ const start = async (mongoTestClient?: MongoClient): Promise<FastifyInstance> =>
licenseCodesRepository,
});
const objectStorageService = new ObjectStorageService(paymentService, envVariablesConfig, axios);
const userFeaturesOverridesService = new UserFeaturesOverridesService(usersService, userFeatureOverridesRepository);
const userFeaturesOverridesService = new UserFeaturesOverridesService(
usersService,
userFeatureOverridesRepository,
tiersService,
);
const productsService = new ProductsService(tiersService, usersService, userFeaturesOverridesService);
const healthService = new HealthService(mongoClient, cacheService);

Expand Down
84 changes: 30 additions & 54 deletions src/services/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,76 +107,52 @@ export class ProductsService {
private mergeTiers(individualTier: Tier, businessTier: Tier): Tier {
const individualEnabledCount = this.countEnabledProducts(individualTier);
const businessEnabledCount = this.countEnabledProducts(businessTier);

const tierWithMostProducts = businessEnabledCount > individualEnabledCount ? businessTier : individualTier;

const individual = individualTier.featuresPerService;
const business = businessTier.featuresPerService;

const baseMerge = Object.values(Service).reduce(
(acc, service) => {
acc[service] = {
...individual[service],
...business[service],
enabled: individual[service].enabled || business[service].enabled,
};
return acc;
},
{} as Record<Service, any>,
);

const mergedFeatures = {
...baseMerge,
[Service.Drive]: {
enabled:
individualTier.featuresPerService[Service.Drive].enabled ||
businessTier.featuresPerService[Service.Drive].enabled,
maxSpaceBytes: individualTier.featuresPerService[Service.Drive].maxSpaceBytes,
workspaces: businessTier.featuresPerService[Service.Drive].workspaces,
...baseMerge[Service.Drive],
maxSpaceBytes: individual[Service.Drive].maxSpaceBytes,
workspaces: business[Service.Drive].workspaces,
passwordProtectedSharing: {
enabled:
individualTier.featuresPerService[Service.Drive].passwordProtectedSharing.enabled ||
businessTier.featuresPerService[Service.Drive].passwordProtectedSharing.enabled,
individual[Service.Drive].passwordProtectedSharing.enabled ||
business[Service.Drive].passwordProtectedSharing.enabled,
},
restrictedItemsSharing: {
enabled:
individualTier.featuresPerService[Service.Drive].restrictedItemsSharing.enabled ||
businessTier.featuresPerService[Service.Drive].restrictedItemsSharing.enabled,
individual[Service.Drive].restrictedItemsSharing.enabled ||
business[Service.Drive].restrictedItemsSharing.enabled,
},
fileVersioning: {
enabled: individual[Service.Drive].fileVersioning.enabled || business[Service.Drive].fileVersioning.enabled,
},
},
[Service.Backups]: {
enabled:
individualTier.featuresPerService[Service.Backups].enabled ||
businessTier.featuresPerService[Service.Backups].enabled,
},
[Service.Antivirus]: {
enabled:
individualTier.featuresPerService[Service.Antivirus].enabled ||
businessTier.featuresPerService[Service.Antivirus].enabled,
},
[Service.Meet]: {
enabled:
individualTier.featuresPerService[Service.Meet].enabled ||
businessTier.featuresPerService[Service.Meet].enabled,
paxPerCall: Math.max(
individualTier.featuresPerService[Service.Meet].paxPerCall,
businessTier.featuresPerService[Service.Meet].paxPerCall,
),
...baseMerge[Service.Meet],
paxPerCall: Math.max(individual[Service.Meet].paxPerCall, business[Service.Meet].paxPerCall),
},
[Service.Mail]: {
enabled:
individualTier.featuresPerService[Service.Mail].enabled ||
businessTier.featuresPerService[Service.Mail].enabled,
addressesPerUser: Math.max(
individualTier.featuresPerService[Service.Mail].addressesPerUser,
businessTier.featuresPerService[Service.Mail].addressesPerUser,
),
...baseMerge[Service.Mail],
addressesPerUser: Math.max(individual[Service.Mail].addressesPerUser, business[Service.Mail].addressesPerUser),
},
[Service.Vpn]: tierWithMostProducts.featuresPerService[Service.Vpn],
[Service.Cleaner]: {
enabled:
individualTier.featuresPerService[Service.Cleaner].enabled ||
businessTier.featuresPerService[Service.Cleaner].enabled,
},
[Service.darkMonitor]: {
enabled:
individualTier.featuresPerService[Service.darkMonitor].enabled ||
businessTier.featuresPerService[Service.darkMonitor].enabled,
},
[Service.Cli]: {
enabled:
individualTier.featuresPerService[Service.Cli].enabled ||
businessTier.featuresPerService[Service.Cli].enabled,
},
[Service.rClone]: {
enabled:
individualTier.featuresPerService[Service.rClone].enabled ||
businessTier.featuresPerService[Service.rClone].enabled,
},
};

return {
Expand Down
Loading
Loading