Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'yarn'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"fast-xml-parser": "^5.2.3",
"firebase": "^12.0.0",
"firebase-admin": "^13.0.2",
"googleapis": "^161.0.0",
"googleapis": "^164.0.0",
"handlebars": "^4.7.8",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
Expand Down
43 changes: 39 additions & 4 deletions src/modules/photo/photo.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import {
BadRequestException,
Body,
Controller,
Delete,
Get,
InternalServerErrorException,
Post,
Patch,
Param,
UseInterceptors,
Patch,
Post,
Query,
Req,
Body,
UseInterceptors,
UnauthorizedException,
} from "@nestjs/common";
import { Request } from "express";
Expand Down Expand Up @@ -211,4 +213,37 @@ export class PhotoController {
throw new InternalServerErrorException("Failed to reject photo");
}
}

@Delete(":photoId")
@Roles(Role.TEAM)
@ApiDoc({
summary: "Delete a photo",
params: [{ name: "photoId" }],
query: [
{
name: "originalName",
description: "Original filename including the extension",
},
],
response: { noContent: true },
})
async deletePhoto(
@Param("photoId") photoId: string,
@Query("originalName") originalName: string,
): Promise<void> {
if (!photoId) {
throw new BadRequestException("photoId is required");
}

if (!originalName) {
throw new BadRequestException("originalName is required");
}

try {
await this.photoService.deletePhoto(photoId, originalName);
} catch (error) {
console.error("Error deleting photo:", error);
throw new InternalServerErrorException("Failed to delete photo");
}
}
}
6 changes: 6 additions & 0 deletions src/modules/photo/photo.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@ export class PhotoService {
},
});
}

deletePhoto(photoId: string, originalName: string) {
return this.photoBucket
.file(this.getPhotoFileName(photoId, originalName))
.delete({ ignoreNotFound: true });
}
}
76 changes: 76 additions & 0 deletions src/modules/team/team.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
Param,
Patch,
Post,
Delete,
Query,
UseFilters,
ValidationPipe,
} from "@nestjs/common";
import { InjectRepository, Repository } from "common/objection";
import { Team, TeamEntity } from "entities/team.entity";
import { User } from "entities/user.entity";
import { Reservation } from "entities/reservation.entity";
import { Hackathon } from "entities/hackathon.entity";
import { ApiProperty, ApiTags, OmitType, PartialType } from "@nestjs/swagger";
import { Role, Roles } from "common/gcp";
Expand Down Expand Up @@ -92,6 +94,8 @@ export class TeamController {
private readonly teamRepo: Repository<Team>,
@InjectRepository(User)
private readonly userRepo: Repository<User>,
@InjectRepository(Reservation)
private readonly reservationRepo: Repository<Reservation>,
) {}

@Get("/")
Expand Down Expand Up @@ -356,6 +360,39 @@ export class TeamController {
}

const team = await this.teamRepo.patchOne(id, data).exec();

// Check if all members have been removed - if so, soft delete the team
const updatedMembers = [
team.member1,
team.member2,
team.member3,
team.member4,
team.member5,
].filter(Boolean);

if (updatedMembers.length === 0) {
// No members left - soft delete team and all associated reservations
console.log(
`All members removed from team ${id}, auto soft-deleting team and reservations`,
);

const deletedReservationsCount = await this.reservationRepo
.findAll()
.raw()
.where("teamId", id)
.delete();

console.log(
`Deleted ${deletedReservationsCount} reservations for team ${id}`,
);

const deletedTeam = await this.teamRepo
.patchOne(id, { isActive: false })
.exec();

return deletedTeam;
}

return team;
}

Expand Down Expand Up @@ -462,4 +499,43 @@ export class TeamController {
const updatedTeam = await this.teamRepo.patchOne(id, updateData).exec();
return updatedTeam;
}

@Delete(":id")
@Roles(Role.NONE)
@ApiDoc({
summary: "Soft delete a team and remove all associated reservations",
params: [
{
name: "id",
description: "ID must be set to a team's ID",
},
],
response: {
ok: { type: TeamEntity },
},
auth: Role.TEAM,
})
async deleteOne(@Param("id") id: string) {
const existingTeam = await this.teamRepo.findOne(id).exec();
if (!existingTeam) {
throw new NotFoundException("Team not found");
}
if (!existingTeam.isActive) {
throw new BadRequestException("Team is already inactive");
}

// Delete all reservations associated with team before soft-deleting
const deletedReservationsCount = await this.reservationRepo
.findAll()
.raw()
.where("teamId", id)
.delete();

console.log(
`Deleted ${deletedReservationsCount} reservations for team ${id}`,
);

const team = await this.teamRepo.patchOne(id, { isActive: false }).exec();
return team;
}
}
3 changes: 2 additions & 1 deletion src/modules/team/team.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { Team } from "entities/team.entity";
import { User } from "entities/user.entity";
import { TeamController } from "./team.controller";
import { Hackathon } from "entities/hackathon.entity";
import { Reservation } from "entities/reservation.entity";

@Module({
imports: [ObjectionModule.forFeature([Team, User, Hackathon])],
imports: [ObjectionModule.forFeature([Team, User, Hackathon, Reservation])],
controllers: [TeamController],
})
export class TeamModule {}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4498,10 +4498,10 @@ googleapis-common@^8.0.0:
qs "^6.7.0"
url-template "^2.0.8"

googleapis@^161.0.0:
version "161.0.0"
resolved "https://registry.npmjs.org/googleapis/-/googleapis-161.0.0.tgz"
integrity sha512-JZy2cWMxgUF8E09KHzplI+z+FVG8NWDB/bsf4xevt9Um4bInb0X1qaG9qpDn49DHT5HsU0mOp3EOBGb8+AdE3Q==
googleapis@^164.0.0:
version "164.0.0"
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-164.0.0.tgz#22945308a3b3cba938f762fd710fb761db2f6a82"
integrity sha512-aR2larBEvu6+HVC4Puu87T41/OA3WjVw2tQRYAMKit2kC95daFacIoDF70DeC4p9M/H5eZSalBJV1FOypR808A==
dependencies:
google-auth-library "^10.2.0"
googleapis-common "^8.0.0"
Expand Down