Skip to content

Commit 55dabbc

Browse files
authored
Merge pull request #177 from Vandivier/175-fix-community-meta
feat: meetup settings
2 parents ff6cb8a + 9e1565e commit 55dabbc

23 files changed

+1633
-492
lines changed

db/checklists.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@
2222
"displayText": "Watched ###LINK### and reproduced it; I have a GitHub portfolio with a Vanilla JS + HTML blog hosted on GitHub pages",
2323
"isRequired": true,
2424
"linkText": "the Trial by Fire",
25-
"linkUri": "https://www.youtube.com/watch?v=V7yeF9AaBxM"
25+
"linkUri": "https://www.youtube.com/watch?v=InK2ciIKs_Q"
26+
},
27+
{
28+
"detailText": "",
29+
"displayText": "Read ###LINK### and updated my settings as needed.",
30+
"isRequired": true,
31+
"linkText": "Understanding User Settings",
32+
"linkUri": "https://ladderly.io/blog/2024-02-16-user-settings"
2633
},
2734
"I feel fairly confident that I have learned basic HTML, including forms and semantic HTML",
2835
"I feel fairly confident that I have learned basic CSS",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "User" ADD COLUMN "hasInPersonEventInterest" BOOLEAN NOT NULL DEFAULT false,
3+
ADD COLUMN "hasOnlineEventInterest" BOOLEAN NOT NULL DEFAULT false,
4+
ADD COLUMN "residenceCountry" TEXT NOT NULL DEFAULT '',
5+
ADD COLUMN "residenceUSState" TEXT NOT NULL DEFAULT '';

db/schema.prisma

