Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: meetup settings #177

Merged
merged 25 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion db/checklists.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@
"displayText": "Watched ###LINK### and reproduced it; I have a GitHub portfolio with a Vanilla JS + HTML blog hosted on GitHub pages",
"isRequired": true,
"linkText": "the Trial by Fire",
"linkUri": "https://www.youtube.com/watch?v=V7yeF9AaBxM"
"linkUri": "https://www.youtube.com/watch?v=InK2ciIKs_Q"
},
{
"detailText": "",
"displayText": "Read ###LINK### and updated my settings as needed.",
"isRequired": true,
"linkText": "Understanding User Settings",
"linkUri": "https://ladderly.io/blog/2024-02-16-user-settings"
},
"I feel fairly confident that I have learned basic HTML, including forms and semantic HTML",
"I feel fairly confident that I have learned basic CSS",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "hasInPersonEventInterest" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "hasOnlineEventInterest" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "residenceCountry" TEXT NOT NULL DEFAULT '',
ADD COLUMN "residenceUSState" TEXT NOT NULL DEFAULT '';
14 changes: 9 additions & 5 deletions db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,21 @@ model User {
nameFirst String @default("")
nameLast String @default("")

hasLiveStreamInterest Boolean @default(false)
hasOpenToWork Boolean @default(false)
hasPublicProfileEnabled Boolean @default(false)
hasShoutOutsEnabled Boolean @default(false)
hasSmallGroupInterest Boolean @default(false)
hasInPersonEventInterest Boolean @default(false)
hasLiveStreamInterest Boolean @default(false)
hasOnlineEventInterest Boolean @default(false)
hasOpenToWork Boolean @default(false)
hasPublicProfileEnabled Boolean @default(false)
hasShoutOutsEnabled Boolean @default(false)
hasSmallGroupInterest Boolean @default(false)

profileBlurb String?
profileContactEmail String?
profileGitHubUri String?
profileHomepageUri String?
profileLinkedInUri String?
residenceCountry String @default("")
residenceUSState String @default("")
totalContributions Float @default(0)
totalStoreSpend Float @default(0)

Expand Down
163 changes: 110 additions & 53 deletions db/seed-utils/updateChecklists.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import db, { Prisma } from "db"
import db, { Checklist, ChecklistItem, Prisma } from "db"
import { r } from "vitest/dist/index-9f5bc072"
import { z } from "zod"

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

export const ChecklistsSchema = z.array(ChecklistSchema)

export const updateChecklistsInPlace = async (checklistData: ChecklistSeedDataType) => {
export const updateChecklistsInPlace = async (
checklistData: ChecklistSeedDataType
) => {
console.log(`Updating checklist: ${checklistData.name}`)

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

if (checklist === null) {
throw new Error(`Attempted to update a checklist, but it wasn't found: ${checklistData.name}`)
console.warn(`Checklist not found: ${checklistData.name}. Creating now.`)
checklist = await db.checklist.create({
data: { name: checklistData.name, version: checklistData.version },
include: { checklistItems: true },
})
}

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

if (checklist === null) {
throw new Error(`Checklist found but not returned by the updater: ${checklistData.name}`)
throw new Error(
`Checklist found but not returned by the updater: ${checklistData.name}`
)
}

// collect data for later bulk operations
const newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[] = []
const newUserChecklistItemsData: Prisma.UserChecklistItemCreateManyInput[] = []
const checklistItemCreateManyInput: Prisma.ChecklistItemCreateManyInput[] = []
for (let i = 0; i < checklistData.items.length; i++) {
const item = checklistData.items[i]
const itemData = ChecklistItemObjectSchema.parse(
typeof item === "string" ? { displayText: item } : item
)

const existingItem = checklist.checklistItems.find(
(ci) => ci.displayText === itemData.displayText
)

if (existingItem) {
await db.checklistItem.update({
where: { id: existingItem.id },
data: { ...itemData, displayIndex: i },
})
} else {
newChecklistItemsData.push({
...itemData,
checklistId: checklist.id,
displayIndex: i,
})
}
checklistItemCreateManyInput.push({
...itemData,
checklistId: checklist.id,
displayIndex: i,
})
}

console.log(`Done updating existing items for: ${checklistData.name}`)
await db.checklistItem.createMany({
data: newChecklistItemsData,
data: checklistItemCreateManyInput,
skipDuplicates: true,
})
console.log(`Done creating new items for: ${checklistData.name}`)
// TODO: remove obsolete ChecklistItems
await updateUserChecklists(checklist.id, newChecklistItemsData)
console.log(`Done updating UserChecklistItems for: ${checklistData.name}`)
// TODO: remove obsolete UserChecklistItems

const checklistItems = await deleteObsoleteChecklistItems(
checklist,
checklistItemCreateManyInput
)
console.log(`deleteObsoleteChecklistItems done for: ${checklistData.name}`)
await updateUserChecklists(checklist, checklistItems)
console.log(`updateUserChecklists done for: ${checklistData.name}`)

return checklist
}

const updateUserChecklists = async (
checklistId: number,
newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[]
checklist: Checklist,
checklistItems: ChecklistItem[]
) => {
const createdChecklistItems = await db.checklistItem.findMany({
const userChecklists = await db.userChecklist.findMany({
where: {
checklistId: checklistId,
displayText: { in: newChecklistItemsData.map((item) => item.displayText) },
checklistId: checklist.id,
},
})

const displayTextToIdMap = createdChecklistItems.reduce((acc, item) => {
acc[item.displayText] = item.id
return acc
}, {} as Record<string, number>)

// Prepare UserChecklistItems for bulk creation or update
const newUserChecklistItemsData: Prisma.UserChecklistItemCreateManyInput[] = []
const userChecklists = await db.userChecklist.findMany({
where: { checklistId: checklistId },
})
for (const newItem of newChecklistItemsData) {
const checklistItemId = displayTextToIdMap[newItem.displayText]
if (checklistItemId === undefined) continue
for (const userChecklist of userChecklists) {
await db.userChecklistItem.deleteMany({
where: {
userChecklistId: userChecklist.id,
},
})

userChecklists.forEach((userChecklist) => {
newUserChecklistItemsData.push({
await db.userChecklistItem.createMany({
data: checklistItems.map((itemData) => ({
userChecklistId: userChecklist.id,
checklistItemId: itemData.id,
userId: userChecklist.userId,
checklistItemId: checklistItemId,
isComplete: false,
})
})),
})
}
}

await db.userChecklistItem.createMany({
data: newUserChecklistItemsData,
const deleteObsoleteChecklistItems = async (
checklist: Checklist & { checklistItems: ChecklistItem[] },
newChecklistItemsData: Prisma.ChecklistItemCreateManyInput[]
): Promise<ChecklistItem[]> => {
const checklistItemsWithIds = await db.checklistItem.findMany({
where: {
checklistId: checklist.id,
},
})

const newItemsSet = new Set(
newChecklistItemsData.map((item) => {
return (
item.displayText +
item.detailText +
item.isRequired +
item.linkText +
item.linkUri
)
})
)

const obsoleteChecklistItems: ChecklistItem[] =
checklist.checklistItems.filter((item) => {
const currCompoundKey =
item.displayText +
item.detailText +
item.isRequired +
item.linkText +
item.linkUri

return !newItemsSet.has(currCompoundKey)
})

if (obsoleteChecklistItems.length > 0) {
const idsToDelete = obsoleteChecklistItems.map((item) => item.id)
await db.userChecklistItem.deleteMany({
where: {
checklistItemId: {
in: idsToDelete,
},
},
})
await db.checklistItem.deleteMany({
where: {
id: {
in: idsToDelete,
},
},
})
}

const countOfItemsAfterDeletion = await db.checklistItem.count({
where: {
checklistId: checklist.id,
},
})

if (countOfItemsAfterDeletion !== newChecklistItemsData.length) {
throw new Error(
`Checklist items count mismatch for: ${checklist.name}.\n` +
`Expected: ${newChecklistItemsData.length}, ` +
`Found: ${countOfItemsAfterDeletion}`
)
}

return checklistItemsWithIds
}
6 changes: 3 additions & 3 deletions db/seeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import {
} from "./seed-utils/updateChecklists"

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

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

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

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

if (updateLatest) {
if (updateLatestInPlace) {
checklist = await updateChecklistsInPlace(checklistData)
}

Expand Down
4 changes: 3 additions & 1 deletion docs/RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ It documents how to handle certain common manual tasks and outages.

## Updating a Production Checklist In-Place

We `npm run seed:update-in-place`
We `npm run seed:update-in-place`.

This script is idempotent. If you run into an issue, in many cases you can just run the script again to resolve the issue.

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

Expand Down
Loading