Skip to content
3 changes: 3 additions & 0 deletions server/src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export interface RequestBadgesQuery extends RequestSearch {
imageUrl?: string;
}

export interface RequestAchievementQuery extends RequestSearch {
custom_action?: string;
}
export interface RequestAchievementStageQuery extends RequestSearch {
stageName?: string;
sound?: string;
Expand Down
98 changes: 96 additions & 2 deletions server/src/controllers/achievementsController.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { NextFunction, Request, Response } from "express";
import { RequestParams, RequestSearch } from "@types";
import {
AchievementUpdateDataController,
CustomAchievementCreateData,
CustomAchievementUpdateData,
createCustomAchievement,
getAchievements,
getAchievementsCount,
getAchievementsProgressesByUserId as getAchievementsProgressesByUserIdService
getAchievementsProgressesByUserId as getAchievementsProgressesByUserIdService,
deleteCustomAchievementById as deleteCustomAchievementByIdService,
updateOneAchievement
} from "@services";
import { AppError } from "@utils";
import { AppError, checkExistResource, logger } from "@utils";
import { filterAchievementsByUrlParams } from "./filters/achievementsFilter";

export const getManyAchievements = async (
Expand Down Expand Up @@ -59,3 +65,91 @@ export const getAchievementsProgressesByUserId = async (
next(err);
}
};

export const editAchievementById = async (
req: Request<RequestParams, {}, AchievementUpdateDataController, {}>,
res: Response,
next: NextFunction
) => {
const { id } = req.params;
const { enabled, description, tag, stages } = req.body;
try {
const updatedAchievement = await updateOneAchievement({ _id: id }, { enabled, description, stages, tag });

const foundAchievement = checkExistResource(updatedAchievement, "Achievement");
return res.status(200).send({
message: "Achievement updated successfully",
data: foundAchievement
});
} catch (err) {
logger.error(`Error when trying to editAchievementById by id: ${id}: ${err}`);
next(err);
}
};

export const addCustomAchievement = async (
req: Request<{}, {}, CustomAchievementCreateData, {}>,
res: Response,
next: NextFunction
) => {
const { name, description, custom, stages, tag, isTime, enabled } = req.body;
try {
if (!custom) throw new AppError(400, `Custom options must be provided.`);
const newCustomAchievement = await createCustomAchievement({
name,
enabled,
description,
custom,
stages,
tag,
isTime
});

return res.status(200).send({ message: "Custom achievement added successfully", data: newCustomAchievement });
} catch (err) {
logger.error(`Error when trying to addCustomAchievement: ${err}`);
next(err);
}
};

export const deleteCustomAchievementById = async (
req: Request<RequestParams, {}, {}, {}>,
res: Response,
next: NextFunction
) => {
const { id } = req.params;

try {
await deleteCustomAchievementByIdService(id);

return res.status(200).send({ message: "Achievement deleted successfully" });
} catch (err) {
logger.error(`Error when trying to deleteCustomAchievementById by id: ${id}: ${err}`);
next(err);
}
};

export const editCustomAchievementById = async (
req: Request<RequestParams, {}, CustomAchievementUpdateData, {}>,
res: Response,
next: NextFunction
) => {
const { id } = req.params;
const { name, enabled, description, custom, stages, tag, isTime } = req.body;

try {
const updatedAchievement = await updateOneAchievement(
{ _id: id },
{ name, enabled, description, custom, stages, tag, isTime }
);

const foundAchievement = checkExistResource(updatedAchievement, "Achievement");
return res.status(200).send({
message: "Custom achievement updated successfully",
data: foundAchievement
});
} catch (err) {
logger.error(`Error when trying to editCustomAchievementById by id: ${id}: ${err}`);
next(err);
}
};
12 changes: 7 additions & 5 deletions server/src/controllers/filters/achievementsFilter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { RequestAchievementStageQuery, RequestSearch } from "@types";
import { RequestAchievementQuery, RequestAchievementStageQuery } from "@types";

export const filterAchievementsByUrlParams = (params: RequestSearch) => {
const { search_name } = params;
export const filterAchievementsByUrlParams = (params: RequestAchievementQuery) => {
const { search_name, custom_action } = params;
const filterName = {
...(search_name && { name: { $regex: search_name, $options: "i" } })
};

const filterCustomAction = {
...(custom_action && { "custom.action": { $regex: custom_action, $options: "i" } })
};
const searchFilter = {
$and: [filterName]
$and: [filterName, filterCustomAction]
};
return searchFilter;
};
Expand Down
66 changes: 0 additions & 66 deletions server/src/defaults/achievements/achievements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,72 +29,6 @@ export const getDefaultAchievementsData = (stagesId: string, tagId: string): Ach
tag: tagId,
isTime: true
},
{
name: ACHIEVEMENTS.COMMAS,
description: "Achievements for at least one ',' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.DICTATOR,
description: "Achievements for '!' at end of message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.DOTS,
description: "Achievements for 'dot' at end of message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.LONG_MESSAGES,
description: "Achievements for long messages (15)",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.SHORT_MESSAGES,
description: "Achievements for short messages (4)",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.MONKEY,
description: "Achievements for at least one '@' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.QUESTION_MARKS,
description: "Achievements for '?' at end of message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.XD,
description: "Achievements for at least one 'xd' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.POLISH_SWEARING,
description: "Achievements for at least one 'polish swear' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.KAPPA,
description: "Achievements for at least one 'Kappa' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.LUL,
description: "Achievements for at least one 'LUL' in message",
stages: stagesId,
tag: tagId
},
{
name: ACHIEVEMENTS.COMMANDS_COUNT,
description: "Achievements for use of command",
Expand Down
2 changes: 1 addition & 1 deletion server/src/defaults/achievements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export { getDefaultBadgeData } from "./badges";
export { getDefaultAchievementStagesData } from "./achievementStages";
export { getDefaultAchievementsData } from "./achievements";

export { ACHIEVEMENTS, POLISH_SWEARING } from "./types";
export { ACHIEVEMENTS } from "./types";
21 changes: 0 additions & 21 deletions server/src/defaults/achievements/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,8 @@ export enum ACHIEVEMENTS {
CHAT_MESSAGES = "CHAT MESSAGES",
POINTS = "POINTS",
FOLLOWAGE = "FOLLOWER",
DOTS = "DOT COUNTS",
QUESTION_MARKS = "QUESTION MARKS",
COMMAS = "COMMA COMMA",
DICTATOR = "DICTATOR",
MONKEY = "MONKEY",
LONG_MESSAGES = "WORD SMITH",
SHORT_MESSAGES = "ENTER",
XD = "XD",
POLISH_SWEARING = "MIENSO",
KAPPA = "KAPPA",
LUL = "LUL",
COMMANDS_COUNT = "COMMANDS",
SONG_REQUEST = "MY MUSIC",
SONG_VOTING = "MY MUSIC TASTE",
BADGES_COUNT = "OBTAINED BADGES"
}

//sorry for swearing LUL
export enum POLISH_SWEARING {
KURWA = "kurwa",
PIERDOLE = "pierdole",
CHUJ = "chuj",
PIZDA = "pizda",
JEBAC = "jebac",
JEBAC2 = "jebać"
}
22 changes: 20 additions & 2 deletions server/src/models/achievementModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Model, model, Schema } from "mongoose";
import { AchievementDocument, AchievementStageDocument, AchievementUserProgressDocument } from "./types";
import {
CustomAchievementAction,
AchievementCustomModel,
AchievementDocument,
AchievementStageDocument,
AchievementUserProgressDocument
} from "./types";
import { enabledField, tagModeField } from "@utils";

const AchievementStageSchema = new Schema<AchievementStageDocument>({
Expand All @@ -18,14 +24,26 @@ const AchievementStageSchema = new Schema<AchievementStageDocument>({
]
});

const AchievementCustomSchema: Schema<AchievementCustomModel> = new Schema({
stringValues: { type: [String], required: false },
numberValue: { type: Number, required: false },
caseSensitive: { type: Boolean, required: false },
action: {
type: String,
default: CustomAchievementAction.INCLUDES,
enum: Object.values(CustomAchievementAction)
}
});

const AchivementSchema: Schema<AchievementDocument> = new Schema(
{
name: { type: String, required: true, unique: true },
description: { type: String },
stages: { type: Schema.Types.ObjectId, required: true, ref: "AchievementStage" },
isTime: { type: Boolean, required: true, default: false },
...tagModeField,
...enabledField
...enabledField,
custom: { type: AchievementCustomSchema, required: false }
},
{ timestamps: true }
);
Expand Down
16 changes: 16 additions & 0 deletions server/src/models/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,21 @@ export interface AchievementStageModel<T = string> extends BaseModel {
}

export type AchievementStageDocument = AchievementStageModel & Document;
export enum CustomAchievementAction {
ALL = "ALL MESSAGES",
INCLUDES = "INCLUDES",
STARTS_WITH = "STARTS WITH",
ENDS_WITH = "ENDS WITH",
MESSAGE_GT = "MESSAGE LENGTH GREATER THAN",
MESSAGE_LT = "MESSAGE LENGTH LESS THAN",
WATCH_TIME = "WATCH TIME"
}
export interface AchievementCustomModel {
stringValues?: string[];
caseSensitive?: boolean;
numberValue?: number;
action: CustomAchievementAction;
}

export interface AchievementModel<T = string | BadgeModel> extends BaseModel {
name: string;
Expand All @@ -313,6 +328,7 @@ export interface AchievementModel<T = string | BadgeModel> extends BaseModel {
isTime: boolean;
tag: string | TagModel;
enabled: boolean;
custom: AchievementCustomModel;
}

export type AchievementWithBadgePopulated = AchievementModel<BadgeModel>;
Expand Down
12 changes: 11 additions & 1 deletion server/src/routes/achievementsRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
uploadAchievementStageSounds,
deleteAchievementStageSoundById,
addNewAchievementStage,
getBadgeById
getBadgeById,
addCustomAchievement,
editCustomAchievementById,
deleteCustomAchievementById,
editAchievementById
} from "@controllers";
import { checkSearchParams, isParamObjectId } from "@middlewares";
import { Router } from "express";
Expand All @@ -27,6 +31,10 @@ const achievementsRouter = Router();

achievementsRouter.get("/", checkSearchParams, getManyAchievements);

achievementsRouter.post("/custom/create", addCustomAchievement);
achievementsRouter.post("/custom/:id", isParamObjectId, editCustomAchievementById);
achievementsRouter.delete("/custom/:id", deleteCustomAchievementById);

/* STAGES RELATED */
achievementsRouter.get("/stages", checkSearchParams, getManyAchievementStages);
achievementsRouter.post("/stages/create", addNewAchievementStage);
Expand All @@ -51,4 +59,6 @@ achievementsRouter.post("/badges/:id", isParamObjectId, editBadgeById);

achievementsRouter.get("/user/:id", isParamObjectId, getAchievementsProgressesByUserId);

achievementsRouter.post("/:id", isParamObjectId, editAchievementById);

export default achievementsRouter;
Loading