+9-5
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,21 @@ model User {
2525
nameFirst String @default("")
2626
nameLast String @default("")
2727
28-
hasLiveStreamInterest Boolean @default(false)
29-
hasOpenToWork Boolean @default(false)
30-
hasPublicProfileEnabled Boolean @default(false)
31-
hasShoutOutsEnabled Boolean @default(false)
32-
hasSmallGroupInterest Boolean @default(false)
28+
hasInPersonEventInterest Boolean @default(false)
29+
hasLiveStreamInterest Boolean @default(false)
30+
hasOnlineEventInterest Boolean @default(false)
31+
hasOpenToWork Boolean @default(false)
32+
hasPublicProfileEnabled Boolean @default(false)
33+
hasShoutOutsEnabled Boolean @default(false)
34+
hasSmallGroupInterest Boolean @default(false)
3335
3436
profileBlurb String?
3537
profileContactEmail String?
3638
profileGitHubUri String?
3739
profileHomepageUri String?
3840
profileLinkedInUri String?
41+
residenceCountry String @default("")
42+
residenceUSState String @default("")
3943
totalContributions Float @default(0)
4044
totalStoreSpend Float @default(0)
4145

db/seed-utils/updateChecklists.ts

+110-53
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import db, { Prisma } from "db"
1+
import db, { Checklist, ChecklistItem, Prisma } from "db"
2+
import { r } from "vitest/dist/index-9f5bc072"
23
import { z } from "zod"
34

45
const ChecklistItemObjectSchema = z.object({
@@ -20,7 +21,9 @@ export type ChecklistSeedDataType = z.infer<typeof ChecklistSchema>
2021

2122
export const ChecklistsSchema = z.array(ChecklistSchema)
2223

23-
export const updateChecklistsInPlace = async (checklistData: ChecklistSeedDataType) => {
24+
export const updateChecklistsInPlace = async (
25+
checklistData: ChecklistSeedDataType
26+
) => {
2427
console.log(`Updating checklist: ${checklistData.name}`)
2528

2629
let checklist = await db.checklist.findFirst({
@@ -30,7 +33,11 @@ export const updateChecklistsInPlace = async (checklistData: ChecklistSeedDataTy
3033
})
3134

3235
if (checklist === null) {
33-
throw new Error(`Attempted to update a checklist, but it wasn't found: ${checklistData.name}`)
36+
console.warn(`Checklist not found: ${checklistData.name}. Creating now.`)
37+
checklist = await db.checklist.create({
38+
data: { name: checklistData.name, version: checklistData.version },
39+
include: { checklistItems: true },
40+
})
3441
}
3542

3643
checklist = await db.checklist.update({
@@ -40,85 +47,135 @@ export const updateChecklistsInPlace = async (checklistData: ChecklistSeedDataTy
4047
})
4148

4249
if (checklist === null) {
43-
throw new Error(`Checklist found but not returned by the updater: ${checklistData.name}`)
50+
throw new Error(
51+
`Checklist found but not returned by the updater: ${checklistData.name}`
52+
)
4453
}
4554

46-
// collect data for later bulk operations
47-
const newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[] = []
48-
const newUserChecklistItemsData: Prisma.UserChecklistItemCreateManyInput[] = []
55+
const checklistItemCreateManyInput: Prisma.ChecklistItemCreateManyInput[] = []
4956
for (let i = 0; i < checklistData.items.length; i++) {
5057
const item = checklistData.items[i]
5158
const itemData = ChecklistItemObjectSchema.parse(
5259
typeof item === "string" ? { displayText: item } : item
5360
)
5461

55-
const existingItem = checklist.checklistItems.find(
56-
(ci) => ci.displayText === itemData.displayText
57-
)
58-
59-
if (existingItem) {
60-
await db.checklistItem.update({
61-
where: { id: existingItem.id },
62-
data: { ...itemData, displayIndex: i },
63-
})
64-
} else {
65-
newChecklistItemsData.push({
66-
...itemData,
67-
checklistId: checklist.id,
68-
displayIndex: i,
69-
})
70-
}
62+
checklistItemCreateManyInput.push({
63+
...itemData,
64+
checklistId: checklist.id,
65+
displayIndex: i,
66+
})
7167
}
7268

7369
console.log(`Done updating existing items for: ${checklistData.name}`)
7470
await db.checklistItem.createMany({
75-
data: newChecklistItemsData,
71+
data: checklistItemCreateManyInput,
72+
skipDuplicates: true,
7673
})
7774
console.log(`Done creating new items for: ${checklistData.name}`)
78-
// TODO: remove obsolete ChecklistItems
79-
await updateUserChecklists(checklist.id, newChecklistItemsData)
80-
console.log(`Done updating UserChecklistItems for: ${checklistData.name}`)
81-
// TODO: remove obsolete UserChecklistItems
75+
76+
const checklistItems = await deleteObsoleteChecklistItems(
77+
checklist,
78+
checklistItemCreateManyInput
79+
)
80+
console.log(`deleteObsoleteChecklistItems done for: ${checklistData.name}`)
81+
await updateUserChecklists(checklist, checklistItems)
82+
console.log(`updateUserChecklists done for: ${checklistData.name}`)
8283

8384
return checklist
8485
}
8586

8687
const updateUserChecklists = async (
87-
checklistId: number,
88-
newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[]
88+
checklist: Checklist,
89+
checklistItems: ChecklistItem[]
8990
) => {
90-
const createdChecklistItems = await db.checklistItem.findMany({
91+
const userChecklists = await db.userChecklist.findMany({
9192
where: {
92-
checklistId: checklistId,
93-
displayText: { in: newChecklistItemsData.map((item) => item.displayText) },
93+
checklistId: checklist.id,
9494
},
9595
})
9696

97-
const displayTextToIdMap = createdChecklistItems.reduce((acc, item) => {
98-
acc[item.displayText] = item.id
99-
return acc
100-
}, {} as Record<string, number>)
101-
102-
// Prepare UserChecklistItems for bulk creation or update
103-
const newUserChecklistItemsData: Prisma.UserChecklistItemCreateManyInput[] = []
104-
const userChecklists = await db.userChecklist.findMany({
105-
where: { checklistId: checklistId },
106-
})
107-
for (const newItem of newChecklistItemsData) {
108-
const checklistItemId = displayTextToIdMap[newItem.displayText]
109-
if (checklistItemId === undefined) continue
97+
for (const userChecklist of userChecklists) {
98+
await db.userChecklistItem.deleteMany({
99+
where: {
100+
userChecklistId: userChecklist.id,
101+
},
102+
})
110103

111-
userChecklists.forEach((userChecklist) => {
112-
newUserChecklistItemsData.push({
104+
await db.userChecklistItem.createMany({
105+
data: checklistItems.map((itemData) => ({
113106
userChecklistId: userChecklist.id,
107+
checklistItemId: itemData.id,
114108
userId: userChecklist.userId,
115-
checklistItemId: checklistItemId,
116-
isComplete: false,
117-
})
109+
})),
118110
})
119111
}
112+
}
120113

121-
await db.userChecklistItem.createMany({
122-
data: newUserChecklistItemsData,
114+
const deleteObsoleteChecklistItems = async (
115+
checklist: Checklist & { checklistItems: ChecklistItem[] },
116+
newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[]
117+
): Promise<ChecklistItem[]> => {
118+
const checklistItemsWithIds = await db.checklistItem.findMany({
119+
where: {
120+
checklistId: checklist.id,
121+
},
122+
})
123+
124+
const newItemsSet = new Set(
125+
newChecklistItemsData.map((item) => {
126+
return (
127+
item.displayText +
128+
item.detailText +
129+
item.isRequired +
130+
item.linkText +
131+
item.linkUri
132+
)
133+
})
134+
)
135+
136+
const obsoleteChecklistItems: ChecklistItem[] =
137+
checklist.checklistItems.filter((item) => {
138+
const currCompoundKey =
139+
item.displayText +
140+
item.detailText +
141+
item.isRequired +
142+
item.linkText +
143+
item.linkUri
144+
145+
return !newItemsSet.has(currCompoundKey)
146+
})
147+
148+
if (obsoleteChecklistItems.length > 0) {
149+
const idsToDelete = obsoleteChecklistItems.map((item) => item.id)
150+
await db.userChecklistItem.deleteMany({
151+
where: {
152+
checklistItemId: {
153+
in: idsToDelete,
154+
},
155+
},
156+
})
157+
await db.checklistItem.deleteMany({
158+
where: {
159+
id: {
160+
in: idsToDelete,
161+
},
162+
},
163+
})
164+
}
165+
166+
const countOfItemsAfterDeletion = await db.checklistItem.count({
167+
where: {
168+
checklistId: checklist.id,
169+
},
123170
})
171+
172+
if (countOfItemsAfterDeletion !== newChecklistItemsData.length) {
173+
throw new Error(
174+
`Checklist items count mismatch for: ${checklist.name}.\n` +
175+
`Expected: ${newChecklistItemsData.length}, ` +
176+
`Found: ${countOfItemsAfterDeletion}`
177+
)
178+
}
179+
180+
return checklistItemsWithIds
124181
}

db/seeds.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import {
88
} from "./seed-utils/updateChecklists"
99

1010
const seed = async () => {
11-
const updateLatest = process.argv.includes("--update-latest-checklists")
11+
const updateLatestInPlace = process.argv.includes("--update-latest-checklists")
1212
const version = new Date().toISOString()
1313
const files = ["./checklists.json", "./premium-checklists.json"]
1414

1515
for (const file of files) {
1616
const filePath = path.resolve(__dirname, file)
1717

1818
if (!fs.existsSync(filePath)) {
19-
console.warn("File ${filePath} does not exist." + "\nContinuing to seed...")
19+
console.warn(`File ${filePath} does not exist." + "\nContinuing to seed...`)
2020
continue
2121
}
2222

@@ -28,7 +28,7 @@ const seed = async () => {
2828
const { name, items } = checklistData as ChecklistSeedDataType
2929
let checklist: Checklist | null = null
3030

31-
if (updateLatest) {
31+
if (updateLatestInPlace) {
3232
checklist = await updateChecklistsInPlace(checklistData)
3333
}
3434

docs/RUNBOOK.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ It documents how to handle certain common manual tasks and outages.
66

77
## Updating a Production Checklist In-Place
88

9-
We `npm run seed:update-in-place`
9+
We `npm run seed:update-in-place`.
10+
11+
This script is idempotent. If you run into an issue, in many cases you can just run the script again to resolve the issue.
1012

1113
If the displayText on an item is mutated, the in-place update target will correctly be unidentified.
1214

0 commit comments

Comments
 (0)