Skip to content

Commit

Permalink
Feat: new endpoint DELETE /api/v1/action-groups/{id}/yesterday (#154)
Browse files Browse the repository at this point in the history
# Background

![image](https://github.com/user-attachments/assets/dd1f60d5-c639-4f8e-8ea1-26c37763ea2e)
ajktown/ConsistencyGPT#85

## What's done
Implement a new endpoint `DELETE /api/v1/action-groups/{id}/yesterday`

## What are the related PRs?
- N/A

## Checklist Before PR Review
- [x] The following has been handled:
  - `Draft` is set for this PR
  - `Title` is checked
  - `Background` is filled
  - `Assignee` is set
  - `Labels` are set
  - `Development` is linked if related issue exists

## Checklist (Right Before PR Review Request)
- [x] The following has been handled:
  - Final Operation Check is done
  - Mobile View Operation Check is done
  - Make this PR as an open PR
  - `What's done` is filled & matches to the actual changes

## Checklist (Reviewers)
- [x] Review if the following has been handled:
  - Review Code
  - `Checklist Before PR Review` is correctly handled
  - `Checklist (Right Before PR Review Request)` is correctly handled
- Check if there are any other missing TODOs (Checklists) that are not
yet listed
  - Check if issue under `Development` is fully handled if exists
  • Loading branch information
mlajkim authored Jan 8, 2025
1 parent 869ad13 commit 1415734
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 4 deletions.
12 changes: 12 additions & 0 deletions src/controllers/action-group.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum ActionGroupControllerPath {
PostArchiveActionGroup = `action-groups/:id/archive`,
GetActionGroupById = `action-groups/:id`,
DeleteTodayActionByActionGroup = `action-groups/:id/actions/today`,
DeleteYesterdayActionByActionGroup = `action-groups/:id/actions/yesterday`,
}

@Controller(AjkTownApiVersion.V1)
Expand Down Expand Up @@ -76,4 +77,15 @@ export class ActionGroupController {
atd,
)
}

@Delete(ActionGroupControllerPath.DeleteYesterdayActionByActionGroup)
async deleteYesterdayActionByActionGroup(
@Req() req: Request,
@Param('id') id: string,
) {
const atd = await AccessTokenDomain.fromReq(req, this.jwtService)
return (
await this.actionGroupService.deleteYesterdayAction(atd, id)
).toResDTO(atd)
}
}
34 changes: 31 additions & 3 deletions src/domains/action/action-group.domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { NotExistOrNoPermissionError } from '@/errors/404/not-exist-or-no-permis
import { SupportedTimeZoneConst } from '@/constants/time-zone.const'
import { NumberNotInRangeError } from '@/errors/400/index.num-not-in-range.error'
import { PostActionBodyDTO } from '@/dto/post-action.dto'
import { DataNotSelectableError } from '@/errors/400/data-not-selectable.error'

/**
* ActionGroupDomain first contains only level 1~4 data.
Expand Down Expand Up @@ -134,6 +135,19 @@ export class ActionGroupDomain extends DomainRoot {
].includes(this.state)
}

get isYesterdayDeletable(): boolean {
const got = this.dateDomainMap.get(
timeHandler.getYYYYMMDD(
timeHandler.getYesterday(this.props.timezone),
this.props.timezone,
),
)

if (got === undefined) return false // not exist => not deletable
if (got.toResDTO(0).isDummy) return false // dummy => not done => deleting does not make difference
return true
}

private static fromMdb(
doc: ActionGroupDoc,
map: Map<string, ActionDomain>,
Expand Down Expand Up @@ -272,23 +286,36 @@ export class ActionGroupDomain extends DomainRoot {
}

/**
* Delete every action associated to the action group that is TODAY!
* Delete every action associated to the action group that is TODAY or YESTERDAY!
*/
async deleteTodayAction(
async deleteAction(
atd: AccessTokenDomain,
model: ActionModel,
which: 'today' | 'yesterday', // Only today or yesterday is allowed to be deleted
): Promise<this> {
if (this.props.ownerId !== atd.userId)
throw new NotExistOrNoPermissionError()

const [startDate, endDate] = (() => {
switch (which) {
case 'today':
return timeHandler.getDateFromDaysAgo(0, this.props.timezone) // Get start and end for today
case 'yesterday':
return timeHandler.getDateFromDaysAgo(1, this.props.timezone) // Get start and end for yesterday
default:
throw new DataNotSelectableError('which', ['today', 'yesterday'])
}
})()

// get todays actions
let docs: ActionDoc[] = []
try {
docs = await model.find({
ownerId: this.props.ownerId,
groupId: this.props.id,
createdAt: {
$gte: timeHandler.getStartOfToday(this.props.timezone),
$gte: startDate,
$lte: endDate, // this is a must as deleting yesterday's action is allowed
},
})
} catch (err) {
Expand Down Expand Up @@ -413,6 +440,7 @@ export class ActionGroupDomain extends DomainRoot {
isDummyCommittable: this.isDummyCommittable,
isLateCommittable: this.isLateCommittable,
isDeletable: this.isDeletable,
isYesterdayDeletable: this.isYesterdayDeletable,
},
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/errors/400/data-not-selectable.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BadRequestError } from './index.error'

/** Thrown when certain data is not acceptable
* @example
* throw new DataNotSelectableError('which', ['today', 'yesterday'])
* => Invalid key [which]: Only acceptable in [today, yesterday]
*/
export class DataNotSelectableError extends BadRequestError {
constructor(key: string, acceptable: string[]) {
super(`Invalid key [${key}]: Only acceptable in [${acceptable.toString()}]`) // i.e) User email is not present
}
}
10 changes: 10 additions & 0 deletions src/handlers/time.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ export const timeHandler = {
getStartOfToday: (timezone: string): Date => {
return DateTime.now().setZone(timezone).startOf('day').toJSDate()
},
getStartOfYesterday: (timezone: string): Date => {
return DateTime.now()
.setZone(timezone)
.minus({ days: 1 })
.startOf('day')
.toJSDate()
},
getToday: (timezone: string): Date => {
return DateTime.now().setZone(timezone).toJSDate()
},
getYesterday: (timezone: string): Date => {
return DateTime.now().setZone(timezone).minus({ days: 1 }).toJSDate()
},

/** return daysAgo for the given JS Date
* This also make sure that it is in the same timezone as the given timezone
Expand Down
1 change: 1 addition & 0 deletions src/responses/get-action-groups.res.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface ActionGroupDerivedState {
isDummyCommittable: boolean
isLateCommittable: boolean
isDeletable: boolean
isYesterdayDeletable: boolean
}
export interface GetActionGroupRes {
props: IActionGroup
Expand Down
14 changes: 13 additions & 1 deletion src/services/action-group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ export class ActionGroupService {
actionGroupId: string,
): Promise<ActionGroupDomain> {
const actionGroup = await this.getById(atd, actionGroupId)
return actionGroup.deleteTodayAction(atd, this.actionModel)
return actionGroup.deleteAction(atd, this.actionModel, 'today')
}

/**
* Delete every actions associated to the action group that is YESTERDAY!
* This is developed as some users want to make the CGT records correct for all the time.
*/
async deleteYesterdayAction(
atd: AccessTokenDomain,
actionGroupId: string,
): Promise<ActionGroupDomain> {
const actionGroup = await this.getById(atd, actionGroupId)
return actionGroup.deleteAction(atd, this.actionModel, 'yesterday')
}
}

0 comments on commit 1415734

Please sign in to comment.