NotifySeasonStart
SEASON_STARTEDnotifySeasonStart.tsNo description, add JSDoc to NotifySeasonStartRoutine.
What the server does without a user clicking anything, and what it is called by apart from the browser. Generated from the cron schedules of app/entry.server.tsx, the routines of app/routines/, the routes of app/routes.ts and the Notification type.
31 routines run by node-cron, in production only (the e2e tests and development never start the schedules). A schedule runs its routines one after another, a routine failing is logged and doesn't stop the rest. In development or an e2e test run a routine can be run by hand by POST /run-routine with its name (run-routine.ts).
0 */1 * * * in the server's time zone, runs everyHourAt00 entry.server.tsx
NotifySeasonStartNo description, add JSDoc to NotifySeasonStartRoutine.
NotifyPlusServerVotingNo description, add JSDoc to NotifyPlusServerVotingRoutine.
NotifyCheckInStartNo description, add JSDoc to NotifyCheckInStartRoutine.
TournamentRepository.findAllBetweenTwoTimestampsNotificationRepository.insert via notifyTournamentRepository.findById via tournamentDataCachedBracketRepository.findByTournamentId via tournamentDataCachedTournamentRepository.findParticipatedUserIdsById via tournamentDataCachedTournamentRepository.findDivisionTiersByTournamentId via tournamentDataCachedNotificationRepository.findUnseenSubscriptionsByNotificationId via notifyTournamentRepository.findStreamsByTournamentId via tournamentDataCachedNotificationRepository.deleteSubscriptionById via notifyTournamentOrganizationRepository.findAllSeries via tournamentDataCachedNotifyScrimStartingSoonNo description, add JSDoc to NotifyScrimStartingSoonRoutine.
NotifyLeagueMatchStartingSoonNo description, add JSDoc to NotifyLeagueMatchStartingSoonRoutine.
SyncSplatoonRotationsNo description, add JSDoc to SyncSplatoonRotationsRoutine.
SyncTournamentVodsNo description, add JSDoc to SyncTournamentVodsRoutine.
TournamentMatchVodRepository.findTournamentsNeedingVodSync via syncTournamentVodsTournamentMatchVodRepository.markVodSyncCompleted via syncTournamentVodsTournamentMatchVodRepository.findMatchesWithStartedAt via syncTournamentVodsTournamentMatchVodRepository.findStreamersByTournamentId via syncTournamentVodsTournamentMatchVodRepository.findCastedMatchHistoryByTournamentId via syncTournamentVodsTournamentMatchVodRepository.insertMany via syncTournamentVods30 */1 * * * in the server's time zone, runs everyHourAt30 entry.server.tsx
SetOldGroupsAsInactiveNo description, add JSDoc to SetOldGroupsAsInactiveRoutine.
SQGroupRepository.setOldGroupsAsInactiveSQGroupRepository.findCurrentGroups via refreshSendouQInstanceSQGroupRepository.findRecentlyFinishedMatches via refreshSendouQInstanceSQGroupRepository.findCurrentReceivedLikerGroupIds via refreshSendouQInstanceSkillRepository.findOrderedUserOrdinalsBySeason via refreshSendouQInstanceUpdatePatreonDataNo description, add JSDoc to UpdatePatreonDataRoutine.
CloseExpiredContinueVotesNo description, add JSDoc to CloseExpiredContinueVotesRoutine.
DeleteOldExternalStreamsNo description, add JSDoc to DeleteOldExternalStreamsRoutine.
EvictStaleRunningTournamentsNo description, add JSDoc to EvictStaleRunningTournamentsRoutine.
ResolveStaleSQMatchesNo description, add JSDoc to ResolveStaleSQMatchesRoutine.
SQMatchRepository.findUnfinishedMatchesCreatedBeforeSQMatchRepository.resolveUnfinishedMatchQStreamsRepository.findAllActiveMatchPlayers via refreshStreamsCacheSQGroupRepository.findCurrentGroups via refreshSendouQInstanceSQGroupRepository.findRecentlyFinishedMatches via refreshSendouQInstanceSQGroupRepository.findCurrentReceivedLikerGroupIds via refreshSendouQInstanceSkillRepository.findOrderedUserOrdinalsBySeason via refreshUserSkillsLeaderboardRepository.findUserSPLeaderboard via refreshStreamsCacheUserRepository.findAllPlusServerMembers via refreshStreamsCacheLeaderboardRepository.findSeasonPopularUsersWeapon via refreshStreamsCacheAdminRepository.findAllBannedUsers via refreshStreamsCache0 4 * * * in the server's time zone, runs daily entry.server.tsx
4:00 AM UTC
NotifySeasonEndNo description, add JSDoc to NotifySeasonEndRoutine.
DeleteObsoleteMatchVodsNo description, add JSDoc to DeleteObsoleteMatchVodsRoutine.
DeleteOldNotificationsNo description, add JSDoc to DeleteOldNotificationsRoutine.
DeleteOldPendingFriendRequestsNo description, add JSDoc to DeleteOldPendingFriendRequestsRoutine.
DeleteOldTournamentAuditLogsNo description, add JSDoc to DeleteOldTournamentAuditLogsRoutine.
DeleteOldScrimPickupRostersNo description, add JSDoc to DeleteOldScrimPickupRostersRoutine.
DeleteOldAvailabilityNo description, add JSDoc to DeleteOldAvailabilityRoutine.
NotifyScheduleTeamReminderReminds users whose teammates reported the week that just started while they have not. Mondays only, so at most one reminder per week.
CloseExpiredCommissionsNo description, add JSDoc to CloseExpiredCommissionsRoutine.
CloseExpiredChatRoomsNo description, add JSDoc to CloseExpiredChatRoomsRoutine.
DeleteOldVoiceTelemetryNo description, add JSDoc to DeleteOldVoiceTelemetryRoutine.
DeleteOldUnlinkedScannerMatchesNo description, add JSDoc to DeleteOldUnlinkedScannerMatchesRoutine.
DeleteOrphanArtTagsNo description, add JSDoc to DeleteOrphanArtTagsRoutine.
ComputeLutiDivsRecomputes User.div from the latest finalized LUTI season for every eligible participant (team did not drop out, played at least one match). Others keep their previous division. Idempotent.
OptimizeDatabaseNo description, add JSDoc to OptimizeDatabaseRoutine.
0 9 * * 3 in Europe/Helsinki, runs weekly entry.server.tsx
9:00 AM Finnish time on Wednesdays, a quiet hour since vacuuming blocks writes longer than the 5s busy_timeout
VacuumDatabaseRewrites the database file to reclaim fragmentation, mostly from migrations that drop a wide column or a large index (ordinary traffic keeps the freelist in single digit MB, so most weeks this skips). Readers keep the pre-vacuum WAL snapshot but writers block for the whole rewrite, longer than the 5s busy_timeout, hence a quiet hour and weekly rather than daily.
*/2 * * * * in the server's time zone, runs everyTwoMinutes entry.server.tsx
SyncLiveStreamsNo description, add JSDoc to SyncLiveStreamsRoutine.
ExpireReadyChecksBackstop for ready checks nobody was around to run out the clock on in the browser.
SQGroupRepository.findAllReadyChecksStartedBeforeSQGroupRepository.deleteReadyCheck via expireNotificationRepository.markAsSeenByType via expireSQGroupRepository.findCurrentGroups via expireSQGroupRepository.findRecentlyFinishedMatches via expireSQGroupRepository.findCurrentReceivedLikerGroupIds via expireSkillRepository.findOrderedUserOrdinalsBySeason via expireRoutes with no page that other services or the dev tools call: found by their path containing webhook, or by their loader (GET) or action (POST) checking the caller with canAccessLohiEndpoint or guarding on DANGEROUS_CAN_ACCESS_DEV_CONTROLS.
Called by Lohi, the sendou.ink Discord bot, with the LOHI_TOKEN environment variable in the Lohi-Token header (canAccessLohiEndpoint).
/auth/create-link/patrons/plus/listCalled by third parties when something happens on their side, each verifying the request's signature.
/api/voice/webhookVoiceRepository.updateFromLeftWebhookVoiceRepository.hasOtherConnectedSessionChatRepository.findAllRoomsByIds via resolveImageRepository.countUnvalidatedBySubmitterUserId via defineActionImageRepository.insert via defineActionSQGroupRepository.findAllByChatRoomIds via resolveSQMatchRepository.findAllByChatRoomIds via resolveTournamentMatchRepository.findAllByChatRoomIds via resolveTournamentTeamRepository.findAllMembersByTeamIds via resolveTournamentRepository.findOrganizerPermissionsByTournamentIds via resolveTournamentTeamRepository.findAllByChatRoomIds via resolveScrimPostRepository.findAllByChatRoomIds via resolveImageRepository.findById via defineActionOnly answer in development and e2e test runs (DANGEROUS_CAN_ACCESS_DEV_CONTROLS), called by the dev tools and the e2e helpers.
/end-season/refresh-cachesUserCardRepository.clearUserCardCache via refreshCachesUserRepository.clearFriendCodesCache via refreshCachesAdminRepository.findAllBannedUsers via refreshCachesImageRepository.countUnvalidatedBySubmitterUserId via defineActionImageRepository.insert via defineActionSQGroupRepository.findCurrentGroups via refreshCachesSQGroupRepository.findRecentlyFinishedMatches via refreshCachesSQGroupRepository.findCurrentReceivedLikerGroupIds via refreshCachesTournamentOrganizationRepository.findAllSeries via refreshCachesApiRepository.findAllApiTokens via refreshCachesTournamentRepository.findRunningTournamentIds via refreshCachesImageRepository.findById via defineActionTournamentRepository.findById via refreshCachesBracketRepository.findByTournamentId via refreshCachesTournamentRepository.findParticipatedUserIdsById via refreshCachesTournamentRepository.findDivisionTiersByTournamentId via refreshCachesSkillRepository.findOrderedUserOrdinalsBySeason via refreshCachesTournamentRepository.findStreamsByTournamentId via refreshCaches/run-routine/seedUserCardRepository.clearUserCardCache via refreshCachesUserRepository.clearFriendCodesCache via refreshCachesAdminRepository.findAllBannedUsers via refreshCachesImageRepository.countUnvalidatedBySubmitterUserId via defineActionImageRepository.insert via defineActionSQGroupRepository.findCurrentGroups via refreshCachesSQGroupRepository.findRecentlyFinishedMatches via refreshCachesSQGroupRepository.findCurrentReceivedLikerGroupIds via refreshCachesTournamentOrganizationRepository.findAllSeries via refreshCachesApiRepository.findAllApiTokens via refreshCachesTournamentRepository.findRunningTournamentIds via refreshCachesImageRepository.findById via defineActionTournamentRepository.findById via refreshCachesBracketRepository.findByTournamentId via refreshCachesTournamentRepository.findParticipatedUserIdsById via refreshCachesTournamentRepository.findDivisionTiersByTournamentId via refreshCachesSkillRepository.findOrderedUserOrdinalsBySeason via refreshCachesTournamentRepository.findStreamsByTournamentId via refreshCaches/set-plus-voting-activeMessages posted to Discord channels, from app/modules/discord-webhook.server.ts. Each channel's webhook URL is an environment variable, the message is skipped when it is unset.
sendModDiscordWebhookFire-and-forget embed to the mod channel: never throws, skipped with a log line when MOD_DISCORD_WEBHOOK_URL is unset.
sendUserBannedWebhook — Fire-and-forget embed to the mod channel webhook. called by action /admin sendUserUnbannedWebhook — Fire-and-forget embed to the mod channel webhook. called by action /admin sendUserReportWebhook — Fire-and-forget embed to the mod channel webhook. called by action /user-report/:id sendSQCancelDiscordWebhookEmbed to the SendouQ canceled matches channel, fire-and-forget like {@link sendModDiscordWebhook}.
sendMatchCanceledWebhook — Posts a rich embed about a finalized SendouQ cancellation to the cancels channel webhook. Fire-and-forget (see sendSQCancelDiscordWebhook). called by notifyStaffOfCanceledMatch /q/match/:id The members of the Notification union (notifications-types.ts), sent with notify(): shown in the notifications list and, after a grace period in which seeing it cancels it, pushed to subscribed devices. Title and text are the English translations of notifications.title.* and notifications.text.* in locales/en/common.json, interpolating the meta.
SQ_ADDED_TO_GROUPAdded to SendouQ Group Added to a group by {{adderUsername}}
adderUsername: stringSENDOUQ_PAGESQ_READY_CHECKReady Check Confirm you are ready to play
SENDOUQ_READY_PAGEstart ready-check.server.tsSQ_NEW_MATCHNew SendouQ Match SendouQ match #{{matchId}} started
matchId: numbersendouQMatchPage(notification.meta.matchId)createMatch ready-check.server.tsTO_ADDED_TO_TEAMAdded to Team Added to a team ({{teamName}}) by {{adderUsername}} in {{tournamentName}}
tournamentId: number, tournamentName: string, adderUsername: string, teamName: string, tournamentTeamId: numbertournamentTeamPage({ tournamentId: notification.meta.tournamentId, tournamentTeamId: notification.meta.tournamentTeamId })TO_BRACKET_STARTEDBracket Started {{bracketName}} of {{tournamentName}} started
tournamentId: number, bracketIdx: number, bracketName: string, tournamentName: stringtournamentBracketsPage({ tournamentId: notification.meta.tournamentId, bracketIdx: notification.meta.bracketIdx })TO_CHECK_IN_OPENEDCheck-in Opened Check-in for {{tournamentName}} open
tournamentId: number, tournamentName: stringtournamentRegisterPage(notification.meta.tournamentId)TO_TEST_CREATEDTest tournament created You created a test tournament {{tournamentName}}
tournamentId: number, tournamentName: stringtournamentRegisterPage(notification.meta.tournamentId)BADGE_ADDEDNew Badge New badge ({{badgeName}})
badgeName: string, badgeId: numberbadgePage(notification.meta.badgeId)BADGE_MANAGER_ADDEDManager Rights Granted You got manager rights for badge {{badgeName}}
badgeName: string, badgeId: numberbadgePage(notification.meta.badgeId)TROPHY_SUBMITTEDNew Trophy Submission {{submitterUsername}} submitted trophy {{trophyName}} for review
trophyName: string, submitterUsername: stringNEW_TROPHY_PAGETROPHY_SUBMISSION_ACCEPTEDTrophy Accepted Your trophy {{trophyName}} was accepted
trophyName: string, trophyId: numbertrophyPage(notification.meta.trophyId)TROPHY_SUBMISSION_DECLINEDTrophy Declined Your trophy {{trophyName}} was declined
trophyName: stringNEW_TROPHY_PAGEPLUS_VOTING_STARTEDPlus Voting Started Plus Server voting of season {{seasonNth}} started
seasonNth: numberPLUS_VOTING_PAGEPLUS_SUGGESTION_ADDEDPlus Suggestion Added You were suggested to +{{tier}}
tier: numberplusSuggestionPage({ tier: notification.meta.tier })TAGGED_TO_ARTTagged in Art Tagged in art by {{adderUsername}}
adderUsername: string, adderId: number, artId: numberuserArtPage({ id: notification.meta.adderId }, "MADE-BY", notification.meta.artId)SEASON_STARTEDNew Season Started SendouQ is open again for Season {{seasonNth}}
seasonNth: numberSENDOUQ_PAGESEASON_ENDEDSeason Ended Thanks for playing Season {{seasonNth}}! Export your season summary while it's available
seasonNth: numberrecipient ? userSeasonsPage({ user: recipient, season: notification.meta.seasonNth }) : SENDOUQ_PAGESCRIM_NEW_REQUESTNew Scrim Request {{fromUsername}} requested a scrim
fromUserId: number, fromUsername: string, scrimPostId: numberscrimsPage()SCRIM_SCHEDULEDScrim Scheduled New scrim scheduled vs. {{opponentTeamName}}
id: number, opponentTeamName: stringscrimPage(notification.meta.id)SCRIM_CANCELEDScrim Canceled The scrim vs. {{opponentTeamName}} was canceled
id: number, opponentTeamName: stringscrimPage(notification.meta.id)SCRIM_STARTING_SOONScrim Starting Soon Your scrim vs. {{opponentTeamName}} is starting soon
id: number, opponentTeamName: stringscrimPage(notification.meta.id)TO_LEAGUE_TIMES_PROPOSEDLeague Times Proposed {{opponentTeamName}} proposed times for your league set
tournamentId: number, matchId: number, opponentTeamName: stringtournamentMatchPage({ tournamentId: notification.meta.tournamentId, matchId: notification.meta.matchId })TO_LEAGUE_MATCH_SCHEDULEDLeague Set Scheduled Your league set vs. {{opponentTeamName}} has been scheduled
tournamentId: number, matchId: number, opponentTeamName: stringtournamentMatchPage({ tournamentId: notification.meta.tournamentId, matchId: notification.meta.matchId })TO_LEAGUE_MATCH_STARTING_SOONLeague Set Starting Soon Your league set vs. {{opponentTeamName}} is starting soon
tournamentId: number, matchId: number, opponentTeamName: stringtournamentMatchPage({ tournamentId: notification.meta.tournamentId, matchId: notification.meta.matchId })SCRIM_AUTO_DELETEDScrim Post Removed Your scrim post was automatically removed because you booked a scrim around that time (± 1 hour)
at: numberscrimsPage()COMMISSIONS_CLOSEDCommissions Closed If your commissions are still open, please re-enable them
recipient ? userEditProfilePage(recipient) : ART_PAGEFRIEND_REQUEST_RECEIVEDFriend Request {{senderUsername}} sent you a friend request
senderId: number, senderUsername: stringFRIENDS_PAGETO_LIKE_RECEIVEDGroup Invitation Received {{likerUsername}} invited your group in {{tournamentName}}
tournamentId: number, tournamentName: string, likerUsername: stringtournamentSubsPage(notification.meta.tournamentId)TO_LIKE_ACCEPTEDGroup Invitation Accepted {{accepterUsername}} accepted your group invitation in {{tournamentName}}
tournamentId: number, tournamentName: string, accepterUsername: stringtournamentSubsPage(notification.meta.tournamentId)TEAM_EVENT_ADDEDNew Team Event {{teamName}} has a new event: {{eventName}}
eventName: string, teamName: string, teamCustomUrl: stringteamSchedulePage(notification.meta.teamCustomUrl)SCHEDULE_TEAM_REMINDERAvailability Missing Your teammates are waiting for you to fill in your availability for the week
EVENTS_PAGECHAT_MENTIONMentioned in Chat {{mentionerUsername}} mentioned you in chat
roomId: number, messageId: number, mentionerUsername: string, roomUrl: stringchatOpenedPage({ pageUrl: notification.meta.roomUrl, roomId: notification.meta.roomId })notifyMentioned ChatMentionNotification.server.ts