forked from OpenWF/SpaceNinjaServer
		
	feat: dojo decorations (#1079)
Closes #525 Reviewed-on: OpenWF/SpaceNinjaServer#1079
This commit is contained in:
		
							parent
							
								
									97b61b51b7
								
							
						
					
					
						commit
						c4ab496aa3
					
				@ -1,4 +1,4 @@
 | 
				
			|||||||
import { getDojoClient, getGuildForRequestEx } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequestEx, removeDojoDeco, removeDojoRoom } from "@/src/services/guildService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
@ -8,12 +8,20 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
 | 
					    const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Move already-contributed credits & items to the clan vault
 | 
					    // TODO: Move already-contributed credits & items to the clan vault
 | 
				
			||||||
    guild.DojoComponents.pull({ _id: request.ComponentId });
 | 
					    if (request.DecoId) {
 | 
				
			||||||
 | 
					        removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        removeDojoRoom(guild, request.ComponentId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
    res.json(getDojoClient(guild, 0));
 | 
					    res.json(getDojoClient(guild, 0, request.ComponentId));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IAbortDojoComponentRequest {
 | 
					interface IAbortDojoComponentRequest {
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
    ComponentId: string;
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,26 @@
 | 
				
			|||||||
 | 
					import { TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
				
			||||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IDojoContributable } from "@/src/types/guildTypes";
 | 
				
			||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, IDojoRecipe } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IContributeToDojoComponentRequest {
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
 | 
					    IngredientContributions: {
 | 
				
			||||||
 | 
					        ItemType: string;
 | 
				
			||||||
 | 
					        ItemCount: number;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					    RegularCredits: number;
 | 
				
			||||||
 | 
					    VaultIngredientContributions: [];
 | 
				
			||||||
 | 
					    VaultCredits: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
 | 
					export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -13,21 +29,54 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
 | 
				
			|||||||
    // Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
 | 
					    // Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
 | 
					    const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
 | 
				
			||||||
    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
    const componentMeta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (!component.CompletionTime) {
 | 
				
			||||||
 | 
					        // Room is in "Collecting Materials" state
 | 
				
			||||||
 | 
					        if (request.DecoId) {
 | 
				
			||||||
 | 
					            throw new Error("attempt to contribute to a deco in an unfinished room?!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
				
			||||||
 | 
					        await processContribution(guild, request, inventory, inventoryChanges, meta, component);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Room is past "Collecting Materials"
 | 
				
			||||||
 | 
					        if (request.DecoId) {
 | 
				
			||||||
 | 
					            const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
 | 
				
			||||||
 | 
					            const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
 | 
				
			||||||
 | 
					            await processContribution(guild, request, inventory, inventoryChanges, meta, deco);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        ...getDojoClient(guild, 0, component._id),
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processContribution = async (
 | 
				
			||||||
 | 
					    guild: TGuildDatabaseDocument,
 | 
				
			||||||
 | 
					    request: IContributeToDojoComponentRequest,
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    inventoryChanges: IInventoryChanges,
 | 
				
			||||||
 | 
					    meta: IDojoRecipe,
 | 
				
			||||||
 | 
					    component: IDojoContributable
 | 
				
			||||||
 | 
					): Promise<void> => {
 | 
				
			||||||
    component.RegularCredits ??= 0;
 | 
					    component.RegularCredits ??= 0;
 | 
				
			||||||
    if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(componentMeta.price)) {
 | 
					    if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(meta.price)) {
 | 
				
			||||||
        request.RegularCredits = scaleRequiredCount(componentMeta.price) - component.RegularCredits;
 | 
					        request.RegularCredits = scaleRequiredCount(meta.price) - component.RegularCredits;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    component.RegularCredits += request.RegularCredits;
 | 
					    component.RegularCredits += request.RegularCredits;
 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = updateCurrency(inventory, request.RegularCredits, false);
 | 
					    inventoryChanges.RegularCredits = -request.RegularCredits;
 | 
				
			||||||
 | 
					    updateCurrency(inventory, request.RegularCredits, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component.MiscItems ??= [];
 | 
					    component.MiscItems ??= [];
 | 
				
			||||||
    const miscItemChanges: IMiscItem[] = [];
 | 
					    const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
    for (const ingredientContribution of request.IngredientContributions) {
 | 
					    for (const ingredientContribution of request.IngredientContributions) {
 | 
				
			||||||
        const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
 | 
					        const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
 | 
				
			||||||
        if (componentMiscItem) {
 | 
					        if (componentMiscItem) {
 | 
				
			||||||
            const ingredientMeta = componentMeta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
					            const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
					                componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
				
			||||||
                scaleRequiredCount(ingredientMeta.ItemCount)
 | 
					                scaleRequiredCount(ingredientMeta.ItemCount)
 | 
				
			||||||
@ -47,9 +96,9 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
 | 
				
			|||||||
    addMiscItems(inventory, miscItemChanges);
 | 
					    addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
    inventoryChanges.MiscItems = miscItemChanges;
 | 
					    inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (component.RegularCredits >= scaleRequiredCount(componentMeta.price)) {
 | 
					    if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
 | 
				
			||||||
        let fullyFunded = true;
 | 
					        let fullyFunded = true;
 | 
				
			||||||
        for (const ingredient of componentMeta.ingredients) {
 | 
					        for (const ingredient of meta.ingredients) {
 | 
				
			||||||
            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
					            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
				
			||||||
            if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
 | 
					            if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
 | 
				
			||||||
                fullyFunded = false;
 | 
					                fullyFunded = false;
 | 
				
			||||||
@ -57,27 +106,13 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (fullyFunded) {
 | 
					        if (fullyFunded) {
 | 
				
			||||||
 | 
					            if (request.IngredientContributions.length) {
 | 
				
			||||||
 | 
					                // We've already updated subpaths of MiscItems, we need to allow MongoDB to save this before we remove MiscItems.
 | 
				
			||||||
 | 
					                await guild.save();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            component.RegularCredits = undefined;
 | 
					            component.RegularCredits = undefined;
 | 
				
			||||||
            component.MiscItems = undefined;
 | 
					            component.MiscItems = undefined;
 | 
				
			||||||
            component.CompletionTime = new Date(Date.now() + componentMeta.time * 1000);
 | 
					            component.CompletionTime = new Date(Date.now() + meta.time * 1000);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    await guild.save();
 | 
					 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
    res.json({
 | 
					 | 
				
			||||||
        ...getDojoClient(guild, 0, component._id),
 | 
					 | 
				
			||||||
        InventoryChanges: inventoryChanges
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface IContributeToDojoComponentRequest {
 | 
					 | 
				
			||||||
    ComponentId: string;
 | 
					 | 
				
			||||||
    IngredientContributions: {
 | 
					 | 
				
			||||||
        ItemType: string;
 | 
					 | 
				
			||||||
        ItemCount: number;
 | 
					 | 
				
			||||||
    }[];
 | 
					 | 
				
			||||||
    RegularCredits: number;
 | 
					 | 
				
			||||||
    VaultIngredientContributions: [];
 | 
					 | 
				
			||||||
    VaultCredits: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								src/controllers/api/destroyDojoDecoController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/controllers/api/destroyDojoDecoController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import { getDojoClient, getGuildForRequest, removeDojoDeco } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const destroyDojoDecoController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
				
			||||||
 | 
					    // TODO: The client says this is supposed to refund the resources to the clan vault, so we should probably do that.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(getDojoClient(guild, 0, request.ComponentId));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IDestroyDojoDecoRequest {
 | 
				
			||||||
 | 
					    DecoType: string;
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,8 +1,18 @@
 | 
				
			|||||||
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
				
			||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IDojoContributable } from "@/src/types/guildTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, IDojoRecipe } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IDojoComponentRushRequest {
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    Amount: number;
 | 
				
			||||||
 | 
					    VaultAmount: number;
 | 
				
			||||||
 | 
					    AllianceVaultAmount: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
					export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -10,14 +20,16 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
 | 
					    const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
 | 
				
			||||||
    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
    const componentMeta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fullPlatinumCost = scaleRequiredCount(componentMeta.skipTimePrice);
 | 
					    if (request.DecoId) {
 | 
				
			||||||
    const fullDurationSeconds = componentMeta.time;
 | 
					        const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
 | 
				
			||||||
    const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
 | 
					        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
 | 
				
			||||||
    component.CompletionTime = new Date(
 | 
					        processContribution(deco, meta, request.Amount);
 | 
				
			||||||
        component.CompletionTime!.getTime() - secondsPerPlatinum * request.Amount * 1000
 | 
					    } else {
 | 
				
			||||||
    );
 | 
					        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
				
			||||||
 | 
					        processContribution(component, meta, request.Amount);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventoryChanges = updateCurrency(inventory, request.Amount, true);
 | 
					    const inventoryChanges = updateCurrency(inventory, request.Amount, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
@ -28,9 +40,11 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IDojoComponentRushRequest {
 | 
					const processContribution = (component: IDojoContributable, meta: IDojoRecipe, platinumDonated: number): void => {
 | 
				
			||||||
    ComponentId: string;
 | 
					    const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
 | 
				
			||||||
    Amount: number;
 | 
					    const fullDurationSeconds = meta.time;
 | 
				
			||||||
    VaultAmount: number;
 | 
					    const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
 | 
				
			||||||
    AllianceVaultAmount: number;
 | 
					    component.CompletionTime = new Date(
 | 
				
			||||||
}
 | 
					        component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,8 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            _id: new Types.ObjectId(),
 | 
					            _id: new Types.ObjectId(),
 | 
				
			||||||
            pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
 | 
					            pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
 | 
				
			||||||
            ppf: "",
 | 
					            ppf: "",
 | 
				
			||||||
            CompletionTime: new Date(Date.now())
 | 
					            CompletionTime: new Date(Date.now()),
 | 
				
			||||||
 | 
					            DecoCapacity: 600
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										43
									
								
								src/controllers/api/placeDecoInComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/controllers/api/placeDecoInComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IPlaceDecoInComponentRequest;
 | 
				
			||||||
 | 
					    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to place decorations.
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (component.DecoCapacity === undefined) {
 | 
				
			||||||
 | 
					        component.DecoCapacity = Object.values(ExportDojoRecipes.rooms).find(
 | 
				
			||||||
 | 
					            x => x.resultType == component.pf
 | 
				
			||||||
 | 
					        )!.decoCapacity;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    component.Decos ??= [];
 | 
				
			||||||
 | 
					    component.Decos.push({
 | 
				
			||||||
 | 
					        _id: new Types.ObjectId(),
 | 
				
			||||||
 | 
					        Type: request.Type,
 | 
				
			||||||
 | 
					        Pos: request.Pos,
 | 
				
			||||||
 | 
					        Rot: request.Rot,
 | 
				
			||||||
 | 
					        Name: request.Name
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
 | 
				
			||||||
 | 
					    if (meta && meta.capacityCost) {
 | 
				
			||||||
 | 
					        component.DecoCapacity -= meta.capacityCost;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(getDojoClient(guild, 0, component._id));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IPlaceDecoInComponentRequest {
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    Revision: number;
 | 
				
			||||||
 | 
					    Type: string;
 | 
				
			||||||
 | 
					    Pos: number[];
 | 
				
			||||||
 | 
					    Rot: number[];
 | 
				
			||||||
 | 
					    Name?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,19 +1,12 @@
 | 
				
			|||||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequest, removeDojoRoom } from "@/src/services/guildService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
 | 
					export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const guild = await getGuildForRequest(req);
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
    const componentId = req.query.componentId as string;
 | 
					    const componentId = req.query.componentId as string;
 | 
				
			||||||
    const component = guild.DojoComponents.splice(
 | 
					
 | 
				
			||||||
        guild.DojoComponents.findIndex(x => x._id.toString() === componentId),
 | 
					    removeDojoRoom(guild, componentId);
 | 
				
			||||||
        1
 | 
					
 | 
				
			||||||
    )[0];
 | 
					 | 
				
			||||||
    const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
 | 
					 | 
				
			||||||
    if (room) {
 | 
					 | 
				
			||||||
        guild.DojoCapacity -= room.capacity;
 | 
					 | 
				
			||||||
        guild.DojoEnergy -= room.energy;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
    res.json(getDojoClient(guild, 1));
 | 
					    res.json(getDojoClient(guild, 1));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,8 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                ppf: request.PlacedComponent.ppf,
 | 
					                ppf: request.PlacedComponent.ppf,
 | 
				
			||||||
                pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
 | 
					                pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
 | 
				
			||||||
                op: request.PlacedComponent.op,
 | 
					                op: request.PlacedComponent.op,
 | 
				
			||||||
                pp: request.PlacedComponent.pp
 | 
					                pp: request.PlacedComponent.pp,
 | 
				
			||||||
 | 
					                DecoCapacity: room?.decoCapacity
 | 
				
			||||||
            }) - 1
 | 
					            }) - 1
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    if (config.noDojoRoomBuildStage) {
 | 
					    if (config.noDojoRoomBuildStage) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,23 @@ import {
 | 
				
			|||||||
    IGuildDatabase,
 | 
					    IGuildDatabase,
 | 
				
			||||||
    IDojoComponentDatabase,
 | 
					    IDojoComponentDatabase,
 | 
				
			||||||
    ITechProjectDatabase,
 | 
					    ITechProjectDatabase,
 | 
				
			||||||
    ITechProjectClient
 | 
					    ITechProjectClient,
 | 
				
			||||||
 | 
					    IDojoDecoDatabase
 | 
				
			||||||
} from "@/src/types/guildTypes";
 | 
					} from "@/src/types/guildTypes";
 | 
				
			||||||
import { Document, Model, model, Schema, Types } from "mongoose";
 | 
					import { Document, Model, model, Schema, Types } from "mongoose";
 | 
				
			||||||
import { typeCountSchema } from "./inventoryModels/inventoryModel";
 | 
					import { typeCountSchema } from "./inventoryModels/inventoryModel";
 | 
				
			||||||
import { toMongoDate } from "../helpers/inventoryHelpers";
 | 
					import { toMongoDate } from "../helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
 | 
				
			||||||
 | 
					    Type: String,
 | 
				
			||||||
 | 
					    Pos: [Number],
 | 
				
			||||||
 | 
					    Rot: [Number],
 | 
				
			||||||
 | 
					    Name: String,
 | 
				
			||||||
 | 
					    RegularCredits: Number,
 | 
				
			||||||
 | 
					    MiscItems: { type: [typeCountSchema], default: undefined },
 | 
				
			||||||
 | 
					    CompletionTime: Date
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
 | 
					const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
 | 
				
			||||||
    pf: { type: String, required: true },
 | 
					    pf: { type: String, required: true },
 | 
				
			||||||
    ppf: String,
 | 
					    ppf: String,
 | 
				
			||||||
@ -18,7 +29,10 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
 | 
				
			|||||||
    Message: String,
 | 
					    Message: String,
 | 
				
			||||||
    RegularCredits: Number,
 | 
					    RegularCredits: Number,
 | 
				
			||||||
    MiscItems: { type: [typeCountSchema], default: undefined },
 | 
					    MiscItems: { type: [typeCountSchema], default: undefined },
 | 
				
			||||||
    CompletionTime: Date
 | 
					    CompletionTime: Date,
 | 
				
			||||||
 | 
					    DestructionTime: Date,
 | 
				
			||||||
 | 
					    Decos: [dojoDecoSchema],
 | 
				
			||||||
 | 
					    DecoCapacity: Number
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const techProjectSchema = new Schema<ITechProjectDatabase>(
 | 
					const techProjectSchema = new Schema<ITechProjectDatabase>(
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ import { contributeToDojoComponentController } from "@/src/controllers/api/contr
 | 
				
			|||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
					import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
				
			||||||
import { creditsController } from "@/src/controllers/api/creditsController";
 | 
					import { creditsController } from "@/src/controllers/api/creditsController";
 | 
				
			||||||
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
 | 
					import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
 | 
				
			||||||
 | 
					import { destroyDojoDecoController } from "@/src/controllers/api/destroyDojoDecoController";
 | 
				
			||||||
import { dojoComponentRushController } from "@/src/controllers/api/dojoComponentRushController";
 | 
					import { dojoComponentRushController } from "@/src/controllers/api/dojoComponentRushController";
 | 
				
			||||||
import { dojoController } from "@/src/controllers/api/dojoController";
 | 
					import { dojoController } from "@/src/controllers/api/dojoController";
 | 
				
			||||||
import { dronesController } from "@/src/controllers/api/dronesController";
 | 
					import { dronesController } from "@/src/controllers/api/dronesController";
 | 
				
			||||||
@ -59,6 +60,7 @@ import { missionInventoryUpdateController } from "@/src/controllers/api/missionI
 | 
				
			|||||||
import { modularWeaponCraftingController } from "@/src/controllers/api/modularWeaponCraftingController";
 | 
					import { modularWeaponCraftingController } from "@/src/controllers/api/modularWeaponCraftingController";
 | 
				
			||||||
import { modularWeaponSaleController } from "@/src/controllers/api/modularWeaponSaleController";
 | 
					import { modularWeaponSaleController } from "@/src/controllers/api/modularWeaponSaleController";
 | 
				
			||||||
import { nameWeaponController } from "@/src/controllers/api/nameWeaponController";
 | 
					import { nameWeaponController } from "@/src/controllers/api/nameWeaponController";
 | 
				
			||||||
 | 
					import { placeDecoInComponentController } from "@/src/controllers/api/placeDecoInComponentController";
 | 
				
			||||||
import { playerSkillsController } from "@/src/controllers/api/playerSkillsController";
 | 
					import { playerSkillsController } from "@/src/controllers/api/playerSkillsController";
 | 
				
			||||||
import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
 | 
					import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
 | 
				
			||||||
import { purchaseController } from "@/src/controllers/api/purchaseController";
 | 
					import { purchaseController } from "@/src/controllers/api/purchaseController";
 | 
				
			||||||
@ -150,6 +152,7 @@ apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
 | 
				
			|||||||
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
 | 
					apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
 | 
				
			||||||
apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
 | 
					apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
 | 
				
			||||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
					apiRouter.post("/createGuild.php", createGuildController);
 | 
				
			||||||
 | 
					apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController);
 | 
				
			||||||
apiRouter.post("/dojoComponentRush.php", dojoComponentRushController);
 | 
					apiRouter.post("/dojoComponentRush.php", dojoComponentRushController);
 | 
				
			||||||
apiRouter.post("/drones.php", dronesController);
 | 
					apiRouter.post("/drones.php", dronesController);
 | 
				
			||||||
apiRouter.post("/endlessXp.php", endlessXpController);
 | 
					apiRouter.post("/endlessXp.php", endlessXpController);
 | 
				
			||||||
@ -175,6 +178,7 @@ apiRouter.post("/login.php", loginController);
 | 
				
			|||||||
apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
 | 
					apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
 | 
				
			||||||
apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
 | 
					apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
 | 
				
			||||||
apiRouter.post("/nameWeapon.php", nameWeaponController);
 | 
					apiRouter.post("/nameWeapon.php", nameWeaponController);
 | 
				
			||||||
 | 
					apiRouter.post("/placeDecoInComponent.php", placeDecoInComponentController);
 | 
				
			||||||
apiRouter.post("/playerSkills.php", playerSkillsController);
 | 
					apiRouter.post("/playerSkills.php", playerSkillsController);
 | 
				
			||||||
apiRouter.post("/projectionManager.php", projectionManagerController);
 | 
					apiRouter.post("/projectionManager.php", projectionManagerController);
 | 
				
			||||||
apiRouter.post("/purchase.php", purchaseController);
 | 
					apiRouter.post("/purchase.php", purchaseController);
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento
 | 
				
			|||||||
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
 | 
					import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
 | 
				
			||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
 | 
					export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -31,7 +32,7 @@ export const getGuildForRequestEx = async (
 | 
				
			|||||||
export const getDojoClient = (
 | 
					export const getDojoClient = (
 | 
				
			||||||
    guild: TGuildDatabaseDocument,
 | 
					    guild: TGuildDatabaseDocument,
 | 
				
			||||||
    status: number,
 | 
					    status: number,
 | 
				
			||||||
    componentId: Types.ObjectId | undefined = undefined
 | 
					    componentId: Types.ObjectId | string | undefined = undefined
 | 
				
			||||||
): IDojoClient => {
 | 
					): IDojoClient => {
 | 
				
			||||||
    const dojo: IDojoClient = {
 | 
					    const dojo: IDojoClient = {
 | 
				
			||||||
        _id: { $oid: guild._id.toString() },
 | 
					        _id: { $oid: guild._id.toString() },
 | 
				
			||||||
@ -46,14 +47,14 @@ export const getDojoClient = (
 | 
				
			|||||||
        DojoComponents: []
 | 
					        DojoComponents: []
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    guild.DojoComponents.forEach(dojoComponent => {
 | 
					    guild.DojoComponents.forEach(dojoComponent => {
 | 
				
			||||||
        if (!componentId || componentId == dojoComponent._id) {
 | 
					        if (!componentId || dojoComponent._id.equals(componentId)) {
 | 
				
			||||||
            const clientComponent: IDojoComponentClient = {
 | 
					            const clientComponent: IDojoComponentClient = {
 | 
				
			||||||
                id: toOid(dojoComponent._id),
 | 
					                id: toOid(dojoComponent._id),
 | 
				
			||||||
                pf: dojoComponent.pf,
 | 
					                pf: dojoComponent.pf,
 | 
				
			||||||
                ppf: dojoComponent.ppf,
 | 
					                ppf: dojoComponent.ppf,
 | 
				
			||||||
                Name: dojoComponent.Name,
 | 
					                Name: dojoComponent.Name,
 | 
				
			||||||
                Message: dojoComponent.Message,
 | 
					                Message: dojoComponent.Message,
 | 
				
			||||||
                DecoCapacity: 600
 | 
					                DecoCapacity: dojoComponent.DecoCapacity ?? 600
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            if (dojoComponent.pi) {
 | 
					            if (dojoComponent.pi) {
 | 
				
			||||||
                clientComponent.pi = toOid(dojoComponent.pi);
 | 
					                clientComponent.pi = toOid(dojoComponent.pi);
 | 
				
			||||||
@ -66,6 +67,20 @@ export const getDojoClient = (
 | 
				
			|||||||
                clientComponent.RegularCredits = dojoComponent.RegularCredits;
 | 
					                clientComponent.RegularCredits = dojoComponent.RegularCredits;
 | 
				
			||||||
                clientComponent.MiscItems = dojoComponent.MiscItems;
 | 
					                clientComponent.MiscItems = dojoComponent.MiscItems;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if (dojoComponent.Decos) {
 | 
				
			||||||
 | 
					                clientComponent.Decos = [];
 | 
				
			||||||
 | 
					                for (const deco of dojoComponent.Decos) {
 | 
				
			||||||
 | 
					                    clientComponent.Decos.push({
 | 
				
			||||||
 | 
					                        id: toOid(deco._id),
 | 
				
			||||||
 | 
					                        Type: deco.Type,
 | 
				
			||||||
 | 
					                        Pos: deco.Pos,
 | 
				
			||||||
 | 
					                        Rot: deco.Rot,
 | 
				
			||||||
 | 
					                        CompletionTime: deco.CompletionTime ? toMongoDate(deco.CompletionTime) : undefined,
 | 
				
			||||||
 | 
					                        RegularCredits: deco.RegularCredits,
 | 
				
			||||||
 | 
					                        MiscItems: deco.MiscItems
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            dojo.DojoComponents.push(clientComponent);
 | 
					            dojo.DojoComponents.push(clientComponent);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -76,3 +91,27 @@ export const scaleRequiredCount = (count: number): number => {
 | 
				
			|||||||
    // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
 | 
					    // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
 | 
				
			||||||
    return Math.max(1, Math.trunc(count / 100));
 | 
					    return Math.max(1, Math.trunc(count / 100));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: string): void => {
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.splice(
 | 
				
			||||||
 | 
					        guild.DojoComponents.findIndex(x => x._id.equals(componentId)),
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    )[0];
 | 
				
			||||||
 | 
					    const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
 | 
				
			||||||
 | 
					    if (meta) {
 | 
				
			||||||
 | 
					        guild.DojoCapacity -= meta.capacity;
 | 
				
			||||||
 | 
					        guild.DojoEnergy -= meta.energy;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeDojoDeco = (guild: TGuildDatabaseDocument, componentId: string, decoId: string): void => {
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(componentId)!;
 | 
				
			||||||
 | 
					    const deco = component.Decos!.splice(
 | 
				
			||||||
 | 
					        component.Decos!.findIndex(x => x._id.equals(decoId)),
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    )[0];
 | 
				
			||||||
 | 
					    const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type);
 | 
				
			||||||
 | 
					    if (meta && meta.capacityCost) {
 | 
				
			||||||
 | 
					        component.DecoCapacity! += meta.capacityCost;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -41,18 +41,33 @@ export interface IDojoComponentClient {
 | 
				
			|||||||
    CompletionTime?: IMongoDate;
 | 
					    CompletionTime?: IMongoDate;
 | 
				
			||||||
    RushPlatinum?: number;
 | 
					    RushPlatinum?: number;
 | 
				
			||||||
    DestructionTime?: IMongoDate;
 | 
					    DestructionTime?: IMongoDate;
 | 
				
			||||||
 | 
					    Decos?: IDojoDecoClient[];
 | 
				
			||||||
    DecoCapacity?: number;
 | 
					    DecoCapacity?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IDojoComponentDatabase
 | 
					export interface IDojoComponentDatabase
 | 
				
			||||||
    extends Omit<
 | 
					    extends Omit<IDojoComponentClient, "id" | "pi" | "CompletionTime" | "RushPlatinum" | "DestructionTime" | "Decos"> {
 | 
				
			||||||
        IDojoComponentClient,
 | 
					 | 
				
			||||||
        "id" | "pi" | "CompletionTime" | "RushPlatinum" | "DestructionTime" | "DecoCapacity"
 | 
					 | 
				
			||||||
    > {
 | 
					 | 
				
			||||||
    _id: Types.ObjectId;
 | 
					    _id: Types.ObjectId;
 | 
				
			||||||
    pi?: Types.ObjectId;
 | 
					    pi?: Types.ObjectId;
 | 
				
			||||||
    CompletionTime?: Date;
 | 
					    CompletionTime?: Date;
 | 
				
			||||||
    //DestructionTime?: Date;
 | 
					    DestructionTime?: Date;
 | 
				
			||||||
 | 
					    Decos?: IDojoDecoDatabase[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IDojoDecoClient {
 | 
				
			||||||
 | 
					    id: IOid;
 | 
				
			||||||
 | 
					    Type: string;
 | 
				
			||||||
 | 
					    Pos: number[];
 | 
				
			||||||
 | 
					    Rot: number[];
 | 
				
			||||||
 | 
					    Name?: string; // for teleporters
 | 
				
			||||||
 | 
					    RegularCredits?: number;
 | 
				
			||||||
 | 
					    MiscItems?: IMiscItem[];
 | 
				
			||||||
 | 
					    CompletionTime?: IMongoDate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IDojoDecoDatabase extends Omit<IDojoDecoClient, "id" | "CompletionTime"> {
 | 
				
			||||||
 | 
					    _id: Types.ObjectId;
 | 
				
			||||||
 | 
					    CompletionTime?: Date;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ITechProjectClient {
 | 
					export interface ITechProjectClient {
 | 
				
			||||||
@ -66,3 +81,9 @@ export interface ITechProjectClient {
 | 
				
			|||||||
export interface ITechProjectDatabase extends Omit<ITechProjectClient, "CompletionDate"> {
 | 
					export interface ITechProjectDatabase extends Omit<ITechProjectClient, "CompletionDate"> {
 | 
				
			||||||
    CompletionDate?: Date;
 | 
					    CompletionDate?: Date;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IDojoContributable {
 | 
				
			||||||
 | 
					    RegularCredits?: number;
 | 
				
			||||||
 | 
					    MiscItems?: IMiscItem[];
 | 
				
			||||||
 | 
					    CompletionTime?: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user