diff --git a/src/app.module.ts b/src/app.module.ts index b4b6ffab..bae06455 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -27,6 +27,7 @@ import { FleaMarketModule } from './fleaMarket/fleaMarket.module'; import { VotingModule } from './voting/voting.module'; import { BullModule } from '@nestjs/bullmq'; import { DailyTasksModule } from './dailyTasks/dailyTasks.module'; +import {BoxModule} from "./box/box.module"; // Set up database connection const mongoUser = envVars.MONGO_USERNAME; @@ -74,15 +75,17 @@ const redisPort = parseInt(envVars.REDIS_PORT); VotingModule, ClanInventoryModule, - + ProfileModule, SiteModule, - + AuthModule, AuthorizationModule, GameAnalyticsModule, - RequestHelperModule + RequestHelperModule, + + BoxModule ], controllers: [AppController], providers: [ @@ -91,4 +94,4 @@ const redisPort = parseInt(envVars.REDIS_PORT); ], }) export class AppModule { -} \ No newline at end of file +} diff --git a/src/box/box.controller.ts b/src/box/box.controller.ts new file mode 100644 index 00000000..b69ea171 --- /dev/null +++ b/src/box/box.controller.ts @@ -0,0 +1,12 @@ +import { Body, Controller, Get, Param, Post, Put } from "@nestjs/common"; +import {BoxService} from "./box.service"; + +@Controller('box') +export class BoxController { + public constructor( + private readonly service: BoxService, + ) { + } + + +} \ No newline at end of file diff --git a/src/box/box.module.ts b/src/box/box.module.ts new file mode 100644 index 00000000..5ce5d481 --- /dev/null +++ b/src/box/box.module.ts @@ -0,0 +1,26 @@ +import {Module} from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import {ModelName} from "../common/enum/modelName.enum"; +import {BoxSchema} from "./schemas/box.schema"; +import {GroupAdminSchema} from "./schemas/groupAdmin.schema"; +import {BoxController} from "./box.controller"; +import {BoxService} from "./box.service"; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: ModelName.BOX, schema: BoxSchema }, + { name: ModelName.GROUP_ADMIN, schema: GroupAdminSchema } + ]) + ], + controllers: [ + BoxController + ], + providers: [ + BoxService + ], + exports: [ + BoxService + ] +}) +export class BoxModule {} diff --git a/src/box/box.service.ts b/src/box/box.service.ts new file mode 100644 index 00000000..481366c1 --- /dev/null +++ b/src/box/box.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model } from "mongoose"; +import {Box, publicReferences} from "./schemas/box.schema"; +import BasicService from "../common/service/basicService/BasicService"; +import {BoxReference} from "./enum/BoxReference.enum"; + +@Injectable() +export class BoxService { + public constructor( + @InjectModel(Box.name) public readonly model: Model, + ){ + this.refsInModel = publicReferences; + this.basicService = new BasicService(model); + } + + public readonly refsInModel: BoxReference[]; + private readonly basicService: BasicService; + +} diff --git a/src/box/dto/box.dto.ts b/src/box/dto/box.dto.ts new file mode 100644 index 00000000..7cdd5936 --- /dev/null +++ b/src/box/dto/box.dto.ts @@ -0,0 +1,61 @@ +import {Expose, Type} from "class-transformer"; +import {ExtractField} from "../../common/decorator/response/ExtractField"; +import {SessionStage} from "../enum/SessionStage.enum"; +import {ObjectId} from "mongoose"; +import {Tester} from "../schemas/tester.schema"; +import {DailyTask} from "../../dailyTasks/dailyTasks.schema"; + +export class BoxDto { + @ExtractField() + @Expose() + _id:string; + + @Expose() + adminPassword: string; + + @Expose() + sessionStage: SessionStage; + + @Expose() + testersSharedPassword: string | null; + + @Expose() + boxRemovalTime: number; + + @Expose() + sessionResetTime: number; + + + @Expose() + adminProfile_id: ObjectId; + + @Expose() + adminPlayer_id: ObjectId; + + @Expose() + clan_ids: ObjectId[]; + + @Expose() + soulHome_ids: ObjectId[]; + + @Expose() + room_ids: ObjectId[]; + + @Expose() + stock_ids: ObjectId[]; + + + @Expose() + chat_id: ObjectId; + + @Expose() + @Type(() => Tester) + testers: Tester[]; + + @Expose() + accountClaimersIds: string[]; + + @Expose() + @Type(() => DailyTask) + dailyTasks: DailyTask[]; +} \ No newline at end of file diff --git a/src/box/dto/createBox.dto.ts b/src/box/dto/createBox.dto.ts new file mode 100644 index 00000000..0c788903 --- /dev/null +++ b/src/box/dto/createBox.dto.ts @@ -0,0 +1,77 @@ +import { + IsArray, + IsEnum, + IsMongoId, + IsNumber, IsOptional, + IsString, + ValidateNested +} from "class-validator"; +import {SessionStage} from "../enum/SessionStage.enum"; +import { ObjectId } from "mongoose"; +import { Tester } from "../schemas/tester.schema"; +import {Type} from "class-transformer"; +import {DailyTask} from "../../dailyTasks/dailyTasks.schema"; + + +export class CreateBoxDto { + @IsString() + adminPassword: string; + + @IsOptional() + @IsEnum(SessionStage) + sessionStage?: SessionStage; + + @IsOptional() + @IsString() + testersSharedPassword?: string | null; + + @IsNumber() + boxRemovalTime: number; + + @IsNumber() + sessionResetTime: number; + + + @IsMongoId() + adminProfile_id: ObjectId; + + @IsMongoId() + adminPlayer_id: ObjectId; + + @IsArray() + @IsMongoId({ each: true }) + clan_ids: ObjectId[]; + + @IsArray() + @IsMongoId({ each: true }) + soulHome_ids: ObjectId[]; + + @IsArray() + @IsMongoId({ each: true }) + room_ids: ObjectId[]; + + @IsArray() + @IsMongoId({ each: true }) + stock_ids: ObjectId[]; + + + @IsMongoId() + chat_id: ObjectId; + + @IsOptional() + @IsArray() + @ValidateNested() + @Type(() => Tester) + testers?: Tester[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + accountClaimersIds?: string[]; + + @IsOptional() + @IsArray() + @ValidateNested() + @Type(() => DailyTask) + dailyTasks?: DailyTask[]; +} \ No newline at end of file diff --git a/src/box/dto/updateBox.dto.ts b/src/box/dto/updateBox.dto.ts new file mode 100644 index 00000000..4df8d75f --- /dev/null +++ b/src/box/dto/updateBox.dto.ts @@ -0,0 +1,80 @@ +import {IsArray, IsEnum, IsMongoId, IsNumber, IsOptional, IsString, ValidateNested} from "class-validator"; +import {SessionStage} from "../enum/SessionStage.enum"; +import {ObjectId} from "mongoose"; +import {Type} from "class-transformer"; +import {Tester} from "../schemas/tester.schema"; +import {DailyTask} from "../../dailyTasks/dailyTasks.schema"; + +export class UpdateBoxDto { + @IsMongoId() + _id: string; + + @IsOptional() + @IsString() + adminPassword?: string; + + @IsOptional() + @IsEnum(SessionStage) + sessionStage?: SessionStage; + + @IsOptional() + @IsString() + testersSharedPassword?: string | null; + + @IsOptional() + @IsNumber() + boxRemovalTime?: number; + + @IsOptional() + @IsNumber() + sessionResetTime?: number; + + @IsOptional() + @IsMongoId() + adminProfile_id?: ObjectId; + + @IsOptional() + @IsMongoId() + adminPlayer_id?: ObjectId; + + @IsOptional() + @IsArray() + @IsMongoId({ each: true }) + clan_ids?: ObjectId[]; + + @IsOptional() + @IsArray() + @IsMongoId({ each: true }) + soulHome_ids?: ObjectId[]; + + @IsOptional() + @IsArray() + @IsMongoId({ each: true }) + room_ids?: ObjectId[]; + + @IsOptional() + @IsArray() + @IsMongoId({ each: true }) + stock_ids?: ObjectId[]; + + @IsOptional() + @IsMongoId() + chat_id?: ObjectId; + + @IsOptional() + @IsArray() + @ValidateNested() + @Type(() => Tester) + testers?: Tester[]; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + accountClaimersIds?: string[]; + + @IsOptional() + @IsArray() + @ValidateNested() + @Type(() => DailyTask) + dailyTasks?: DailyTask[]; +} diff --git a/src/box/enum/BoxReference.enum.ts b/src/box/enum/BoxReference.enum.ts new file mode 100644 index 00000000..acf4ce83 --- /dev/null +++ b/src/box/enum/BoxReference.enum.ts @@ -0,0 +1,16 @@ +/** + * All the references of the box collection + */ +export enum BoxReference { + ADMIN_PROFILE = 'AdminProfile', + ADMIN_PLAYER = 'AdminPlayer', + GROUP_ADMIN = 'GroupAdmin', + CLANS = 'Clans', + SOUL_HOMES = 'SoulHomes', + ROOMS = 'Rooms', + STOCKS = 'Stocks', + CHAT = 'CHAT', + TESTER_PROFILES = 'TesterProfiles', + TESTER_PLAYERS = 'TesterPlayers', + DAILY_TASKS = 'DailyTasks' +} diff --git a/src/box/enum/SessionStage.enum.ts b/src/box/enum/SessionStage.enum.ts new file mode 100644 index 00000000..3c28c285 --- /dev/null +++ b/src/box/enum/SessionStage.enum.ts @@ -0,0 +1,17 @@ +/** + * Stage of testing session + */ +export enum SessionStage { + /** + * Stage 2. Preparing for testing + */ + PREPARING = 'Preparing', + /** + * Stage 3: Testing session is started + */ + TESTING = 'Testing', + /** + * Stage 4: Testing session is ended + */ + END = 'End' +} diff --git a/src/box/schemas/box.schema.ts b/src/box/schemas/box.schema.ts new file mode 100644 index 00000000..528b48ec --- /dev/null +++ b/src/box/schemas/box.schema.ts @@ -0,0 +1,187 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import {HydratedDocument} from "mongoose"; +import {ModelName} from "../../common/enum/modelName.enum"; +import {ObjectId} from "mongodb"; +import {DailyTask} from "../../dailyTasks/dailyTasks.schema"; +import {SessionStage} from "../enum/SessionStage.enum"; +import {ExtractField} from "../../common/decorator/response/ExtractField"; +import {Tester} from "./tester.schema"; +import {BoxReference} from "../enum/BoxReference.enum"; + +export type BoxDocument = HydratedDocument; + +/** + * The box collection contain metadata, which can be used to determine the resources corresponding to different testing sessions + */ +@Schema({ toJSON: { virtuals: true }, toObject: { virtuals: true }, timestamps: true }) +export class Box { + /** + * Group admin password, which is also used as an identifier of whom box it is. not hashed + */ + @Prop({ type: String, required: true, unique: true }) + adminPassword: string; + + /** + * On which stage the testing session is + */ + @Prop({ type: String, enum: SessionStage, required: true, default: SessionStage.PREPARING }) + sessionStage: SessionStage; + + /** + * Password for testers to claim their accounts in the box. Not hashed. + * It used as a proof that the claimer is eligible to access the particular box + */ + @Prop({ type: String, default: null }) + testersSharedPassword: string | null; + + /** + * When the box should be automatically completely removed, timestamp + */ + @Prop({ type: Number, required: true }) + boxRemovalTime: number; + + /** + * When the testing session be automatically reset, timestamp + */ + @Prop({ type: Number, required: true }) + sessionResetTime: number; + + + /** + * Group admin profile _id + */ + @Prop({ type: ObjectId, required: true }) + adminProfile_id: ObjectId; + + /** + * Group admin player _id + */ + @Prop({ type: ObjectId, required: true }) + adminPlayer_id: ObjectId; + + + /** + * All clans that are related to the box + */ + @Prop({ type: Array, required: true }) + clan_ids: ObjectId[]; + + /** + * All soul homes' _ids that are related to the box + */ + @Prop({ type: Array, required: true }) + soulHome_ids: ObjectId[]; + + /** + * All rooms' _ids that are related to the box + */ + @Prop({ type: Array, required: true }) + room_ids: ObjectId[]; + + /** + * All stocks' _ids that are related to the box + */ + @Prop({ type: Array, required: true }) + stock_ids: ObjectId[]; + + + /** + * _id of the common chat related to the box + */ + @Prop({ type: ObjectId, required: true }) + chat_id: ObjectId; + + + /** + * Testers that are related to the box and should have access to the box resources + */ + @Prop({ type: Array, required: true, default: [] }) + testers: Tester[]; + + /** + * Array of unique identifiers, which is used to identify the device sending the request to claim the profile. + * Each identifier is unique within the box + */ + @Prop({ type: Array, required: true, default: [] }) + accountClaimersIds: string[]; + + + /** + * array of predefined by the group admin tasks + */ + @Prop({ type: Array, required: true, default: [] }) + dailyTasks: DailyTask[]; + + + @ExtractField() + _id: ObjectId; +} + +export const BoxSchema = SchemaFactory.createForClass(Box); +BoxSchema.set('collection', ModelName.BOX); +BoxSchema.virtual(BoxReference.ADMIN_PROFILE, { + ref: ModelName.PROFILE, + localField : "adminProfile_id", + foreignField :"_id", + justOne: true +}); +BoxSchema.virtual(BoxReference.ADMIN_PLAYER, { + ref: ModelName.PLAYER, + localField : "adminPlayer_id", + foreignField :"_id", + justOne: true +}); + +BoxSchema.virtual(BoxReference.GROUP_ADMIN, { + ref: ModelName.GROUP_ADMIN, + localField : "adminPassword", + foreignField :"password", + justOne: true +}); + +BoxSchema.virtual(BoxReference.CLANS, { + ref: ModelName.CLAN, + localField : "clan_ids", + foreignField :"_id" +}); +BoxSchema.virtual(BoxReference.SOUL_HOMES, { + ref: ModelName.SOULHOME, + localField : "soulHome_ids", + foreignField :"_id" +}); +BoxSchema.virtual(BoxReference.ROOMS, { + ref: ModelName.ROOM, + localField : "room_ids", + foreignField :"_id" +}); +BoxSchema.virtual(BoxReference.STOCKS, { + ref: ModelName.STOCK, + localField : "stock_ids", + foreignField :"_id" +}); + +BoxSchema.virtual(BoxReference.CHAT, { + ref: ModelName.CHAT, + localField : "chat_id", + foreignField :"_id", + justOne: true +}); + +BoxSchema.virtual(BoxReference.TESTER_PROFILES, { + ref: ModelName.PROFILE, + localField : "testers.profile_id", + foreignField :"_id" +}); +BoxSchema.virtual(BoxReference.TESTER_PLAYERS, { + ref: ModelName.PLAYER, + localField : "testers.player_id", + foreignField :"_id" +}); + +BoxSchema.virtual(BoxReference.DAILY_TASKS, { + ref: ModelName.DAILY_TASK, + localField : "dailyTasks", + foreignField :"_id" +}); + +export const publicReferences = Object.values(BoxReference); diff --git a/src/box/schemas/groupAdmin.schema.ts b/src/box/schemas/groupAdmin.schema.ts new file mode 100644 index 00000000..4658fcb3 --- /dev/null +++ b/src/box/schemas/groupAdmin.schema.ts @@ -0,0 +1,27 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import {HydratedDocument} from "mongoose"; +import {ModelName} from "../../common/enum/modelName.enum"; +import {ObjectId} from "mongodb"; +import {ExtractField} from "../../common/decorator/response/ExtractField"; + +export type GroupAdminDocument = HydratedDocument; + +@Schema({ toJSON: { virtuals: true }, toObject: { virtuals: true } }) +export class GroupAdmin { + @Prop({ type: String, required: true, unique: true }) + password: string; + + @ExtractField() + _id: ObjectId; +} + +export const GroupAdminSchema = SchemaFactory.createForClass(GroupAdmin); +GroupAdminSchema.set('collection', ModelName.GROUP_ADMIN); +GroupAdminSchema.virtual(ModelName.BOX, { + ref: ModelName.SOULHOME, + localField : "password", + foreignField :"adminPassword", + justOne: true +}); + +export const publicReferences = [ ModelName.BOX ]; diff --git a/src/box/schemas/tester.schema.ts b/src/box/schemas/tester.schema.ts new file mode 100644 index 00000000..4b304d2d --- /dev/null +++ b/src/box/schemas/tester.schema.ts @@ -0,0 +1,27 @@ +import { Prop, Schema } from '@nestjs/mongoose'; +import {ObjectId} from "mongodb"; +import {ModelName} from "../../common/enum/modelName.enum"; + +/** + * Tester is a user that have access to the box resources + */ +@Schema({ toJSON: { virtuals: true }, toObject: { virtuals: true } }) +export class Tester { + /** + * Profile _id of the tester + */ + @Prop({ type: ObjectId, required: true, ref: ModelName.PROFILE }) + profile_id: ObjectId; + + /** + * Profile _id of the tester + */ + @Prop({ type: ObjectId, required: true, ref: ModelName.PLAYER }) + player_id: ObjectId; + + /** + * Has the account already been claimed by some device + */ + @Prop({ type: Boolean, default: false }) + isClaimed: boolean; +} diff --git a/src/common/enum/modelName.enum.ts b/src/common/enum/modelName.enum.ts index f85641d4..51ae27a7 100644 --- a/src/common/enum/modelName.enum.ts +++ b/src/common/enum/modelName.enum.ts @@ -26,13 +26,12 @@ export enum ModelName { PROFILE = 'Profile', CHAT = 'Chat', JOIN = 'Join', - CLAN_META = 'ClanMeta', ROOM = 'Room', SOULHOME = 'SoulHome', - CLANVOTE = 'ClanVote', - ITEMSHOP = 'ItemShop', GAME = 'Game', DAILY_TASK = 'DailyTask', FLEA_MARKET_ITEM = 'FleaMarketItem', VOTING = 'Voting', -} \ No newline at end of file + BOX = 'Box', + GROUP_ADMIN = 'GroupAdmin' +}