Skip to content

Commit ff59f66

Browse files
committed
Closed #8, add use coupon api
1 parent 25aa617 commit ff59f66

File tree

7 files changed

+73
-18
lines changed

7 files changed

+73
-18
lines changed

api/command/useCoupon.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1-
import { Command } from '@/core'
1+
import { Repository, Command } from '@/core'
2+
import { Status } from '@/puzzle'
3+
import { PuzzleReceiverNotFoundError } from './errors'
24

35
export type UseCouponInput = {
4-
token: string
5-
eventId: string
6+
publicToken: string
67
}
78

89
export class UseCouponCommand implements Command<UseCouponInput, boolean> {
9-
constructor() {}
10+
private readonly statuses: Repository<Status>
11+
12+
constructor(status: Repository<Status>) {
13+
this.statuses = status
14+
}
15+
16+
public async execute({ publicToken }: UseCouponInput): Promise<boolean> {
17+
const status = await this.statuses.findById(publicToken)
18+
if (!status) {
19+
throw new PuzzleReceiverNotFoundError()
20+
}
21+
22+
if (status.isNew()) {
23+
return false
24+
}
25+
26+
status.redeem()
27+
await this.statuses.save(status)
1028

11-
public async execute(_input: UseCouponInput): Promise<boolean> {
1229
return true
1330
}
1431
}

api/repository/puzzleStatuses.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { type D1Database } from '@cloudflare/workers-types'
22
import { type Class } from '@/core/utils'
33
import { Repository } from '@/core'
4-
import { Status, ActivityEvent, AttendeeInitialized, PuzzleCollected, Revoked } from '@/puzzle'
4+
import {
5+
Status,
6+
ActivityEvent,
7+
AttendeeInitialized,
8+
PuzzleCollected,
9+
Revoked,
10+
Redeemed,
11+
} from '@/puzzle'
512

613
type EventSchema = {
714
id: string
@@ -16,6 +23,7 @@ const eventConstructors: Record<string, Class<ActivityEvent>> = {
1623
AttendeeInitialized: AttendeeInitialized,
1724
PuzzleCollected: PuzzleCollected,
1825
Revoked: Revoked,
26+
Redeemed: Redeemed,
1927
}
2028

2129
export class D1PuzzleStatusRepository implements Repository<Status> {

features/puzzle_coupon.feature

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
Feature: Puzzle Coupon
22
Scenario: PUT /event/puzzle/coupon to redeem a puzzle coupon
3-
Given there have some booths
4-
| token | name | event_id |
5-
| 1024914b-ee65-4728-b687-8341f5affa89 | COSCUP | SITCON |
6-
And there have some attendees
3+
Given there have some attendees
74
| token | event_id | display_name |
85
| f185f505-d8c0-43ce-9e7b-bb9e8909072d | SITCON | Aotoki |
9-
When I make a PUT request to "/event/puzzle/coupon?token=1024914b-ee65-4728-b687-8341f5affa89&event_id=SITCON":
6+
And there have some puzzle activity events
7+
| id | type | aggregate_id | version | payload | occurred_at |
8+
| b44845bd-8bd2-428d-ad65-f6a619bf8a96 | AttendeeInitialized | f185f505-d8c0-43ce-9e7b-bb9e8909072d | 0 | { "displayName": "Aotoki" } | 2023-09-10 20:48:00 |
9+
When I make a PUT request to "/event/puzzle/coupon?token=f185f505-d8c0-43ce-9e7b-bb9e8909072d&event_id=SITCON":
1010
"""
1111
{}
1212
"""
@@ -17,3 +17,27 @@ Feature: Puzzle Coupon
1717
}
1818
"""
1919
And the response status should be 200
20+
21+
Scenario: PUT /event/puzzle/coupon to redeem a puzzle coupon and status is updated
22+
Given there have some attendees
23+
| token | event_id | display_name |
24+
| f185f505-d8c0-43ce-9e7b-bb9e8909072d | SITCON | Aotoki |
25+
And there have some puzzle activity events
26+
| id | type | aggregate_id | version | payload | occurred_at |
27+
| b44845bd-8bd2-428d-ad65-f6a619bf8a96 | AttendeeInitialized | f185f505-d8c0-43ce-9e7b-bb9e8909072d | 0 | { "displayName": "Aotoki" } | 2023-09-10 20:48:00 |
28+
When I make a PUT request to "/event/puzzle/coupon?token=f185f505-d8c0-43ce-9e7b-bb9e8909072d&event_id=SITCON":
29+
"""
30+
{}
31+
"""
32+
And I make a GET request to "/event/puzzle?token=f185f505-d8c0-43ce-9e7b-bb9e8909072d"
33+
Then the response json should be:
34+
"""
35+
{
36+
"user_id": "Aotoki",
37+
"puzzles": [],
38+
"deliverers": [],
39+
"valid": null,
40+
"coupon": 1693065600
41+
}
42+
"""
43+
And the response status should be 200

src/puzzle/event.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class PuzzleCollected extends ActivityEvent {
3333
}
3434

3535
export class Revoked extends ActivityEvent {}
36+
export class Redeemed extends ActivityEvent {}
3637

3738
export abstract class StatEvent implements DomainEvent {
3839
public readonly id: string

src/puzzle/status.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AggregateRoot, Replayable, getCurrentTime } from '@/core'
2-
import { ActivityEvent, AttendeeInitialized, PuzzleCollected, Revoked } from './event'
2+
import { ActivityEvent, AttendeeInitialized, PuzzleCollected, Revoked, Redeemed } from './event'
33
import { Piece } from './piece'
44

55
@Replayable
@@ -46,6 +46,10 @@ export class Status extends AggregateRoot<string, ActivityEvent> {
4646
this.apply(new Revoked(crypto.randomUUID(), this.id, getCurrentTime()))
4747
}
4848

49+
redeem(): void {
50+
this.apply(new Redeemed(crypto.randomUUID(), this.id, getCurrentTime()))
51+
}
52+
4953
collectPiece(name: string, giverName: string): void {
5054
this.apply(new PuzzleCollected(crypto.randomUUID(), this.id, getCurrentTime(), name, giverName))
5155
}
@@ -72,4 +76,8 @@ export class Status extends AggregateRoot<string, ActivityEvent> {
7276
this._completedAt = this._revokedAt
7377
}
7478
}
79+
80+
private _onRedeemed(event: Redeemed) {
81+
this._redeemedAt = new Date(event.occurredAt)
82+
}
7583
}

worker/controller/useCoupon.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export class UseCoupon extends OpenAPIRoute {
1616
tags: ['Puzzle'],
1717
requestBody: {},
1818
parameters: {
19-
event_id: schema.EventIdQuery,
2019
token: schema.OptionalAttendeeTokenQuery,
2120
},
2221
responses: {
@@ -28,10 +27,8 @@ export class UseCoupon extends OpenAPIRoute {
2827
}
2928

3029
async handle(request: UseCouponRequest, _env: unknown, _context: unknown) {
31-
const token = request.query.token as string
32-
const eventId = request.query.event_id as string
33-
34-
const success = await request.useCoupon.execute({ token, eventId })
30+
const publicToken = request.query.token as string
31+
const success = await request.useCoupon.execute({ publicToken })
3532

3633
if (!success) {
3734
throw new StatusError(400, 'Failed to redeem coupon')

worker/middlewares/command.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const withCommands = (request: IRequest, env: Env) => {
3131
puzzleStatusRepository,
3232
puzzleStatsRepository
3333
)
34-
const useCoupon = new Command.UseCouponCommand()
34+
const useCoupon = new Command.UseCouponCommand(puzzleStatusRepository)
3535

3636
Object.assign(request, {
3737
createAnnouncementCommand,

0 commit comments

Comments
 (0)