Skip to content

Commit 6f1ed8a

Browse files
authored
Cleanup ID types (#109)
1 parent de66e6c commit 6f1ed8a

20 files changed

+94
-67
lines changed

src/apiClient/approvals.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { toIsoDate } from "src/lib/fmtDate";
22

33
import { request } from "./client";
44
import { GearTypeWithShorthand } from "./gear";
5+
import { ApprovalID, GearItemID, PersonID } from "./idTypes";
56
import { PersonBase } from "./people";
67

78
export type Approval = {
8-
id: number;
9+
id: ApprovalID;
910
startDate: string;
1011
endDate: string;
1112
note: string;
@@ -17,7 +18,7 @@ export type Approval = {
1718
export type RenterApproval = Omit<Approval, "renter">;
1819

1920
interface GearItem {
20-
id: string;
21+
id: GearItemID;
2122
type: GearTypeWithShorthand;
2223
}
2324

@@ -57,7 +58,7 @@ export type CreateNewApprovalArgs = {
5758
startDate: Date;
5859
endDate: Date;
5960
note?: string;
60-
renter: string;
61+
renter: PersonID;
6162
items: ApprovalItemToCreate[];
6263
};
6364

src/apiClient/gear.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { request } from "./client";
2+
import { GearItemID, GearTypeID, LocationID, PurchasableID } from "./idTypes";
23
import { PersonBase, PersonSummary } from "./people";
34
import { ListWrapper, Note } from "./types";
45

56
export interface GearSummary {
6-
id: string;
7+
id: GearItemID;
78
available: boolean;
89
broken: string;
910
// TODO: This is weird, we shouldn't have the rentals in there
@@ -19,7 +20,7 @@ export interface GearSummary {
1920
type: GearTypeWithFee;
2021
picture?: string;
2122
location: {
22-
id: number;
23+
id: LocationID;
2324
shorthand: string;
2425
};
2526
}
@@ -36,14 +37,14 @@ export interface GearRental {
3637
}
3738

3839
export interface PurchasableItem {
39-
id: string;
40+
id: PurchasableID;
4041
price: number;
4142
name: string;
4243
}
4344

4445
/** The minimal representation of a gear type*/
4546
export interface GearTypeBase {
46-
id: number;
47+
id: GearTypeID;
4748
typeName: string;
4849
}
4950

@@ -63,49 +64,49 @@ export interface GearType extends GearTypeWithShorthand {
6364
}
6465

6566
export interface GearLocation {
66-
id: number;
67+
id: LocationID;
6768
shorthand: string;
6869
}
6970

7071
async function getGearRentalHistory(
71-
id: string,
72+
id: GearItemID,
7273
page?: number,
7374
): Promise<ListWrapper<GearRental>> {
7475
return request(`/gear/${id}/rentals/`, "GET", { ...(page && { page }) });
7576
}
7677

77-
async function addNote(id: string, note: string) {
78+
async function addNote(id: GearItemID, note: string) {
7879
return request(`/gear/${id}/note/`, "POST", {
7980
note,
8081
});
8182
}
8283

83-
async function markRetired(id: string, note?: string) {
84+
async function markRetired(id: GearItemID, note?: string) {
8485
return request(`/gear/${id}/retired/`, "POST", {
8586
note,
8687
});
8788
}
88-
async function markBroken(id: string, note: string) {
89+
async function markBroken(id: GearItemID, note: string) {
8990
return request(`/gear/${id}/broken/`, "POST", {
9091
note,
9192
});
9293
}
93-
async function markMissing(id: string, note?: string) {
94+
async function markMissing(id: GearItemID, note?: string) {
9495
return request(`/gear/${id}/missing/`, "POST", {
9596
note,
9697
});
9798
}
98-
async function markUnretired(id: string, note?: string) {
99+
async function markUnretired(id: GearItemID, note?: string) {
99100
return request(`/gear/${id}/retired/`, "DELETE", {
100101
note,
101102
});
102103
}
103-
async function markFixed(id: string, note?: string) {
104+
async function markFixed(id: GearItemID, note?: string) {
104105
return request(`/gear/${id}/broken/`, "DELETE", {
105106
note,
106107
});
107108
}
108-
async function markFound(id: string, note?: string) {
109+
async function markFound(id: GearItemID, note?: string) {
109110
return request(`/gear/${id}/missing/`, "DELETE", {
110111
note,
111112
});
@@ -129,7 +130,7 @@ async function createGear(
129130
}
130131

131132
async function editGearItem(
132-
id: string,
133+
id: GearItemID,
133134
item: {
134135
specification?: string;
135136
description?: string;

src/apiClient/idTypes.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Branded types improve type safety by differenciating ID-types that have the same
2+
// run-time type but represent different entities.
3+
declare const __brand: unique symbol;
4+
5+
export type PersonID = number & { readonly [__brand]: "PersonID" };
6+
export type GearItemID = string & { readonly [__brand]: "GearItemID" };
7+
export type PeopleGroupID = number & { readonly [__brand]: "PeopleGroupID" };
8+
export type ApprovalID = number & { readonly [__brand]: "ApprovalID" };
9+
export type LocationID = number & { readonly [__brand]: "LocationID" };
10+
export type GearTypeID = number & { readonly [__brand]: "GearTypeID" };
11+
export type PurchasableID = number & { readonly [__brand]: "PurchasableID" };
12+
export type OfficeHourID = number & { readonly [__brand]: "OfficeHourID" };
13+
export type SignupID = number & { readonly [__brand]: "SignupID" };
14+
export type GearNoteID = number & { readonly [__brand]: "GearNoteID" };
15+
export type PersonNoteID = number & { readonly [__brand]: "PersonNoteID" };
16+
export type NoteID = GearNoteID | PersonNoteID;

src/apiClient/officeHours.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { request } from "./client";
2+
import { OfficeHourID, SignupID } from "./idTypes";
23

3-
export async function signUp(officeHourId: string) {
4+
export async function signUp(officeHourId: OfficeHourID) {
45
return request(`/office-hours/${officeHourId}/signup/`, "POST");
56
}
67

7-
export async function cancelSignUp(signupId: string) {
8+
export async function cancelSignUp(signupId: SignupID) {
89
return request(`/office-hour-signups/${signupId}/`, "DELETE");
910
}
1011

src/apiClient/people.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import dayjs from "dayjs";
22

33
import { request } from "./client";
44
import { GearTypeWithFee } from "./gear";
5+
import { GearItemID, PeopleGroupID, PersonID } from "./idTypes";
56
import { ListWrapper, Note } from "./types";
67

78
/** The minimal representation of a person*/
89
export interface PersonBase {
9-
id: string;
10+
id: PersonID;
1011
firstName: string;
1112
lastName: string;
1213
}
@@ -28,7 +29,7 @@ export interface Expireable {
2829

2930
export interface PeopleGroup {
3031
groupName: string;
31-
id: number;
32+
id: PeopleGroupID;
3233
}
3334

3435
/** The representation of a person in the retrieve endpoint*/
@@ -44,7 +45,7 @@ export interface Person extends PersonSummary {
4445
}
4546

4647
export interface Rental {
47-
id: string;
48+
id: GearItemID;
4849
checkedout: string;
4950
returned?: string;
5051
totalAmount: number;
@@ -53,16 +54,10 @@ export interface Rental {
5354
}
5455

5556
export interface GearToReturn {
56-
id: string;
57+
id: GearItemID;
5758
daysCharged?: number;
5859
}
5960

60-
export interface Affiliation {
61-
id: string;
62-
name: string;
63-
dues: number;
64-
}
65-
6661
export type CreatePersonArgs = {
6762
firstName: string;
6863
lastName: string;
@@ -73,51 +68,51 @@ async function createPerson(args: CreatePersonArgs): Promise<PersonSummary> {
7368
return request(`/people/`, "POST", args);
7469
}
7570

76-
async function addFFChecks(id: string, date: Date, checkNumber: string) {
71+
async function addFFChecks(id: PersonID, date: Date, checkNumber: string) {
7772
return request(`/people/${id}/frequent_flyer_check/`, "POST", {
7873
expires: dayjs(date).format("YYYY-MM-DD"),
7974
...(checkNumber && { checkNumber }),
8075
});
8176
}
8277

83-
async function addWaiver(id: string, date: Date) {
78+
async function addWaiver(id: PersonID, date: Date) {
8479
return request(`/people/${id}/waiver/`, "POST", {
8580
expires: dayjs(date).format("YYYY-MM-DD"),
8681
});
8782
}
8883

89-
async function addMembership(id: string, date: Date, membershipType: string) {
84+
async function addMembership(id: PersonID, date: Date, membershipType: string) {
9085
return request(`/people/${id}/membership/`, "POST", {
9186
expires: dayjs(date).format("YYYY-MM-DD"),
9287
membershipType,
9388
});
9489
}
9590

96-
async function addNote(id: string, note: string) {
91+
async function addNote(id: PersonID, note: string) {
9792
return request(`/people/${id}/note/`, "POST", {
9893
note,
9994
});
10095
}
10196

102-
async function archiveNote(personId: string, noteId: string) {
97+
async function archiveNote(personId: PersonID, noteId: number) {
10398
return request(`/people/${personId}/note/${noteId}/archive/`, "POST");
10499
}
105100

106101
async function getPersonRentalHistory(
107-
id: string,
102+
id: PersonID,
108103
page?: number,
109104
): Promise<ListWrapper<Rental>> {
110105
return request(`/people/${id}/rentals/`, "GET", { ...(page && { page }) });
111106
}
112107

113-
async function checkoutGear(personID: string, gearIDs: string[]) {
108+
async function checkoutGear(personID: PersonID, gearIDs: string[]) {
114109
return request(`/people/${personID}/rentals/`, "POST", { gearIds: gearIDs });
115110
}
116111

117112
async function returnGear(
118-
personID: string,
113+
personID: PersonID,
119114
gear: GearToReturn[],
120-
purchases: string[] = [],
115+
purchases: number[] = [],
121116
checkNumber: string = "",
122117
useMitocCredit?: number,
123118
) {
@@ -130,7 +125,7 @@ async function returnGear(
130125
}
131126

132127
async function editPerson(
133-
id: string,
128+
id: PersonID,
134129
firstName: string,
135130
lastName: string,
136131
email: string,
@@ -144,11 +139,11 @@ async function editPerson(
144139
});
145140
}
146141

147-
async function updatePersonGroups(id: string, groups: number[]) {
142+
async function updatePersonGroups(id: PersonID, groups: number[]) {
148143
return request(`/people/${id}/groups/`, "PUT", { groups });
149144
}
150145

151-
async function addMitocCredit(id: string, amount: number) {
146+
async function addMitocCredit(id: PersonID, amount: number) {
152147
return request(`/people/${id}/credit/add/`, "PATCH", { amount });
153148
}
154149

src/apiClient/types.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { NoteID, OfficeHourID, SignupID } from "./idTypes";
12
import { PeopleGroup, PersonBase, PersonWithOfficeAccess } from "./people";
23

34
export interface ListWrapper<T> {
@@ -21,7 +22,7 @@ export interface APIErrorType {
2122
}
2223

2324
export interface Note {
24-
id: string;
25+
id: NoteID;
2526
note: string;
2627
dateInserted: string;
2728
author: PersonBase;
@@ -34,27 +35,27 @@ export interface Affiliations {
3435
}
3536

3637
export interface OfficeHour {
37-
googleId: string;
38+
googleId: OfficeHourID;
3839
title: string;
3940
startTime: string;
4041
endTime: string;
4142
signups: {
42-
id: string;
43+
id: SignupID;
4344
deskWorker: PersonWithOfficeAccess;
4445
}[];
4546
}
4647

4748
export interface PersonSignup {
49+
id: SignupID;
4850
creditRequested?: string;
4951
approved?: string;
5052
duration?: string;
5153
date: string;
52-
id: number;
5354
note?: string;
5455
eventType: string;
5556
credit?: number;
5657
}
5758

5859
export interface Signup extends PersonSignup {
59-
deskWorker: { id: string; firstName: string; lastName: string };
60+
deskWorker: PersonWithOfficeAccess;
6061
}

src/components/AddApprovalLink.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Link } from "react-router-dom";
22

3-
export function AddApprovalLink({ personId }: { personId?: string }) {
3+
import { PersonID } from "src/apiClient/idTypes";
4+
5+
export function AddApprovalLink({ personId }: { personId?: PersonID }) {
46
const to =
57
personId == null ? "/add-approval" : `/add-approval?personId=${personId}`;
68
return (

src/components/GearLink.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { LinkProps } from "react-router-dom";
22

3+
import { GearItemID } from "src/apiClient/idTypes";
34
import { gearDbApi } from "src/redux/api";
45

56
import { PrefetchLink } from "./PrefetchLink";
67

7-
type Props = { id: string } & Omit<LinkProps, "to">;
8+
type Props = { id: GearItemID } & Omit<LinkProps, "to">;
89

910
export function GearLink({ id, ...otherProps }: Props) {
1011
const prefetchGearItem = gearDbApi.usePrefetch("getGearItem");

src/components/Notes.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React, { useState } from "react";
22

3+
import { NoteID } from "src/apiClient/idTypes";
34
import type { Person } from "src/apiClient/people";
4-
import { ToggleExpandButton, ArchiveButton } from "src/components/Buttons";
5+
import { ArchiveButton, ToggleExpandButton } from "src/components/Buttons";
56
import { TextArea } from "src/components/Inputs/TextArea";
67
import { formatDateTime } from "src/lib/fmtDate";
78

89
type Props = {
910
notes: Person["notes"];
1011
onAdd: (note: string) => Promise<any>;
11-
onArchive?: (id: string) => void;
12+
onArchive?: (id: NoteID) => void;
1213
};
1314

1415
export function Notes({ notes, onAdd, onArchive }: Props) {

0 commit comments

Comments
 (0)