diff --git a/packages/api/controllers/challenges.controller.ts b/packages/api/controllers/challenges.controller.ts index cf76125..5057406 100644 --- a/packages/api/controllers/challenges.controller.ts +++ b/packages/api/controllers/challenges.controller.ts @@ -40,6 +40,19 @@ export class ChallengeController extends ChallengeService { } } + public getProgress = async (ctx: Context) => { + try { + const userTeamID = await ctx.get("team_id"); + const data = await this.getTeamProgressS(userTeamID); + return ctx.json(data); + } + catch (error: any) { + return ctx.json({ + error: error.message + }); + } + } + public sumbitFlag = async (ctx: Context) => { try { const reqBody = await ctx.req.json(); diff --git a/packages/api/controllers/stats.controller.ts b/packages/api/controllers/stats.controller.ts index e69de29..f65900c 100644 --- a/packages/api/controllers/stats.controller.ts +++ b/packages/api/controllers/stats.controller.ts @@ -0,0 +1,30 @@ +import { Context } from "hono"; + +import { StatsService } from "../services/stats.service"; + +export class StatsControtoller extends StatsService { + public getTeamScore = async (ctx: Context) => { + try { + const teamId = ctx.get("team_id"); + const data = await this.getTeamScoreS(teamId); + return ctx.json(data); + } + catch (error: any) { + return ctx.json({ + error: error.message + }); + } + }; + + public getAllTeamsScore = async (ctx: Context) => { + try { + const data = await this.getAllTeamScoreS(); + return ctx.json(data); + } + catch (error: any) { + return ctx.json({ + error: error.message + }); + } + } +} \ No newline at end of file diff --git a/packages/api/controllers/teams.controller.ts b/packages/api/controllers/teams.controller.ts index 32732eb..ad0f6a6 100644 --- a/packages/api/controllers/teams.controller.ts +++ b/packages/api/controllers/teams.controller.ts @@ -72,10 +72,30 @@ export class TeamController extends TeamService { } } - public getProgress = async (ctx: Context) => { + public leaveTeam = async (ctx: Context) => { + try { + const user = ctx.get("user"); + const data = await this.leaveTeamS({ + user_id: user.id + }); + return ctx.json(data); + } + catch (error: any) { + return ctx.json({ + error: error.message + }); + } + } + + public editTeam = async (ctx: Context) => { try { const userTeamID = await ctx.get("team_id"); - const data = await this.getTeamProgressS(userTeamID); + const reqBody = await ctx.req.json(); + const data = await this.EditTeamInfo({ + id: userTeamID, + team_name: reqBody.team_name, + join_code: reqBody.join_code + }); return ctx.json(data); } catch (error: any) { diff --git a/packages/api/routes/challenges.routes.ts b/packages/api/routes/challenges.routes.ts index df8bb73..8de03cc 100644 --- a/packages/api/routes/challenges.routes.ts +++ b/packages/api/routes/challenges.routes.ts @@ -11,4 +11,6 @@ const challengeController = new ChallengeController(); challengeRouter.post("/manage/machine", authMiddleware.authenticate(AUTH_PERMS.ADMIN), challengeController.createMachine); challengeRouter.get("/manage/machine/:machineId", authMiddleware.authenticate(AUTH_PERMS.ADMIN), challengeController.getMachineById); challengeRouter.get("/manage/machines", authMiddleware.authenticate(AUTH_PERMS.ADMIN), challengeController.getMachines); + +challengeRouter.get("/progress", authMiddleware.authenticate(AUTH_PERMS.TEAM_MEMBER), challengeController.getProgress); challengeRouter.put("/submit/:challengeId", authMiddleware.authenticate(AUTH_PERMS.TEAM_MEMBER), challengeController.sumbitFlag); \ No newline at end of file diff --git a/packages/api/routes/index.ts b/packages/api/routes/index.ts index daf9fbe..227c5da 100644 --- a/packages/api/routes/index.ts +++ b/packages/api/routes/index.ts @@ -1,13 +1,19 @@ import { Hono } from "hono"; import { challengeRouter } from "./challenges.routes"; -import { userRoutes } from "./users.routes"; +import { statsRouter } from "./stats.routes"; import { teamRouter } from "./teams.routes"; +import { userRoutes } from "./users.routes"; export const routes = new Hono(); routes.get("/", ctx => ctx.text("Shushhhhhh! Dont look here. ;)")); -routes.route("/user", userRoutes); +routes.route("/challenge", challengeRouter); +routes.route("/stats", statsRouter); routes.route("/team", teamRouter); -routes.route("/challenge", challengeRouter); \ No newline at end of file +routes.route("/user", userRoutes); + +routes.get("/health", ctx => ctx.text("OK")); +routes.get("/flag.txt", ctx => ctx.text("flag{th1s_1s_n0t_th3_fl4g}")); +routes.get("*", ctx => ctx.text("Are you lost?")) \ No newline at end of file diff --git a/packages/api/routes/stats.routes.ts b/packages/api/routes/stats.routes.ts index 5dca216..2cab1b2 100644 --- a/packages/api/routes/stats.routes.ts +++ b/packages/api/routes/stats.routes.ts @@ -1,5 +1,13 @@ import { Hono } from "hono"; -const router = new Hono(); +import { AuthMiddleware } from "../middlewares/auth.middleware"; +import { AUTH_PERMS } from "../extras/permissions"; +import { StatsControtoller } from "../controllers/stats.controller"; -export default router; \ No newline at end of file +export const statsRouter = new Hono(); + +const authMiddleware = new AuthMiddleware(); +const statsController = new StatsControtoller(); + +statsRouter.get("/team", authMiddleware.authenticate(AUTH_PERMS.TEAM_MEMBER), statsController.getTeamScore); +statsRouter.get("/leaderboard", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), statsController.getAllTeamsScore); \ No newline at end of file diff --git a/packages/api/routes/teams.routes.ts b/packages/api/routes/teams.routes.ts index dc0c5ab..ff7c6c0 100644 --- a/packages/api/routes/teams.routes.ts +++ b/packages/api/routes/teams.routes.ts @@ -5,12 +5,12 @@ import { TeamController } from "../controllers/teams.controller"; import { AUTH_PERMS } from "../extras/permissions"; export const teamRouter = new Hono(); -const auth = new AuthMiddleware(); -const controller = new TeamController(); +const authMiddleware = new AuthMiddleware(); +const teamController = new TeamController(); -teamRouter.post("/", auth.authenticate(AUTH_PERMS.AUTHENTICATED), controller.createTeam); -teamRouter.get("/", auth.authenticate(AUTH_PERMS.AUTHENTICATED), controller.getTeams); -teamRouter.get("/whoami", auth.authenticate(AUTH_PERMS.TEAM_MEMBER), controller.whoami); -teamRouter.get("/progress", auth.authenticate(AUTH_PERMS.TEAM_MEMBER), controller.getProgress); -teamRouter.get("/:teamId", auth.authenticate(AUTH_PERMS.AUTHENTICATED), controller.getTeamById); -teamRouter.post("/join", auth.authenticate(AUTH_PERMS.AUTHENTICATED), controller.joinTeam); \ No newline at end of file +teamRouter.post("/", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), teamController.createTeam); +teamRouter.get("/", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), teamController.getTeams); +teamRouter.get("/whoami", authMiddleware.authenticate(AUTH_PERMS.TEAM_MEMBER), teamController.whoami); +teamRouter.put("/edit", authMiddleware.authenticate(AUTH_PERMS.TEAM_MEMBER), teamController.editTeam); +teamRouter.get("/i/:teamId", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), teamController.getTeamById); +teamRouter.post("/join", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), teamController.joinTeam); \ No newline at end of file diff --git a/packages/api/routes/users.routes.ts b/packages/api/routes/users.routes.ts index 8c72c69..6a2e557 100644 --- a/packages/api/routes/users.routes.ts +++ b/packages/api/routes/users.routes.ts @@ -5,10 +5,10 @@ import { AuthMiddleware } from "../middlewares/auth.middleware"; import { AUTH_PERMS } from "../extras/permissions"; export const userRoutes = new Hono(); -const contoller = new UserController(); -const auth = new AuthMiddleware(); +const userContoller = new UserController(); +const authMiddleware = new AuthMiddleware(); -userRoutes.post("/register", contoller.registerUser); -userRoutes.post("/verify", contoller.verifyUser); -userRoutes.post("/login", contoller.loginUser); -userRoutes.get("/whoami", auth.authenticate(AUTH_PERMS.AUTHENTICATED), contoller.whoami); \ No newline at end of file +userRoutes.post("/register", userContoller.registerUser); +userRoutes.post("/verify", userContoller.verifyUser); +userRoutes.post("/login", userContoller.loginUser); +userRoutes.get("/whoami", authMiddleware.authenticate(AUTH_PERMS.AUTHENTICATED), userContoller.whoami); \ No newline at end of file diff --git a/packages/api/services/challenges.service.ts b/packages/api/services/challenges.service.ts index 5664d86..90bc37d 100644 --- a/packages/api/services/challenges.service.ts +++ b/packages/api/services/challenges.service.ts @@ -171,8 +171,8 @@ export class ChallengeService { } const queryPreCheck = gql` - query checkScore($challenge_id: String!, $user_id: String!) { - scores(where: {challenge_id: {_eq: $challenge_id}, user_id: {_eq: $user_id}}) { + query checkScore($challenge_id: String!, $team_id: String!) { + scores(where: {challenge_id: {_eq: $challenge_id}, team_id: { _eq: $team_id }}) { id } } @@ -180,7 +180,7 @@ export class ChallengeService { const { scores } : Query_Root = await client.request(queryPreCheck, { challenge_id, - user_id, + team_id, }); if(scores.length > 0){ @@ -264,4 +264,111 @@ export class ChallengeService { throw new Error(error.message); } } + + public getTeamProgressS = (team_id: string) => { + return new Promise(async (resolve, reject) => { + try { + const query = gql` + query getTeamProgress($team_id: String!) { + machines { + id + name + description + challenges { + id + name + point + description + } + } + + scores(where: { + team_id: { + _eq: $team_id + } + }) { + id + challenge_id + team_id + user_id + challenge { + id + name + point + machine { + id + name + } + } + } + } + `; + + const data: Query_Root = await client.request(query, { + team_id + }); + + if(data.machines && data.scores) { + const challengeCollection : IParedMachineProgress[] = [] + + for(const machine of data.machines) { + const machineProgress: IParedMachineProgress = { + id: machine.id, + name: machine.name, + description: machine.description, + challenges: [] + } + + for(const challenge of machine.challenges) { + const challengeProgress : IParsedChallengeProgress = { + id: challenge.id, + name: challenge.name, + point: challenge.point, + description: challenge.description, + solved: false + } + + for(const score of data.scores) { + if(score.challenge_id === challenge.id) { + challengeProgress.solved = true; + break; + } + } + + machineProgress.challenges?.push(challengeProgress); + } + + challengeCollection.push(machineProgress); + } + + for (const machine of challengeCollection) { + const solvedChallenges = machine.challenges?.filter(challenge => challenge.solved); + const unsolvedChallenges = machine.challenges?.filter(challenge => !challenge.solved); + + if(solvedChallenges?.length) { + machine.challenges = solvedChallenges; + if(unsolvedChallenges?.length) { + machine.challenges.push(unsolvedChallenges[0]); + } + } + else { + machine.challenges = [machine.challenges![0]]; + } + } + + resolve(challengeCollection); + } + else { + throw new Error("No data found"); + } + } + catch (error: any) { + if(error.response) { + reject(error.response.errors[0].message); + } + else + reject(error.message); + } + }); + } } \ No newline at end of file diff --git a/packages/api/services/stats.service.ts b/packages/api/services/stats.service.ts index e69de29..805b1cb 100644 --- a/packages/api/services/stats.service.ts +++ b/packages/api/services/stats.service.ts @@ -0,0 +1,119 @@ +import { gql } from 'graphql-request'; + +import { client } from '../helpers/gqlClient'; +import { Query_Root } from '../graphql/types'; + +export class StatsService { + public getTeamScoreS = async (teamId: string) => { + try { + const query = gql` + query getTeamScore($teamId: String!) { + scores(where: {team_id: {_eq: $teamId}}) { + id + challenge { + id + name + machine { + id + name + } + point + } + team { + name + } + submission { + id + submited_flag + } + } + } + `; + + const { scores } : Query_Root = await client.request(query, { + teamId, + }); + + if(!scores){ + throw new Error("No team scores found"); + } + + const teamScore: ITeamStats = { + team_id: teamId, + team_name: scores[0].team.name, + score: 0, + submissions: [], + } + + for (const score of scores) { + teamScore.score += score.challenge.point; + teamScore.submissions.push({ + challenge_id: score.challenge.id, + challenge_name: score.challenge.name, + machine_id: score.challenge.machine.id, + machine_name: score.challenge.machine.name, + submited_flag: score.submission.submited_flag, + id: score.submission.id, + }) + } + + return teamScore; + } + catch (error: any) { + throw new Error(error.message); + } + } + + public getAllTeamScoreS = async () => { + try { + const query = gql` + query getAllTeamScore { + scores { + id + team { + id + name + } + challenge { + id + point + } + } + } + `; + + const { scores } : Query_Root = await client.request(query); + + if(!scores){ + throw new Error("No team scores found"); + } + + const teamsLeaderboard: IStatsLeaderboard[] = []; + + for (const score of scores) { + const teamIndex = teamsLeaderboard.findIndex(team => team.team_id === score.team.id); + if(teamIndex === -1) { + teamsLeaderboard.push({ + team_id: score.team.id, + team_name: score.team.name, + score: score.challenge.point, + rank: 0, + }) + } + else { + teamsLeaderboard[teamIndex].score += score.challenge.point; + } + } + + teamsLeaderboard.sort((a, b) => b.score - a.score); + teamsLeaderboard.forEach((team, index) => { + team.rank = index + 1; + }) + + return teamsLeaderboard; + } + catch (error: any) { + throw new Error(error.message); + } + } +} \ No newline at end of file diff --git a/packages/api/services/teams.service.ts b/packages/api/services/teams.service.ts index f615059..472e6d2 100644 --- a/packages/api/services/teams.service.ts +++ b/packages/api/services/teams.service.ts @@ -146,6 +146,51 @@ export class TeamService { } } + public EditTeamInfo = async (teamData: ITeamEditInput) => { + try { + const { id, team_name, join_code } = teamData; + if(!id || !team_name || !join_code) { + throw new Error("Invalid request"); + } + const query = gql` + mutation EditTeam($id: String!, $team_name: String!, $join_code: String!) { + update_teams_by_pk( + pk_columns: { + id: $id + }, _set: { + name: $team_name, + join_code: $join_code + } + ) { + id + name + join_code + } + } + `; + + const data : Mutation_Root = await client.request(query, { + id, + team_name, + join_code + }); + + if(data.update_teams_by_pk) { + return data.update_teams_by_pk; + } + else { + throw new Error("Unable to edit team"); + } + } + catch (error: any) { + if(error.response) { + throw new Error(error.response.errors[0].message); + } + else + throw new Error(error.message); + } + } + public joinTeamS = async (reqBody: ITeamJoinInput) => { try { const { user_id, join_code } = reqBody; @@ -153,6 +198,11 @@ export class TeamService { query GetTeam($join_code: String!) { teams(where: {join_code: {_eq: $join_code}}) { id + users_aggregate { + aggregate { + count + } + } } } `; @@ -162,6 +212,9 @@ export class TeamService { }); if(team_join.teams.length) { + if(team_join.teams[0].users_aggregate.aggregate?.count === 4) { + throw new Error("Team is full"); + } const team_id = team_join.teams[0].id; const query = gql` mutation JoinTeam($user_id: String!, $team_id: String!) { @@ -302,111 +355,4 @@ export class TeamService { throw new Error(error.message); } } - - public getTeamProgressS = (team_id: string) => { - return new Promise(async (resolve, reject) => { - try { - const query = gql` - query getTeamProgress($team_id: String!) { - machines { - id - name - description - challenges { - id - name - point - description - } - } - - scores(where: { - team_id: { - _eq: $team_id - } - }) { - id - challenge_id - team_id - user_id - challenge { - id - name - point - machine { - id - name - } - } - } - } - `; - - const data: Query_Root = await client.request(query, { - team_id - }); - - if(data.machines && data.scores) { - const challengeCollection : IParedMachineProgress[] = [] - - for(const machine of data.machines) { - const machineProgress: IParedMachineProgress = { - id: machine.id, - name: machine.name, - description: machine.description, - challenges: [] - } - - for(const challenge of machine.challenges) { - const challengeProgress : IParsedChallengeProgress = { - id: challenge.id, - name: challenge.name, - point: challenge.point, - description: challenge.description, - solved: false - } - - for(const score of data.scores) { - if(score.challenge_id === challenge.id) { - challengeProgress.solved = true; - break; - } - } - - machineProgress.challenges?.push(challengeProgress); - } - - challengeCollection.push(machineProgress); - } - - for (const machine of challengeCollection) { - const solvedChallenges = machine.challenges?.filter(challenge => challenge.solved); - const unsolvedChallenges = machine.challenges?.filter(challenge => !challenge.solved); - - if(solvedChallenges?.length) { - machine.challenges = solvedChallenges; - if(unsolvedChallenges?.length) { - machine.challenges.push(unsolvedChallenges[0]); - } - } - else { - machine.challenges = [machine.challenges![0]]; - } - } - - resolve(challengeCollection); - } - else { - throw new Error("No data found"); - } - } - catch (error: any) { - if(error.response) { - reject(error.response.errors[0].message); - } - else - reject(error.message); - } - }); - } } \ No newline at end of file diff --git a/packages/api/types/index.d.ts b/packages/api/types/index.d.ts index 7ec46e4..1009c28 100644 --- a/packages/api/types/index.d.ts +++ b/packages/api/types/index.d.ts @@ -27,6 +27,12 @@ interface ITeamCreateInput { join_code: string; } +interface ITeamEditInput { + id: string; + team_name: string; + join_code: string; +} + interface ITeamLeaveInput { user_id: string; } @@ -79,4 +85,27 @@ interface IParsedChallengeProgress { point: number; description: string; solved: boolean; +} + +interface IStatSubmissions { + id: string; + challenge_id: string + challenge_name: string; + machine_id: string + machine_name: string; + submited_flag: string; +} + +interface IStatsLeaderboard { + rank: number; + team_id: string; + team_name: string; + score: number; +} + +interface ITeamStats { + team_id: string; + team_name: string; + score: number; + submissions: IStatSubmissions[]; } \ No newline at end of file