Skip to content

Commit

Permalink
fix: get reminders working again
Browse files Browse the repository at this point in the history
  • Loading branch information
stayradiated committed Jul 21, 2023
1 parent 84f38ad commit e1e0a9a
Show file tree
Hide file tree
Showing 17 changed files with 439 additions and 168 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"semicolon": false,
"prettier": true,
"rules": {
"@typescript-eslint/naming-convention": "off"
"@typescript-eslint/naming-convention": "off",
"no-empty-pattern": "off"
}
},
"prettier": {
Expand Down
30 changes: 30 additions & 0 deletions src/db/delete-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { errorBoundary } from '@stayradiated/error-boundary'
import { prisma } from './prisma.js'

type DeleteUserOptions = {
userId: string
}

const deleteUser = async (
options: DeleteUserOptions,
): Promise<void | Error> => {
const { userId } = options
return errorBoundary(async () => {
const deletePostItems = prisma.postItem.deleteMany({

Check failure on line 13 in src/db/delete-user.ts

View workflow job for this annotation

GitHub Actions / Handover Bot

Unsafe assignment of an `any` value.

Check failure on line 13 in src/db/delete-user.ts

View workflow job for this annotation

GitHub Actions / Handover Bot

Unsafe call of an `any` typed value.
where: { post: { userId } },
})
const deletePosts = prisma.post.deleteMany({ where: { userId } })
const deleteReminder = prisma.reminder.deleteMany({
where: { userId },
})
const deleteUser = prisma.user.delete({ where: { id: userId } })
await prisma.$transaction([
deletePostItems,
deletePosts,
deleteReminder,
deleteUser,
])
})
}

export { deleteUser }
9 changes: 6 additions & 3 deletions src/db/get-active-user-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import type { UserWithPosts } from './types.js'

// Find users that have posted something in the last 7 days
type GetActiveUserListOptions = {
activeSince: Date
startDate: Date
endDate: Date
}

const getActiveUserList = async (
options: GetActiveUserListOptions,
): Promise<UserWithPosts[] | Error> => {
const { activeSince } = options
const { startDate, endDate } = options

return errorBoundary(async () =>
prisma.user.findMany({
where: {
posts: {
some: {
date: {
gte: activeSince,
gte: startDate,
lte: endDate,
},
items: {
some: {},
Expand Down
2 changes: 1 addition & 1 deletion src/db/get-post-with-items.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as db from './index.js'

describe('getPostWithItems', () => {
const userId = randomUUID()
const date = '2022-08-15T00:00:00+00:00'
const date = new Date('2022-08-15T00:00:00+00:00')

beforeEach(async () => {
const user = await db.upsertUser({
Expand Down
4 changes: 2 additions & 2 deletions src/db/get-post-with-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PostWithItems } from './types.js'

type GetPostWithItemsOptions = {
userId: string
date: string
date: Date
}

const getPostWithItems = async (
Expand All @@ -13,7 +13,7 @@ const getPostWithItems = async (
const { userId, date } = options
return errorBoundary(() =>
prisma.post.findUniqueOrThrow({
where: { userDate: { userId, date: new Date(date) } },
where: { userDate: { userId, date } },
include: {
items: {
orderBy: {
Expand Down
1 change: 1 addition & 0 deletions src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './delete-post-item.js'
export * from './delete-user.js'
export * from './get-active-user-list.js'
export * from './get-format-list.js'
export * from './get-post-list-with-items.js'
Expand Down
12 changes: 10 additions & 2 deletions src/db/upsert-post.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { Prisma } from '@prisma/client'
import { errorBoundary } from '@stayradiated/error-boundary'
import { prisma } from './prisma.js'

const upsertPost = async (post: Prisma.PostUncheckedCreateInput) =>
type UpsertPostInput = {
userId: string
date: Date
title: string

channel?: string
ts?: string
}

const upsertPost = async (post: UpsertPostInput) =>
errorBoundary(() =>
prisma.post.upsert({
create: post,
Expand Down
18 changes: 12 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import {
publishPrivateContentToSlack,
} from './publish-to-slack.js'
import { getDateFromTs, getDateFromMessage } from './date-utils.js'
import { checkAndRemindUsers } from './remind-user.js'
import { checkAndRemindUsers } from './lib/remind/index.js'
import {
SLACK_BOT_TOKEN,
SLACK_APP_TOKEN,
HANDOVER_CHANNEL,
HANDOVER_TITLE,
PORT,
SLACK_SIGNING_SECRET,
HANDOVER_DAILY_REMINDER_TIME,
} from './constants.js'
import * as db from './db/index.js'
import { isCommand, execCommand } from './command/index.js'
Expand Down Expand Up @@ -67,7 +68,8 @@ const addHeading = async (
}

const userList = await db.getActiveUserList({
activeSince: dateFns.subDays(dateFns.parseISO(date), 7),
startDate: dateFns.subDays(dateFns.parseISO(date), 7),
endDate: dateFns.endOfDay(dateFns.parseISO(date)),
})
if (userList instanceof Error) {
return userList
Expand All @@ -79,7 +81,7 @@ const addHeading = async (
const upsertPostResult = await db.upsertPost({
userId: user.id,
title: user.name,
date,
date: dateFns.parseISO(date),
})
if (upsertPostResult instanceof Error) {
return upsertPostResult
Expand Down Expand Up @@ -115,7 +117,7 @@ const updateUserPost = async (

const post = await db.getPostWithItems({
userId,
date,
date: dateFns.parseISO(date),
})
if (!post) {
return new Error('Could not find with post with items')
Expand Down Expand Up @@ -165,7 +167,7 @@ const addPostItem = async (
const post = await db.upsertPost({
userId,
title: postTitle,
date: postDate,
date: dateFns.parseISO(postDate),
})
if (post instanceof Error) {
return post
Expand Down Expand Up @@ -402,7 +404,11 @@ const start = async () => {

// Check if there are any users who need reminding to send their handover.
setInterval(async () => {
const result = await checkAndRemindUsers({ web })
const result = await checkAndRemindUsers({
web,
defaultDailyReminderTime: HANDOVER_DAILY_REMINDER_TIME,
daysSinceLastPostCutOff: 7,
})
if (result instanceof Error) {
console.error(result)
}
Expand Down
48 changes: 48 additions & 0 deletions src/lib/remind/check-and-remind-users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { WebClient } from '@slack/web-api'
import { formatDateAsISODate } from '../../date-utils.js'
import { sendReminderToUser } from './send-reminder-to-user.js'
import { findUsersWhoNeedReminder } from './find-users-who-need-reminder.js'

type CheckAndRemindUsersOptions = {
web: WebClient
daysSinceLastPostCutOff: number
defaultDailyReminderTime: string
}
const checkAndRemindUsers = async (
options: CheckAndRemindUsersOptions,
): Promise<void | Error> => {
const { web, daysSinceLastPostCutOff, defaultDailyReminderTime } = options

const instant = Date.now()

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})
if (userList instanceof Error) {
return userList
}

const result = await Promise.all(
userList.map(async (user) => {
const userDate = formatDateAsISODate({
instant,
timeZone: user.timeZone,
})

const sendReminderToUserResult = await sendReminderToUser({
web,
user,
userDate,
daysSinceLastPostCutOff,
})

return sendReminderToUserResult
}),
)
if (result instanceof Error) {
return result
}
}

export { checkAndRemindUsers }
153 changes: 153 additions & 0 deletions src/lib/remind/find-users-who-need-reminder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { randomUUID } from 'node:crypto'
import { vi, describe, test, expect } from 'vitest'
import { assertOk } from '@stayradiated/error-boundary'
import type { User } from '@prisma/client'
import * as dateFns from 'date-fns'
import {
upsertUser,
upsertPost,
upsertPostItem,
deleteUser,
} from '../../db/index.js'
import { findUsersWhoNeedReminder } from './find-users-who-need-reminder.js'

const daysSinceLastPostCutOff = 7
const defaultDailyReminderTime = '15:00'

// Should not be a weekend
vi.setSystemTime(new Date('2021-02-15T17:00:00.000Z'))

type Context = {
user: User
}

const myTest = test.extend<Context>({
async user({}, use) {
const user = await upsertUser({
id: randomUUID(),
name: 'Test User xyz',
timeZone: 'UTC',
})
assertOk(user)

await use(user)

const result = await deleteUser({ userId: user.id })
assertOk(result)
},
})

describe('findUsersWhoNeedReminder', () => {
myTest('person who posted yesterday → true', async ({ user }) => {
const yesterdaysPost = await upsertPost({
userId: user.id,
title: 'Test Post',
date: dateFns.subDays(new Date(), 1),
})
assertOk(yesterdaysPost)

const postItem = await upsertPostItem({
postId: yesterdaysPost.id,
text: '',
channel: '',
ts: Date.now().toString(),
})
assertOk(postItem)

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})
assertOk(userList)
expect(userList).toHaveLength(1)
expect(userList[0]!.id).toBe(user.id)
})

myTest('person with no posts → false', async ({ user }) => {
// Must destructure user so that vitest creates them
expect(user.id).toBeDefined()

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})
assertOk(userList)
expect(userList).toHaveLength(0)
})

myTest('person with an empty post → false', async ({ user }) => {
// Must destructure user so that vitest creates them
expect(user.id).toBeDefined()

const yesterdaysPost = await upsertPost({
userId: user.id,
title: 'Test Post',
date: dateFns.subDays(new Date(), 1),
})
assertOk(yesterdaysPost)

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})

assertOk(userList)
expect(userList).toHaveLength(0)
})

myTest('person with a post from > 7 days ago → false', async ({ user }) => {
// Must destructure user so that vitest creates them
expect(user.id).toBeDefined()

const oldPost = await upsertPost({
userId: user.id,
title: 'Test Post',
date: dateFns.subDays(new Date(), 8),
})
assertOk(oldPost)

const postItem = await upsertPostItem({
postId: oldPost.id,
text: '',
channel: '',
ts: Date.now().toString(),
})
assertOk(postItem)

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})

assertOk(userList)
expect(userList).toHaveLength(0)
})

myTest('person with a post from today → false', async ({ user }) => {
// Must destructure user so that vitest creates them
expect(user.id).toBeDefined()

const todaysPost = await upsertPost({
userId: user.id,
title: 'Test Post',
date: new Date(),
})
assertOk(todaysPost)

const postItem = await upsertPostItem({
postId: todaysPost.id,
text: '',
channel: '',
ts: Date.now().toString(),
})
assertOk(postItem)

const userList = await findUsersWhoNeedReminder({
daysSinceLastPostCutOff,
defaultDailyReminderTime,
})

assertOk(userList)
expect(userList).toHaveLength(0)
})
})
Loading

0 comments on commit e1e0a9a

Please sign in to comment.