diff --git a/keyserver/src/cron/cron.js b/keyserver/src/cron/cron.js --- a/keyserver/src/cron/cron.js +++ b/keyserver/src/cron/cron.js @@ -21,6 +21,7 @@ import { deleteExpiredUpdates } from '../deleters/update-deleters'; import { deleteUnassignedUploads } from '../deleters/upload-deleters'; import { backupDB } from './backups'; +import { createDailyUpdatesThread } from './daily-updates'; import { updateAndReloadGeoipDB } from './update-geoip-db'; if (cluster.isMaster) { @@ -61,7 +62,7 @@ }, ); schedule.scheduleJob( - '0 3 ? * 0', // every Sunday at 3:00 AM Pacific Time + '0 3 ? * 0', // every Sunday at 3:00 AM GMT async () => { try { await updateAndReloadGeoipDB(); @@ -73,4 +74,20 @@ } }, ); + schedule.scheduleJob( + '0 0 * * *', // every day at midnight GMT + async () => { + try { + if (process.env.RUN_COMM_TEAM_DEV_SCRIPTS) { + // This is a job that the Comm internal team uses + await createDailyUpdatesThread(); + } + } catch (e) { + console.warn( + 'encountered error while trying to create daily updates thread', + e, + ); + } + }, + ); } diff --git a/keyserver/src/cron/daily-updates.js b/keyserver/src/cron/daily-updates.js new file mode 100644 --- /dev/null +++ b/keyserver/src/cron/daily-updates.js @@ -0,0 +1,101 @@ +// @flow + +import invariant from 'invariant'; + +import ashoat from 'lib/facts/ashoat'; +import { messageTypes } from 'lib/types/message-types'; +import { threadTypes } from 'lib/types/thread-types'; +import { + getDate, + dateString, + prettyDateWithoutYear, + prettyDateWithoutDay, +} from 'lib/utils/date-utils'; + +import createMessages from '../creators/message-creator'; +import { createThread } from '../creators/thread-creator'; +import { fetchEntryInfosForThreadThisWeek } from '../fetchers/entry-fetchers'; +import { createScriptViewer } from '../session/scripts'; + +const devUpdateThread = '1358777'; +const weeklyDevSyncScheduleThread = '4138372'; + +const dailyUpdateMessage = (dateWithoutYear: string, dateWithoutDay: string) => + `### ${dateWithoutDay} update + +Share your updates for ${dateWithoutYear} here please!`; + +const dateIsWeekend = (date: Date) => + date.getDay() === 0 || date.getDay() === 6; + +// This function will do something four days a week. It skips Saturday and +// Sunday. The hard part is the third skipped day, which is the day of the +// weekly dev sync. By default this is Monday, but if the dev sync is on a +// different day, then an admin will put a calendar entry in the +// weeklyDevSyncScheduleThread indicating which day to skip. +async function createDailyUpdatesThread() { + if (!process.env.RUN_COMM_TEAM_DEV_SCRIPTS) { + // This is a job that the Comm internal team uses + return; + } + + const viewer = createScriptViewer(ashoat.id); + const now = new Date(); + if (dateIsWeekend(now)) { + // nothing happens on Saturday or Sunday + return; + } + + // Figure out which day the dev sync is on + let devSyncDay = 1; // default to Monday + const entryInfosInDevSyncScheduleThreadThisWeek = await fetchEntryInfosForThreadThisWeek( + viewer, + weeklyDevSyncScheduleThread, + ); + for (const entryInfo of entryInfosInDevSyncScheduleThreadThisWeek) { + const entryInfoDate = getDate( + entryInfo.year, + entryInfo.month, + entryInfo.day, + ); + if (dateIsWeekend(entryInfoDate)) { + // Ignore calendar entries on weekend + continue; + } + devSyncDay = entryInfoDate.getDay(); + // Use the newest entryInfo. fetchEntryInfos sorts by creation time + break; + } + + if (devSyncDay === now.getDay()) { + // Skip the dev sync day + return; + } + + const dayString = dateString(now); + const dateWithoutYear = prettyDateWithoutYear(dayString); + const dateWithoutDay = prettyDateWithoutDay(dayString); + + const [{ id: messageID }] = await createMessages(viewer, [ + { + type: messageTypes.TEXT, + threadID: devUpdateThread, + creatorID: ashoat.id, + time: Date.now(), + text: dailyUpdateMessage(dateWithoutYear, dateWithoutDay), + }, + ]); + invariant( + messageID, + 'message returned from createMessages always has ID set', + ); + + await createThread(viewer, { + type: threadTypes.SIDEBAR, + parentThreadID: devUpdateThread, + name: `${dateWithoutDay} update`, + sourceMessageID: messageID, + }); +} + +export { createDailyUpdatesThread }; diff --git a/keyserver/src/fetchers/entry-fetchers.js b/keyserver/src/fetchers/entry-fetchers.js --- a/keyserver/src/fetchers/entry-fetchers.js +++ b/keyserver/src/fetchers/entry-fetchers.js @@ -21,6 +21,7 @@ threadPermissions, type ThreadPermission, } from 'lib/types/thread-types'; +import { dateString } from 'lib/utils/date-utils'; import { ServerError } from 'lib/utils/errors'; import { @@ -317,6 +318,30 @@ return rawEntryInfoFromRow(result[0]); } +function getSunday(weeksFromLastSunday: number) { + const date = new Date(); + const today = date.getDate(); + const currentDay = date.getDay(); + const newDate = date.setDate(today - currentDay + 7 * weeksFromLastSunday); + return new Date(newDate); +} + +async function fetchEntryInfosForThreadThisWeek( + viewer: Viewer, + threadID: string, +): Promise<$ReadOnlyArray> { + const startDate = dateString(getSunday(0)); + const endDate = dateString(getSunday(1)); + const filters = [ + { type: 'not_deleted' }, + { type: 'threads', threadIDs: [threadID] }, + ]; + const { rawEntryInfos } = await fetchEntryInfos(viewer, [ + { startDate, endDate, filters }, + ]); + return rawEntryInfos; +} + export { fetchEntryInfo, fetchEntryInfosByID, @@ -325,4 +350,5 @@ fetchEntryRevisionInfo, fetchEntriesForSession, fetchEntryInfoForLocalID, + fetchEntryInfosForThreadThisWeek, }; diff --git a/lib/types/filter-types.js b/lib/types/filter-types.js --- a/lib/types/filter-types.js +++ b/lib/types/filter-types.js @@ -14,7 +14,7 @@ +type: 'threads', +threadIDs: $ReadOnlyArray, }; -export type CalendarFilter = { type: 'not_deleted' } | CalendarThreadFilter; +export type CalendarFilter = { +type: 'not_deleted' } | CalendarThreadFilter; export const defaultCalendarFilters: $ReadOnlyArray = [ { type: calendarThreadFilterTypes.NOT_DELETED }, diff --git a/lib/utils/date-utils.js b/lib/utils/date-utils.js --- a/lib/utils/date-utils.js +++ b/lib/utils/date-utils.js @@ -73,6 +73,10 @@ return dateFormat(dateFromString(dayString), 'mmmm dS, yyyy'); } +function prettyDateWithoutYear(dayString: string): string { + return dateFormat(dateFromString(dayString), 'dddd, mmmm dS'); +} + function dateFromString(dayString: string): Date { const matches = dayString.match(/^([0-9]+)-([0-1][0-9])-([0-3][0-9])$/); invariant(matches && matches.length === 4, `invalid dayString ${dayString}`); @@ -160,6 +164,7 @@ fifteenDaysLater, prettyDate, prettyDateWithoutDay, + prettyDateWithoutYear, dateFromString, shortAbsoluteDate, longAbsoluteDate,