├── .circleci └── config.yml ├── .dockerignore ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .git-crypt ├── .gitattributes └── keys │ └── default │ └── 0 │ ├── 27DE4D956FCC3AEFFDE6A223829FB1011004D789.gpg │ └── 74235A652201EA7C16D5D8D72472D3473F0C19F2.gpg ├── .gitattributes ├── .github ├── CODEOWNERS └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .graphqlconfig ├── .infra ├── .gitignore ├── .npmrc ├── .nvmrc ├── Pulumi.adhoc.yaml ├── Pulumi.prod.yaml ├── Pulumi.yaml ├── application.properties ├── clickhouse-sync.yml ├── common.ts ├── crons.ts ├── index.ts ├── package.json ├── pnpm-lock.yaml ├── tsconfig.json └── workers.ts ├── .npmrc ├── .nvmrc ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── README.md ├── Tiltfile ├── __tests__ ├── .eslintrc.json ├── __snapshots__ │ ├── alerts.ts.snap │ ├── bookmarks.ts.snap │ ├── comments.ts.snap │ ├── compatibility.ts.snap │ ├── feeds.ts.snap │ ├── keywords.ts.snap │ ├── notifications.ts.snap │ ├── posts.ts.snap │ ├── rss.ts.snap │ ├── settings.ts.snap │ ├── sourceRequests.ts.snap │ ├── sources.ts.snap │ ├── submissions.ts.snap │ ├── tags.ts.snap │ └── users.ts.snap ├── actions.ts ├── alerts.ts ├── automations.ts ├── bookmarks.ts ├── boot.ts ├── comments.ts ├── common │ ├── contentPreference.ts │ ├── date.ts │ ├── fibonacci.ts │ ├── mailing.ts │ ├── njord.ts │ ├── paddle │ │ └── pricing.ts │ ├── personalizedDigest.ts │ ├── post.ts │ ├── pubsub.ts │ ├── redisCounters.test.ts │ ├── search.ts │ ├── standardizeURL.ts │ ├── timezone.ts │ ├── utils.ts │ └── vordr.ts ├── compatibility.ts ├── contentPreference.ts ├── cron │ ├── __snapshots__ │ │ ├── updateTagsStr.ts.snap │ │ └── updateTrending.ts.snap │ ├── calculateTopReaders.ts │ ├── checkAnalyticsReport.ts │ ├── checkReferralReminder.ts │ ├── cleanGiftedPlus.ts │ ├── cleanStaleUserTransactions.ts │ ├── cleanZombieImages.ts │ ├── cleanZombieUserCompany.ts │ ├── cleanZombieUsers.ts │ ├── dailyDigest.ts │ ├── generateSearchInvites.ts │ ├── hourlyNotifications.ts │ ├── personalizedDigest.ts │ ├── syncSubscriptionWithCIO.ts │ ├── updateCurrentStreak.ts │ ├── updateHighlightedViews.ts │ ├── updateSourcePublicThreshold.ts │ ├── updateTagRecommendations.ts │ ├── updateTagsStr.ts │ ├── updateTrending.ts │ ├── updateViews.ts │ └── validateActiveUsers.ts ├── dataLoaderService.ts ├── devcard.ts ├── directive │ └── rateLimitCounterDirective.test.ts ├── feeds.ts ├── fixture │ ├── happy_card.png │ ├── index.ts │ ├── keywords.ts │ ├── marketingCta.ts │ ├── notifications.ts │ ├── paddle │ │ └── transaction.ts │ ├── post.ts │ ├── rss.xml │ ├── source.ts │ ├── sourceRequest.ts │ ├── testCA.der │ └── user.ts ├── health.ts ├── helpers.ts ├── integrations.ts ├── integrations │ ├── feed.ts │ ├── openExchangeRates.ts │ ├── retry.ts │ └── slack.ts ├── keywords.ts ├── leaderboard.ts ├── njord.ts ├── notifications.ts ├── notifications │ ├── index.ts │ └── notificationWorkerToWorker.ts ├── organizations.ts ├── paddle.ts ├── posts.ts ├── private.ts ├── prompts.ts ├── publicSquadRequests.ts ├── redirector.ts ├── roles.ts ├── routes │ ├── graphql.ts │ ├── private │ │ ├── kvasir.ts │ │ ├── rpc.ts │ │ └── snotra.ts │ └── webhooks │ │ ├── apple.ts │ │ ├── customerio.ts │ │ └── paddle.ts ├── rss.ts ├── search.ts ├── settings.ts ├── setup.ts ├── sitemaps.ts ├── sourceRequests.ts ├── sources.ts ├── submissions.ts ├── tags.ts ├── teardown.ts ├── temporal │ └── notifications │ │ ├── activities.ts │ │ ├── utils.ts │ │ └── workflows.ts ├── triggers │ ├── comment.ts │ ├── feedSource.ts │ ├── notifications.ts │ ├── post.ts │ ├── postKeywordStatus.ts │ ├── sourceMember.ts │ └── user.ts ├── urlShortener.ts ├── users.ts ├── whoami.ts └── workers │ ├── __snapshots__ │ ├── newView.ts.snap │ ├── personalizedDigestEmail.ts.snap │ └── postUpdated.ts.snap │ ├── bannerAdded.ts │ ├── bannerDeleted.ts │ ├── cdc │ ├── notifications.ts │ └── primary.ts │ ├── commentDownvoteCanceledRep.ts │ ├── commentDownvotedRep.ts │ ├── commentEditedImages.ts │ ├── commentMarkdownImages.ts │ ├── commentUpvoteCanceledRep.ts │ ├── commentUpvotedRep.ts │ ├── completedUserActions.ts │ ├── experimentAllocated.ts │ ├── newNotificationV2Mail.ts │ ├── newNotificationV2Push.ts │ ├── newNotificationV2RealTime.ts │ ├── newView.ts │ ├── notifications.ts │ ├── notifications │ ├── articleNewCommentCommentCommented.ts │ ├── articleNewCommentPostCommented.ts │ ├── collectionUpdated.ts │ ├── commentMention.ts │ ├── commentReply.ts │ ├── postAdded.ts │ ├── sourcePostModerationApprovedNotification.ts │ ├── sourcePostModerationRejectedNotification.ts │ ├── sourcePostModerationSubmittedNotification.ts │ └── userGiftedPlusNotification.ts │ ├── notifySourcePrivacyUpdated.ts │ ├── organization │ ├── organizationUserLeft.ts │ └── organizationUserRemoved.ts │ ├── personalizedDigestEmail.ts │ ├── postAddedSlackChannelSend.ts │ ├── postBannedRep.ts │ ├── postCommentedRedis.ts │ ├── postDeletedSharedPostCleanup.ts │ ├── postDownvoteCanceledRep.ts │ ├── postDownvotedRep.ts │ ├── postEditedFreeformImages.ts │ ├── postFreeformImages.ts │ ├── postTranslated.ts │ ├── postUpdated.ts │ ├── postUpvoteCanceledRep.ts │ ├── postUpvotedRedis.ts │ ├── postUpvotedRep.ts │ ├── sourceRequestApproved.ts │ ├── sourceSquadCreatedUserAction.ts │ ├── squadPublicRequestNotification.ts │ ├── transactionBalanceLog.ts │ ├── transactions │ ├── userBoughtCores.ts │ └── userReceivedAward.ts │ ├── userCompanyApprovedCio.ts │ ├── userCreatedPersonalizedDigestSendType.ts │ ├── userReadmeImages.ts │ ├── userStreakUpdatedCio.ts │ ├── userUpdatedCio.ts │ ├── userUpdatedPlusSubscriptionCustomFeed.ts │ ├── userUpdatedPlusSubscriptionSquad.ts │ ├── vordrPostCommentPrevented.ts │ ├── vordrPostPrevented.ts │ └── workers.ts ├── bin ├── addDisallowHandles.ts ├── addShortIds.ts ├── addWelcomePosts.ts ├── allocateDigestExperiment.ts ├── analyzePulumiStack.ts ├── assignCores.ts ├── assignUsersToPersonalizedDigestVariation.ts ├── categorizedSquads.ts ├── checkNjordBalance.ts ├── cioSyncBasedOnActivity.ts ├── cli.ts ├── commentMarkdownToHTML.ts ├── common.ts ├── completedUserActions.ts ├── copyFeedSourceContentPreference.ts ├── copyFeedTagContentPreference.ts ├── copySourceMemberContentPreference.ts ├── copyUserCommentUpvoteData.ts ├── copyUserPostHiddenData.ts ├── export.ts ├── import.ts ├── indexSearch.ts ├── insertCategories.sql ├── insertCodeSnippets.ts ├── insertMissingAlerts.ts ├── insertMissingSourceFollows.ts ├── mutePublicSquadNotifications.ts ├── notificationSubscribedUsers.ts ├── retroFixCommentImages.ts ├── runWorkflow.ts ├── seedUserStreaks.ts ├── subscribeUsersToPersonalizedDigest.ts ├── syncMembersCount.ts ├── syncProductFlags.ts ├── syncSourceFlags.ts ├── topUpNjordBalance.ts ├── traces.ts ├── updateKeyword.ts ├── updateSource.ts ├── updateSquadsCategory.ts └── updateUserFeedSettingsFeedback.ts ├── docker-compose.yml ├── geoip └── .gitignore ├── jest.config.js ├── nodemon.json ├── ormconfig.js ├── package.json ├── patches └── mercurius-upload.patch ├── pg-init-scripts └── create-databases.sh ├── pnpm-lock.yaml ├── pull.ts ├── queries └── calculateTopReaders.sql ├── seeds ├── AdvancedSettings.json ├── Category.json ├── ExperimentVariant.json ├── Keyword.json ├── MarketingCta.json ├── Post.json ├── PostKeyword.json ├── Product.json ├── Source.json ├── SourceCategory.json ├── SourceMember.json ├── User.json ├── UserTopReader.json └── YouTubePost.json ├── src ├── Context.ts ├── auth.ts ├── background.ts ├── cio.ts ├── commands │ └── personalizedDigest.ts ├── common │ ├── apple │ │ ├── purchase.ts │ │ ├── subscription.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── async.ts │ ├── base64.ts │ ├── cloudinary.ts │ ├── connectRpc.ts │ ├── constants.ts │ ├── contentPreference.ts │ ├── crypto.ts │ ├── date.ts │ ├── datePageGenerator.ts │ ├── devcard.ts │ ├── experiment.ts │ ├── feed.ts │ ├── feedGenerator.ts │ ├── fibonacci.ts │ ├── flags.ts │ ├── geo.ts │ ├── googleCloud.ts │ ├── handles.ts │ ├── healthCheck.ts │ ├── index.ts │ ├── links.ts │ ├── mailing.ts │ ├── markdown.ts │ ├── njord.ts │ ├── number.ts │ ├── object.ts │ ├── paddle │ │ ├── cores │ │ │ ├── eventHandler.ts │ │ │ ├── index.ts │ │ │ └── processing.ts │ │ ├── index.ts │ │ ├── organization │ │ │ ├── eventHandler.ts │ │ │ ├── index.ts │ │ │ └── processing.ts │ │ ├── plus │ │ │ ├── eventHandler.ts │ │ │ └── processing.ts │ │ ├── pricing.ts │ │ └── slack.ts │ ├── pagination.ts │ ├── personalizedDigest.ts │ ├── plus │ │ ├── index.ts │ │ └── subscription.ts │ ├── post.ts │ ├── pubsub.ts │ ├── queryDataSource.ts │ ├── queryReadReplica.ts │ ├── rateLimit.ts │ ├── redis │ │ └── redisCounters.ts │ ├── redisCache.ts │ ├── reporting.ts │ ├── reputation.ts │ ├── search.ts │ ├── slack.ts │ ├── storekit.ts │ ├── streak.ts │ ├── streaming.ts │ ├── timezone.ts │ ├── twitter.ts │ ├── typedPubsub.ts │ ├── user.ts │ ├── userIntegration.ts │ ├── users.ts │ ├── utils.ts │ ├── vordr.ts │ └── vote.ts ├── compatibility │ ├── entity.ts │ ├── index.ts │ ├── publications.ts │ └── utils.ts ├── config.ts ├── cookies.ts ├── cron.ts ├── cron │ ├── calculateTopReaders.ts │ ├── checkAnalyticsReport.ts │ ├── checkReferralReminder.ts │ ├── cleanGiftedPlus.ts │ ├── cleanStaleUserTransactions.ts │ ├── cleanZombieImages.ts │ ├── cleanZombieUserCompany.ts │ ├── cleanZombieUsers.ts │ ├── cron.ts │ ├── dailyDigest.ts │ ├── generateSearchInvites.ts │ ├── hourlyNotifications.ts │ ├── index.ts │ ├── personalizedDigest.ts │ ├── syncSubscriptionWithCIO.ts │ ├── updateCurrentStreak.ts │ ├── updateDiscussionScore.ts │ ├── updateFeaturedComments.ts │ ├── updateHighlightedViews.ts │ ├── updateSourcePublicThreshold.ts │ ├── updateSourceTagView.ts │ ├── updateTagRecommendations.ts │ ├── updateTagsStr.ts │ ├── updateTrending.ts │ ├── updateViews.ts │ └── validateActiveUsers.ts ├── data-source.ts ├── dataLoaderService.ts ├── db.ts ├── directive │ ├── auth.ts │ ├── feedPlus.ts │ ├── rateLimit.ts │ ├── rateLimitCounter.ts │ └── url.ts ├── entity │ ├── ActiveView.ts │ ├── AdvancedSettings.ts │ ├── Alerts.ts │ ├── Banner.ts │ ├── Bookmark.ts │ ├── BookmarkList.ts │ ├── Category.ts │ ├── Checkpoint.ts │ ├── ClaimableItem.ts │ ├── Comment.ts │ ├── CommentMention.ts │ ├── CommentReport.ts │ ├── Company.ts │ ├── ContentImage.ts │ ├── DevCard.ts │ ├── DisallowHandle.ts │ ├── ExperimentVariant.ts │ ├── Feature.ts │ ├── Feed.ts │ ├── FeedAdvancedSettings.ts │ ├── FeedSource.ts │ ├── FeedTag.ts │ ├── Invite.ts │ ├── Keyword.ts │ ├── MarketingCta.ts │ ├── Organization.ts │ ├── PopularPost.ts │ ├── PopularSource.ts │ ├── PopularTag.ts │ ├── PopularVideoPost.ts │ ├── PopularVideoSource.ts │ ├── PostKeyword.ts │ ├── PostReport.ts │ ├── PostTag.ts │ ├── Product.ts │ ├── Prompt.ts │ ├── ReputationEvent.ts │ ├── Settings.ts │ ├── Source.ts │ ├── SourceDisplay.ts │ ├── SourceFeed.ts │ ├── SourceMember.ts │ ├── SourcePostModeration.ts │ ├── SourceRequest.ts │ ├── SourceTagView.ts │ ├── SquadPublicRequest.ts │ ├── Submission.ts │ ├── TagRecommendation.ts │ ├── TagSegment.ts │ ├── TrendingPost.ts │ ├── TrendingSource.ts │ ├── TrendingTag.ts │ ├── UserCompany.ts │ ├── UserIntegration.ts │ ├── UserReport.ts │ ├── UserSourceIntegration.ts │ ├── View.ts │ ├── common.ts │ ├── contentPreference │ │ ├── ContentPreference.ts │ │ ├── ContentPreferenceKeyword.ts │ │ ├── ContentPreferenceOrganization.ts │ │ ├── ContentPreferenceSource.ts │ │ ├── ContentPreferenceUser.ts │ │ ├── ContentPreferenceWord.ts │ │ └── types.ts │ ├── index.ts │ ├── notifications │ │ ├── NotificationAttachmentV2.ts │ │ ├── NotificationAvatarV2.ts │ │ ├── NotificationPreference.ts │ │ ├── NotificationPreferenceComment.ts │ │ ├── NotificationPreferencePost.ts │ │ ├── NotificationPreferenceSource.ts │ │ ├── NotificationPreferenceUser.ts │ │ ├── NotificationV2.ts │ │ ├── UserNotification.ts │ │ └── index.ts │ ├── posts │ │ ├── ArticlePost.ts │ │ ├── CollectionPost.ts │ │ ├── FreeformPost.ts │ │ ├── Post.ts │ │ ├── PostCodeSnippet.ts │ │ ├── PostMention.ts │ │ ├── PostQuestion.ts │ │ ├── PostRelation.ts │ │ ├── SharePost.ts │ │ ├── WelcomePost.ts │ │ ├── YouTubePost.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── sources │ │ ├── SourceCategory.ts │ │ └── SourceReport.ts │ └── user │ │ ├── BotUser.ts │ │ ├── DeletedUser.ts │ │ ├── User.ts │ │ ├── UserAction.ts │ │ ├── UserComment.ts │ │ ├── UserMarketingCta.ts │ │ ├── UserPersonalizedDigest.ts │ │ ├── UserPost.ts │ │ ├── UserState.ts │ │ ├── UserStats.ts │ │ ├── UserStreak.ts │ │ ├── UserStreakAction.ts │ │ ├── UserTopReader.ts │ │ ├── UserTransaction.ts │ │ ├── index.ts │ │ └── utils.ts ├── errors.ts ├── graphorm │ ├── graphorm.ts │ └── index.ts ├── graphql.ts ├── growthbook.ts ├── http.ts ├── ids.ts ├── index.ts ├── integrations │ ├── analytics.ts │ ├── automation │ │ ├── automations.ts │ │ ├── index.ts │ │ ├── retool.ts │ │ └── types.ts │ ├── feed │ │ ├── clients.ts │ │ ├── configs.ts │ │ ├── generators.ts │ │ ├── index.ts │ │ └── types.ts │ ├── freyja │ │ ├── clients.ts │ │ ├── index.ts │ │ └── types.ts │ ├── garmr.ts │ ├── index.ts │ ├── lofn │ │ ├── clients.ts │ │ ├── index.ts │ │ └── types.ts │ ├── magni.ts │ ├── meilisearch.ts │ ├── mimir │ │ ├── clients.ts │ │ ├── index.ts │ │ └── types.ts │ ├── networkError.ts │ ├── openExchangeRates.ts │ ├── retry.ts │ ├── skadi │ │ ├── clients.ts │ │ ├── index.ts │ │ └── types.ts │ └── snotra │ │ ├── clients.ts │ │ ├── index.ts │ │ └── types.ts ├── kratos.ts ├── logger.ts ├── migration │ ├── 1587564396149-Notification.ts │ ├── 1587658873043-Source.ts │ ├── 1587762408779-SourceRequest.ts │ ├── 1587987622739-SourceFeed-Unique.ts │ ├── 1588054676723-Settings.ts │ ├── 1588076515290-Post.ts │ ├── 1588087351218-Tag.ts │ ├── 1588088109887-Bookmark.ts │ ├── 1588172477587-NullablePublishedAt.ts │ ├── 1588188622554-RemovePostDisplay.ts │ ├── 1588189138218-OptionalCanonicalUrl.ts │ ├── 1588313795555-View.ts │ ├── 1588598643689-UpdateIndex.ts │ ├── 1588607400115-TagCountIndex.ts │ ├── 1588662906958-HiddenPost.ts │ ├── 1588675500678-FixView.ts │ ├── 1588676028973-BookmarkIndex.ts │ ├── 1588863598567-Feed.ts │ ├── 1589575400734-Checkpoint.ts │ ├── 1589616762057-UpdateScore.ts │ ├── 1589696758621-UpdateViewPK.ts │ ├── 1589700555463-UpdateScoreIndex.ts │ ├── 1589812598274-Banner.ts │ ├── 1590596508994-BookmarkList.ts │ ├── 1591621000530-Integration.ts │ ├── 1593949036242-TagSegment.ts │ ├── 1595059874588-TagsInPost.ts │ ├── 1595072041410-OpenNewTabSetting.ts │ ├── 1595246727426-PostShortId.ts │ ├── 1595249902828-UniqueShortId.ts │ ├── 1595345916300-User.ts │ ├── 1595349858627-UserNullable.ts │ ├── 1595424192556-Upvote.ts │ ├── 1595433730831-Comment.ts │ ├── 1596003287300-FeaturedComment.ts │ ├── 1598351006459-SourceFeedLastFetched.ts │ ├── 1600267654798-Reputation.ts │ ├── 1600350754278-RetroReputation.ts │ ├── 1600691199678-Username.ts │ ├── 1602427333872-ActiveSources.ts │ ├── 1602486972915-IndexViews.ts │ ├── 1602572107385-PostAuthor.ts │ ├── 1602675015961-ReputationOne.ts │ ├── 1602762420580-SentAnalyticsReport.ts │ ├── 1603894888844-SourceRankBoost.ts │ ├── 1604234608029-ViewsThreshold.ts │ ├── 1606130760128-Trending.ts │ ├── 1606393430015-RoomyDefault.ts │ ├── 1609057573320-EcoDefault.ts │ ├── 1609163543654-Keywords.ts │ ├── 1609232929522-KeywordSynonym.ts │ ├── 1609235813074-KeywordOccurrences.ts │ ├── 1609237342955-KeywordOccurrencesDefaultUpdate.ts │ ├── 1611589310852-TagsStrCheckpoint.ts │ ├── 1616400644669-PostDiscussionScore.ts │ ├── 1620214912330-PostBanned.ts │ ├── 1620304260355-PostReport.ts │ ├── 1620311370218-CommentLastUpdate.ts │ ├── 1622476886845-AddDisplayPropsToSource.ts │ ├── 1622483114702-FillSourceNameAndImage.ts │ ├── 1622487935336-AddSourcePrivateColumn.ts │ ├── 1623319349676-FullTextSearch.ts │ ├── 1623847855158-PostToc.ts │ ├── 1625059927393-BlockedTags.ts │ ├── 1626085244394-PostKeywordStatus.ts │ ├── 1626185470708-DevCard.ts │ ├── 1627479592436-DevCardEligible.ts │ ├── 1628440848669-ReplicaFull.ts │ ├── 1628603355496-PostReportReplica.ts │ ├── 1628691360469-UserCreatedAt.ts │ ├── 1631197656554-DeleteViewColumns.ts │ ├── 1631692219532-UserColumns.ts │ ├── 1633613931533-PostMetadataChanged.ts │ ├── 1634186001253-TagCategories.ts │ ├── 1634187611640-AlertsEntity.ts │ ├── 1635136469049-FeedAdvancedSettingsEntity.ts │ ├── 1635231888769-PostSummary.ts │ ├── 1635425073172-AdvancedSettingsArrayAsInt.ts │ ├── 1635777599202-ReportReasonComment.ts │ ├── 1636372778451-AlertsFullReplication.ts │ ├── 1636431439048-ViewHiddenColumn.ts │ ├── 1636615784674-AddTimeZoneToUser.ts │ ├── 1636725824921-AlertsRankColumn.ts │ ├── 1637823009822-HTMLComments.ts │ ├── 1638431396006-OpenSidebarSettings.ts │ ├── 1639645108022-SidebarExpandedSettings.ts │ ├── 1639656961912-UnnecessarySettingsProperties.ts │ ├── 1640931306714-AlertsMyFeedColumn.ts │ ├── 1641904779220-AlertMyFeedDefaultValue.ts │ ├── 1642055432413-SettingsEnabledSorting.ts │ ├── 1642656948858-SettingsCustomLinks.ts │ ├── 1643634601290-BookmarkSlug.ts │ ├── 1643720074970-OptOutWeeklyGoal.ts │ ├── 1644408031624-addViewHiddenIndex.ts │ ├── 1645627361402-ActiveView.ts │ ├── 1646059151489-AddUniqueConstraintForURLs.ts │ ├── 1646123000110-CommentMentionEntity.ts │ ├── 1646231933553-AddOptOutCompanion.ts │ ├── 1649851992869-AddCompanionHelper.ts │ ├── 1650266213744-ReputationEventEntity.ts │ ├── 1650459501417-AddCompanionExpanded.ts │ ├── 1651214210491-AutoDismissSettings.ts │ ├── 1651802478465-SubmissionEntity.ts │ ├── 1655199148489-UserStateEntity.ts │ ├── 1655452252620-ChangeSubmissionStatus.ts │ ├── 1658242193484-AddNewOneAITags.ts │ ├── 1660205249491-AddReferralIncreaseUsername.ts │ ├── 1660640181400-UniqueSocialHandles.ts │ ├── 1669630762042-DeleteLegacyNotification.ts │ ├── 1669639147706-Notification.ts │ ├── 1669735640941-AvatarName.ts │ ├── 1670243653439-NotificationReference.ts │ ├── 1671161744023-EmailNotificationPreference.ts │ ├── 1671454144328-PostTypes.ts │ ├── 1671547534880-Squad.ts │ ├── 1671719516235-NullableSourceImage.ts │ ├── 1671725294007-TokenToString.ts │ ├── 1671781195018-RequiredHandle.ts │ ├── 1671874590903-SourceMemberCDC.ts │ ├── 1672583245776-SourceImageDefault.ts │ ├── 1674543814613-Feature.ts │ ├── 1674979079360-UserReferral.ts │ ├── 1675181201626-PrivatePost.ts │ ├── 1675235844097-SourceReplica.ts │ ├── 1675687066898-SourceCreatedAt.ts │ ├── 1675936666556-AlertsChangelog.ts │ ├── 1677030838294-AlertViewedTour.ts │ ├── 1677645829524-PostOrigin.ts │ ├── 1678256072490-PostOptionality.ts │ ├── 1678365541578-ActivePostView.ts │ ├── 1680519844392-MemberPostingRank.ts │ ├── 1681297594048-OwnerToAdmin.ts │ ├── 1681817520844-MemberInviteRank.ts │ ├── 1682497694917-FreeformPost.ts │ ├── 1682673635953-ActionEntity.ts │ ├── 1682690930599-ContentCuration.ts │ ├── 1683690019569-PostMentionEntity.ts │ ├── 1683721525235-DummyUser.ts │ ├── 1683868176744-PostMentionTracking.ts │ ├── 1684239736772-PinnedPost.ts │ ├── 1684485068564-DropActivePost.ts │ ├── 1684512961790-FixFeedTagIndex.ts │ ├── 1685257633038-ContentImage.ts │ ├── 1685509975573-PostOrder.ts │ ├── 1685524779381-UserReferralOrigin.ts │ ├── 1685957377259-PostTitleHtml.ts │ ├── 1686735674028-Downvote.ts │ ├── 1686736666472-PostDownvotes.ts │ ├── 1686749310179-UpvoteTrigger.ts │ ├── 1686835784506-DownvoteTrigger.ts │ ├── 1687171948216-PostReportTags.ts │ ├── 1687422430370-DisallowHandle.ts │ ├── 1687956058691-CommentReport.ts │ ├── 1687960180394-PostFlags.ts │ ├── 1688567430880-CommentReportReplica.ts │ ├── 1689068187186-PostFlagsDefaultValueTrigger.ts │ ├── 1689654133517-NotificationPreference.ts │ ├── 1689851339253-PromoteDateTime.ts │ ├── 1689854830946-WelcomeShowOnFeed.ts │ ├── 1689858892658-PublicSourceData.ts │ ├── 1690196456312-SourceMemberFlags.ts │ ├── 1690963996461-PostYggdrasilId.ts │ ├── 1691592305690-PostQuestion.ts │ ├── 1691668733329-UserPost.ts │ ├── 1692265909018-VoteTriggers.ts │ ├── 1692536195223-AlertsBanner.ts │ ├── 1693820674317-SourceImageDefault.ts │ ├── 1693903931385-UserPostVoteIndex.ts │ ├── 1694443626041-ViewIndex.ts │ ├── 1695735320395-UserPersonalizedDigest.ts │ ├── 1695795022369-FeatureValue.ts │ ├── 1696422431723-SourceIndex.ts │ ├── 1696520516809-IndexEmail.ts │ ├── 1697546410147-KeywordFlags.ts │ ├── 1697624128431-TagRecommendation.ts │ ├── 1697642145710-TagSearchIndex.ts │ ├── 1697708253831-Invites.ts │ ├── 1698144204806-UserCreatedAt.ts │ ├── 1698230451545-OnboardingTagsIndex.ts │ ├── 1698317123443-DigestVariation.ts │ ├── 1698323453474-OnboardingTagsTextIndex.ts │ ├── 1698838065767-AlertsFlags.ts │ ├── 1699284661116-UserPersonalizedDigestSubscribeTrigger.ts │ ├── 1699372102745-UserPersonalizedDigestLastSendDate.ts │ ├── 1699508350425-UserAlertsTrigger.ts │ ├── 1700064380738-PostCollection.ts │ ├── 1700559262252-AlertsReplicaRemoval.ts │ ├── 1700661785471-PostCollectionSources.ts │ ├── 1700735236452-YouTubePost.ts │ ├── 1700836062543-Notification.ts │ ├── 1701100478531-PostRelationReplica.ts │ ├── 1702045361925-NotificationV2.ts │ ├── 1702112901986-NotificationV2Unique.ts │ ├── 1702224104423-NotificationV2Triggers.ts │ ├── 1702655844110-AdvancedSettings.ts │ ├── 1702899818874-DeleteNotificationV1.ts │ ├── 1702914653781-NotificationAttachmentTrigger.ts │ ├── 1703422184359-AdditionalMissingIndexes.ts │ ├── 1703668189004-ProfileV2.ts │ ├── 1704816100142-RemoveUpvoteDownvoteHiddenPostTables.ts │ ├── 1704948666033-GhostUserProtect.ts │ ├── 1704973835929-RenameInactiveUser.ts │ ├── 1706107563960-UserStreakEntity.ts │ ├── 1706110501504-UserStreakTrigger.ts │ ├── 1706363895498-SharePostTrigger.ts │ ├── 1706779866978-SharedPostTriggerFix.ts │ ├── 1707227186596-DevCard20Settings.ts │ ├── 1707921624847-BotUser.ts │ ├── 1707983477045-NewIndexes.ts │ ├── 1707995262750-IndexCleanup.ts │ ├── 1708024370161-readingStreaksAlert.ts │ ├── 1708364779258-PostGin.ts │ ├── 1708498483203-DevCardIndexes.ts │ ├── 1708961227968-PostLanguage.ts │ ├── 1709002546321-UserAcquisitionChannel.ts │ ├── 1709739543179-FeedSourceBlockUnsubscribe.ts │ ├── 1710235892939-MarketingCta.ts │ ├── 1710417425669-UserPersonalizedDigestSendType.ts │ ├── 1711384423929-CommentDownvotes.ts │ ├── 1711384643544-UserComment.ts │ ├── 1711649106510-UserCommentTriggers.ts │ ├── 1711962800897-ReadingReminder.ts │ ├── 1712134121379-RemoveDigestTimezone.ts │ ├── 1712228361792-PostSlug.ts │ ├── 1712315208717-PostSlugMaxLength.ts │ ├── 1712910394483-KeywordIndexes.ts │ ├── 1712930614437-SourceTagView.ts │ ├── 1713194178000-PostContentMeta.ts │ ├── 1713775430475-MarketingCta.ts │ ├── 1713784472035-UserExperienceLevel.ts │ ├── 1714045647906-LastBootPopup.ts │ ├── 1714063032247-YggdrasilIdUnique.ts │ ├── 1714140296134-SourceFlags.ts │ ├── 1714395908762-PostContentQuality.ts │ ├── 1714633817175-SquadTotalPostsTrigger.ts │ ├── 1714662136871-SquadTotalUpvotesTrigger.ts │ ├── 1714667947050-SquadTotalViewsTrigger.ts │ ├── 1715072633296-NotificationPreferenceFk.ts │ ├── 1715095164142-AddUserRelationFK.ts │ ├── 1715346601781-CustomFeed.ts │ ├── 1715592299433-SourceFlagFeaturedIndex.ts │ ├── 1715679235259-AdvancedSettingsUpgrade.ts │ ├── 1715682646033-SourceAdvancedSettingsRemoval.ts │ ├── 1715795499542-SquadPublicRequest.ts │ ├── 1716379554138-SquadPublicRequestReplica.ts │ ├── 1716976350324-AlertFeedSettingsFeedback.ts │ ├── 1717019094737-HighlightedViews.ts │ ├── 1717144015912-OptOutReadingStreak.ts │ ├── 1717589112159-SourceStatsTrigger.ts │ ├── 1718023112446-SidebarExpanded.ts │ ├── 1718116498943-OnboardingChecklistSetting.ts │ ├── 1718643810609-UserStats.ts │ ├── 1719227161827-AdvancedSettingsCopy.ts │ ├── 1719406451367-ReplicaUserStreak.ts │ ├── 1720102631131-UserIntegration.ts │ ├── 1720103113795-UserSourceIntegration.ts │ ├── 1720104393427-AdvancedSettingsCopy.ts │ ├── 1720502712657-BookmarkRemindAt.ts │ ├── 1721296457196-CommentTriggers.ts │ ├── 1721301769783-PostStatsUpdated.ts │ ├── 1721302074280-PostCommentTriggers.ts │ ├── 1721316842386-BookmarkReplica.ts │ ├── 1721377128858-FeedSettingsBlocked.ts │ ├── 1721403018310-NewSocialHandles.ts │ ├── 1721903257825-Vordr.ts │ ├── 1722585215836-UserWeekStart.ts │ ├── 1722942338711-UserEmailIndex.ts │ ├── 1723135865854-SubmissionFlags.ts │ ├── 1723215750644-UserGinIndexes.ts │ ├── 1723579275223-SourceCategory.ts │ ├── 1723670727196-UserStreakAction.ts │ ├── 1723751266939-AlertShowRecoverStreak.ts │ ├── 1724076968672-LeaderboardIndexes.ts │ ├── 1724144430626-Company.ts │ ├── 1724168427191-UserLanguage.ts │ ├── 1724657443097-CompanyImage.ts │ ├── 1724764618023-UserStreakActionTimestamp.ts │ ├── 1724925076759-UserCompanyFlags.ts │ ├── 1724929128486-PostCodeSnippets.ts │ ├── 1725342495373-SourceReportEntity.ts │ ├── 1725981526888-SourceFlagMembersIncrement.ts │ ├── 1725981967186-SourceMembersCountDecrement.ts │ ├── 1725988464760-SourceMembersIndex.ts │ ├── 1726147441577-SourceMembersCountFix.ts │ ├── 1726149087071-ContentPreference.ts │ ├── 1726562236767-SourceCategoryTitleSlug.ts │ ├── 1726584940063-SourceCategoryPriority.ts │ ├── 1726691862710-User.ts │ ├── 1727025212763-SourceReportReplica.ts │ ├── 1727345240782-SourceFlagsIndexes.ts │ ├── 1727627009736-UserCompanyUserIndex.ts │ ├── 1727848363303-SourceType.ts │ ├── 1727958325934-SourceModerationRequired.ts │ ├── 1728030331673-UserNotificationUniqueKey.ts │ ├── 1728563175020-ContentPreferenceFeedKeyword.ts │ ├── 1728656542326-ContentPreferenceSource.ts │ ├── 1728894860612-UserNotificationIndex.ts │ ├── 1728910864611-UserStreakActionIndex.ts │ ├── 1728978352110-VordrUser.ts │ ├── 1728989030929-ViewRelationUserId.ts │ ├── 1730214092490-SettingsFlags.ts │ ├── 1730216355432-UserTopReader.ts │ ├── 1730377577679-ContentPreferenceUserStatusTypeIndex.ts │ ├── 1730379505031-AlertsTopReader.ts │ ├── 1730390077794-ContentPreferenceSquad.ts │ ├── 1730710262320-PostDeletedStatsTrigger.ts │ ├── 1730717128269-UserStatsTopReader.ts │ ├── 1730860362949-UserStatsIndex.ts │ ├── 1730890313095-SourcePostModeration.ts │ ├── 1730903564613-UserTopReader.ts │ ├── 1730978012371-SourcePostModerationReplica.ts │ ├── 1730992663679-SourceModerationIndexes.ts │ ├── 1731044639557-ContentPreferencePKType.ts │ ├── 1731065998183-SourceDefaultImage.ts │ ├── 1731315131357-FeedTagDateColumns.ts │ ├── 1731398655167-UserSubscriptionFlags.ts │ ├── 1731423979883-ContentPreferencePKFml.ts │ ├── 1731424255967-ContentPreferenceReplicaFull.ts │ ├── 1731520135216-ContentPreferencesReadIndexes.ts │ ├── 1731913456006-UserSubscriptionIndex.ts │ ├── 1732464610853-ContentPreferenceIndexes.ts │ ├── 1733244072344-SourcePostModerationFlagsVordr.ts │ ├── 1733312873990-BookmarkListRevamp.ts │ ├── 1733918463705-BookmarkListLowerIndex.ts │ ├── 1733932616522-BookmarkUpdatedAt.ts │ ├── 1733987625520-UserDefaultFeed.ts │ ├── 1734071060646-FeedTypeColumn.ts │ ├── 1734294820179-UserCioRegistered.ts │ ├── 1735645895838-Prompt.ts │ ├── 1736363898760-UserReport.ts │ ├── 1736519518183-PostTranslations.ts │ ├── 1736761342821-UserReportReplica.ts │ ├── 1737231506705-Users.ts │ ├── 1737718327973-DeletedPost.ts │ ├── 1739337003828-UserDropProfileConfirmed.ts │ ├── 1739339702842-PostDropRationPlaceholder.ts │ ├── 1739429058124-UserEmailConfirmed.ts │ ├── 1739787441374-UserInfoEmailConfirmedIdx.ts │ ├── 1740414691929-UserTransaction.ts │ ├── 1740757299661-SettingsCommentsAlgo.ts │ ├── 1741184461161-AwardPost.ts │ ├── 1741187989804-AwardPostTriggers.ts │ ├── 1741621921297-AwardComment.ts │ ├── 1741624559787-AwardCommentTriggers.ts │ ├── 1741860900963-UserActiveSubscriptionDeleteProtect.ts │ ├── 1741863600700-UserUniqueAppAccountToken.ts │ ├── 1741868793950-UserTransactionProcessor.ts │ ├── 1742566045271-UserCoresRole.ts │ ├── 1743501278867-UserTransactionProviderId.ts │ ├── 1743605489726-ExperimentVariant.ts │ ├── 1743776947033-UserTransactionValueFee.ts │ ├── 1744015053751-UserNotificationReplica.ts │ ├── 1744027556819-UserAwardEmail.ts │ ├── 1744037437681-TransactionIndexes.ts │ ├── 1744110320719-CoresRoleDefaultNone.ts │ ├── 1744965354822-DeletedUser.ts │ ├── 1744972478032-SpecialUserDeleteTrigger.ts │ ├── 1745511937419-PostSourceIndex.ts │ ├── 1745812404446-PostReportLength.ts │ ├── 1746564832985-AwardIdIndex.ts │ ├── 1746779086984-DropRoleContentPreference.ts │ ├── 1746779368405-Organization.ts │ ├── 1747238500907-ClaimableItem.ts │ ├── 1747300279523-StaleUserTransactionIndex.ts │ ├── 1747309505414-UserTransactionValueDesc.ts │ ├── 1747922559941-SystemUser.ts │ └── 1748371994832-ExperimentVariationType.ts ├── notifications │ ├── builder.ts │ ├── common.ts │ ├── generate.ts │ ├── icons.ts │ ├── index.ts │ └── types.ts ├── onesignal.ts ├── paddle.ts ├── plusSubscription.ts ├── redis.ts ├── remoteConfig.ts ├── roles.ts ├── routes │ ├── alerts.ts │ ├── automations.ts │ ├── boot.ts │ ├── devcards.ts │ ├── index.ts │ ├── integrations │ │ ├── index.ts │ │ └── slack.ts │ ├── localAds.ts │ ├── notifications.ts │ ├── private.ts │ ├── private │ │ ├── kvasir.ts │ │ ├── rpc.ts │ │ └── snotra.ts │ ├── redirector.ts │ ├── redirects.ts │ ├── rss.ts │ ├── sitemaps.ts │ ├── users.ts │ ├── webhooks.ts │ ├── webhooks │ │ ├── apple.ts │ │ ├── customerio.ts │ │ └── paddle │ │ │ └── index.ts │ └── whoami.ts ├── schema │ ├── actions.ts │ ├── alerts.ts │ ├── bookmarks.ts │ ├── comments.ts │ ├── common.ts │ ├── compatibility.ts │ ├── contentPreference.ts │ ├── devcards.ts │ ├── feeds.ts │ ├── integrations.ts │ ├── keywords.ts │ ├── leaderboard.ts │ ├── njord.ts │ ├── notifications.ts │ ├── organizations.ts │ ├── paddle.ts │ ├── posts.ts │ ├── prompts.ts │ ├── search.ts │ ├── settings.ts │ ├── sourceRequests.ts │ ├── sources.ts │ ├── submissions.ts │ ├── tags.ts │ ├── trace.ts │ ├── urlShortener.ts │ └── users.ts ├── subscription.ts ├── telemetry │ ├── common.ts │ ├── index.ts │ ├── metrics.ts │ └── opentelemetry.ts ├── templates │ └── devcard.ts ├── temporal │ ├── client.ts │ ├── common.ts │ ├── config.ts │ ├── local.ts │ ├── notifications │ │ ├── activities.ts │ │ ├── index.ts │ │ ├── utils.ts │ │ └── workflows.ts │ └── worker.ts ├── tracking.ts ├── types.ts └── workers │ ├── bannerAdded.ts │ ├── bannerDeleted.ts │ ├── cdc │ ├── common.ts │ ├── notifications.ts │ └── primary.ts │ ├── commentCommentedSlackMessage.ts │ ├── commentDownvoteCanceledRep.ts │ ├── commentDownvotedRep.ts │ ├── commentEditedImages.ts │ ├── commentMarkdownImages.ts │ ├── commentUpvoteCanceledRep.ts │ ├── commentUpvotedRep.ts │ ├── deleteCloudinaryImage.ts │ ├── digestDeadLetterLog.ts │ ├── experimentAllocated.ts │ ├── generators │ ├── generateEditImagesHandler.ts │ ├── generateNewImagesHandler.ts │ └── index.ts │ ├── index.ts │ ├── newNotificationV2Mail.ts │ ├── newNotificationV2Push.ts │ ├── newNotificationV2RealTime.ts │ ├── newView.ts │ ├── notifications │ ├── articleAnalytics.ts │ ├── articleNewCommentCommentCommented.ts │ ├── articleNewCommentPostCommented.ts │ ├── articleReportApproved.ts │ ├── articleUpvoteMilestone.ts │ ├── collectionUpdated.ts │ ├── commentMention.ts │ ├── commentReply.ts │ ├── commentUpvoteMilestone.ts │ ├── communityPicksFailed.ts │ ├── communityPicksGranted.ts │ ├── devCardUnlocked.ts │ ├── index.ts │ ├── postAdded.ts │ ├── postAddedUserNotification.ts │ ├── postBookmarkReminder.ts │ ├── postMention.ts │ ├── sourceMemberRoleChanged.ts │ ├── sourcePostModerationApprovedNotification.ts │ ├── sourcePostModerationRejectedNotification.ts │ ├── sourcePostModerationSubmittedNotification.ts │ ├── sourceRequest.ts │ ├── squadFeaturedUpdated.ts │ ├── squadMemberJoined.ts │ ├── squadPublicRequestNotification.ts │ ├── userGiftedPlusNotification.ts │ ├── userStreakResetNotification.ts │ ├── userTopReaderAdded.ts │ ├── utils.ts │ └── worker.ts │ ├── organization │ ├── organizationUserJoined.ts │ ├── organizationUserLeft.ts │ └── organizationUserRemoved.ts │ ├── personalizedDigestEmail.ts │ ├── postAddedSlackChannelSend.ts │ ├── postBannedRep.ts │ ├── postCommentedRedis.ts │ ├── postCommentedSlackMessage.ts │ ├── postDeletedCommentsCleanup.ts │ ├── postDeletedSharedPostCleanup.ts │ ├── postDownvoteCanceledRep.ts │ ├── postDownvotedRep.ts │ ├── postEditedFreeformImages.ts │ ├── postFreeformImages.ts │ ├── postScoutMatchedSlack.ts │ ├── postTranslated.ts │ ├── postUpdated.ts │ ├── postUpvoteCanceledRep.ts │ ├── postUpvotedRedis.ts │ ├── postUpvotedRep.ts │ ├── sourcePrivacyUpdated.ts │ ├── sourceRequestApprovedRep.ts │ ├── sourceSquadCreatedUserAction.ts │ ├── transactionBalanceLog.ts │ ├── transactions │ ├── userBoughtCores.ts │ └── userReceivedAward.ts │ ├── updateComments.ts │ ├── userCompanyApprovedCio.ts │ ├── userCreatedPersonalizedDigestSendType.ts │ ├── userDeletedCio.ts │ ├── userReadmeImages.ts │ ├── userStreakUpdatedCio.ts │ ├── userUpdatedCio.ts │ ├── userUpdatedPlusSubscriptionCustomFeed.ts │ ├── userUpdatedPlusSubscriptionSquad.ts │ ├── usernameChanged.ts │ ├── vordrPostCommentPrevented.ts │ ├── vordrPostPrevented.ts │ └── worker.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .garden/ 3 | .infra/ 4 | .circleci/ 5 | node_modules/ 6 | .env.development 7 | .env.production 8 | local.sh 9 | prod.sh 10 | schema.graphql 11 | debezium/conf/key.json 12 | debezium/data 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | max_line_length = 80 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/* 2 | src/migration/* 3 | infra/node_modules 4 | -------------------------------------------------------------------------------- /.git-crypt/.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not edit this file. To specify the files to encrypt, create your own 2 | # .gitattributes file in the directory where your files are. 3 | * !filter !diff 4 | *.gpg binary 5 | -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/27DE4D956FCC3AEFFDE6A223829FB1011004D789.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydotdev/daily-api/5707d3333e59f1b45ca0ba9fdf84e00303ebdb52/.git-crypt/keys/default/0/27DE4D956FCC3AEFFDE6A223829FB1011004D789.gpg -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/74235A652201EA7C16D5D8D72472D3473F0C19F2.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydotdev/daily-api/5707d3333e59f1b45ca0ba9fdf84e00303ebdb52/.git-crypt/keys/default/0/74235A652201EA7C16D5D8D72472D3473F0C19F2.gpg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | helm/values/** filter=git-crypt diff=git-crypt -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dailydotdev/web-team @capJavert 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .garden/ 3 | build/ 4 | node_modules/ 5 | .env.development 6 | .env.production 7 | local.sh 8 | prod.sh 9 | schema.graphql 10 | debezium/conf/key.json 11 | debezium/data 12 | coverage 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Untitled GraphQL Schema", 3 | "schemaPath": "schema.graphql", 4 | "extensions": { 5 | "endpoints": { 6 | "Default GraphQL Endpoint": { 7 | "url": "http://localhost:5000/graphql", 8 | "headers": { 9 | "user-agent": "JS GraphQL" 10 | }, 11 | "introspect": false 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.infra/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | .env 4 | -------------------------------------------------------------------------------- /.infra/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /.infra/.nvmrc: -------------------------------------------------------------------------------- 1 | 22.13 2 | -------------------------------------------------------------------------------- /.infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: api 2 | runtime: 3 | name: nodejs 4 | options: 5 | packagemanager: pnpm 6 | description: Infrastructure for daily-api 7 | -------------------------------------------------------------------------------- /.infra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "scripts": { 4 | "prepare": "corepack enable || true" 5 | }, 6 | "devDependencies": { 7 | "@types/node": "22.13.x" 8 | }, 9 | "dependencies": { 10 | "@dailydotdev/pulumi-common": "^2.8.0", 11 | "@pulumi/gcp": "^8.19.1", 12 | "@pulumi/kubernetes": "^4.21.1", 13 | "@pulumi/pulumi": "^3.150.0" 14 | }, 15 | "packageManager": "pnpm@9.14.4+sha256.26a726b633b629a3fabda006f696ae4260954a3632c8054112d7ae89779e5f9a", 16 | "volta": { 17 | "node": "22.13.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.infra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "outDir": "bin", 5 | "target": "es2016", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "pretty": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "files": [ 16 | "index.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.13 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["firsttris.vscode-jest-runner", "qufiwefefwoyn.inline-sql-syntax"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [{ 4 | "type": "node", 5 | "request": "launch", 6 | "name": "Node: Nodemon", 7 | "runtimeVersion": "16", 8 | "runtimeExecutable": "npm", 9 | "runtimeArgs": ["run", "dev"], 10 | "outputCapture": "std", 11 | }], 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jestrunner.codeLensSelector": "**/*{.test,.spec,__tests__/**/*}.{js,jsx,ts,tsx}", 3 | "typescript.updateImportsOnFileMove.enabled": "always", 4 | "typescript.preferences.preferTypeOnlyAutoImports": true, 5 | "typescript.preferences.includePackageJsonAutoImports": "on", 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "plugins": ["jest-extended"] 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/submissions.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mutation submitArticle should create a submission entity if the url is valid 1`] = ` 4 | Object { 5 | "submitArticle": Object { 6 | "post": null, 7 | "reason": null, 8 | "result": "succeed", 9 | "submission": Object { 10 | "id": Any, 11 | "status": "STARTED", 12 | "userId": "1", 13 | }, 14 | }, 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/tags.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`query searchTags should search for tags and order by value 1`] = ` 4 | Object { 5 | "searchTags": Object { 6 | "hits": Array [ 7 | Object { 8 | "name": "development", 9 | }, 10 | Object { 11 | "name": "webdev", 12 | }, 13 | ], 14 | "query": "dev", 15 | }, 16 | } 17 | `; 18 | 19 | exports[`query searchTags should take into account keyword synonyms 1`] = ` 20 | Object { 21 | "searchTags": Object { 22 | "hits": Array [], 23 | "query": "web-dev", 24 | }, 25 | } 26 | `; 27 | -------------------------------------------------------------------------------- /__tests__/common/fibonacci.ts: -------------------------------------------------------------------------------- 1 | import { isFibonacci } from '../../src/common/fibonacci'; 2 | 3 | describe('isFibonacci tests', () => { 4 | it('should return true for fibonacci numbers', () => { 5 | const fibs = [0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]; 6 | fibs.forEach((n) => { 7 | expect(isFibonacci(n)).toBeTruthy(); 8 | }); 9 | }); 10 | 11 | it('should return false for non-fibonacci numbers', () => { 12 | const nonFibs = [4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 99]; 13 | nonFibs.forEach((n) => { 14 | expect(isFibonacci(n)).toBeFalsy(); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/common/search.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultSearchLimit, 3 | getSearchLimit, 4 | maxSearchLimit, 5 | } from '../../src/common/search'; 6 | 7 | describe('getSearchLimit', () => { 8 | it('should return default limit', () => { 9 | expect(getSearchLimit({ limit: undefined })).toBe(defaultSearchLimit); 10 | }); 11 | 12 | it('should return custom search limit', () => { 13 | expect(getSearchLimit({ limit: 20 })).toBe(20); 14 | }); 15 | 16 | it('should return max search limit', () => { 17 | expect(getSearchLimit({ limit: 200 })).toBe(maxSearchLimit); 18 | }); 19 | 20 | it('should return min search limit', () => { 21 | expect(getSearchLimit({ limit: 0 })).toBe(1); 22 | expect(getSearchLimit({ limit: -5 })).toBe(1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /__tests__/cron/__snapshots__/updateTrending.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should update the trending score of the relevant articles 1`] = ` 4 | Array [ 5 | Post { 6 | "id": "p1", 7 | "trending": 100, 8 | }, 9 | Post { 10 | "id": "p2", 11 | "trending": null, 12 | }, 13 | Post { 14 | "id": "p3", 15 | "trending": 200, 16 | }, 17 | Post { 18 | "id": "p4", 19 | "trending": null, 20 | }, 21 | Post { 22 | "id": "p5", 23 | "trending": null, 24 | }, 25 | Post { 26 | "id": "p6", 27 | "trending": null, 28 | }, 29 | Post { 30 | "id": "p7", 31 | "trending": null, 32 | }, 33 | Post { 34 | "id": "squadP1", 35 | "trending": null, 36 | }, 37 | Post { 38 | "id": "yt2", 39 | "trending": null, 40 | }, 41 | ] 42 | `; 43 | -------------------------------------------------------------------------------- /__tests__/cron/updateHighlightedViews.ts: -------------------------------------------------------------------------------- 1 | import { crons } from '../../src/cron/index'; 2 | import cron from '../../src/cron/updateHighlightedViews'; 3 | 4 | describe('updateHighlightedViews cron', () => { 5 | it('should be registered', () => { 6 | const registeredWorker = crons.find((item) => item.name === cron.name); 7 | 8 | expect(registeredWorker).toBeDefined(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /__tests__/fixture/happy_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydotdev/daily-api/5707d3333e59f1b45ca0ba9fdf84e00303ebdb52/__tests__/fixture/happy_card.png -------------------------------------------------------------------------------- /__tests__/fixture/index.ts: -------------------------------------------------------------------------------- 1 | export * from './marketingCta'; 2 | export * from './source'; 3 | export * from './user'; 4 | -------------------------------------------------------------------------------- /__tests__/fixture/notifications.ts: -------------------------------------------------------------------------------- 1 | import { DeepPartial } from 'typeorm'; 2 | import { NotificationType } from '../../src/notifications/common'; 3 | import { NotificationV2 } from '../../src/entity'; 4 | 5 | const now = new Date(2021, 4, 2); 6 | 7 | export const notificationV2Fixture: DeepPartial = { 8 | createdAt: now, 9 | icon: 'icon', 10 | title: 'notification #1', 11 | description: 'description', 12 | targetUrl: 'https://daily.dev', 13 | type: NotificationType.CommentMention, 14 | public: true, 15 | }; 16 | -------------------------------------------------------------------------------- /__tests__/fixture/testCA.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydotdev/daily-api/5707d3333e59f1b45ca0ba9fdf84e00303ebdb52/__tests__/fixture/testCA.der -------------------------------------------------------------------------------- /__tests__/teardown.ts: -------------------------------------------------------------------------------- 1 | import '../src/config'; 2 | import createOrGetConnection from '../src/db'; 3 | import { ioRedisPool, redisPubSub, singleRedisClient } from '../src/redis'; 4 | 5 | async function teardown() { 6 | const con = await createOrGetConnection(); 7 | await con.destroy(); 8 | singleRedisClient.disconnect(); 9 | redisPubSub.getPublisher().disconnect(); 10 | redisPubSub.getSubscriber().disconnect(); 11 | await redisPubSub.close(); 12 | await ioRedisPool.end(); 13 | } 14 | 15 | module.exports = teardown; 16 | -------------------------------------------------------------------------------- /__tests__/workers/bannerDeleted.ts: -------------------------------------------------------------------------------- 1 | import { expectSuccessfulBackground } from '../helpers'; 2 | import worker from '../../src/workers/bannerDeleted'; 3 | import { getRedisObject } from '../../src/redis'; 4 | import { REDIS_BANNER_KEY } from '../../src/config'; 5 | 6 | beforeEach(async () => { 7 | jest.resetAllMocks(); 8 | }); 9 | 10 | it('should update redis cache', async () => { 11 | const postDateTs = Date.now(); 12 | 13 | await expectSuccessfulBackground(worker, { 14 | banner: { 15 | timestamp: postDateTs * 1000, // createdAt comes as μs from messageToJson, 16 | title: 'test', 17 | subtitle: 'test', 18 | cta: 'test', 19 | url: 'test', 20 | theme: 'cabbage', 21 | }, 22 | }); 23 | expect(await getRedisObject(REDIS_BANNER_KEY)).toEqual('false'); 24 | }); 25 | -------------------------------------------------------------------------------- /bin/common.ts: -------------------------------------------------------------------------------- 1 | import fastq from 'fastq'; 2 | import { ReadStream } from 'fs'; 3 | 4 | const QUEUE_CONCURRENCY = 1; 5 | 6 | export const runInQueueStream = async ( 7 | stream: ReadStream, 8 | callback: (props: T) => Promise, 9 | queue = QUEUE_CONCURRENCY, 10 | ) => { 11 | let insertCount = 0; 12 | const insertQueue = fastq.promise(async (props: T) => { 13 | await callback(props); 14 | 15 | insertCount += 1; 16 | }, queue); 17 | 18 | stream.on('data', insertQueue.push); 19 | 20 | await new Promise((resolve, reject) => { 21 | stream.on('error', reject); 22 | stream.on('end', () => resolve(true)); 23 | }); 24 | await insertQueue.drained(); 25 | console.log('update finished with a total of: ', insertCount); 26 | }; 27 | -------------------------------------------------------------------------------- /bin/export.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs'; 2 | import createOrGetConnection from '../src/db'; 3 | 4 | const start = async (): Promise => { 5 | const con = await createOrGetConnection(); 6 | for (const entity of con.entityMetadatas) { 7 | console.log(`exporting ${entity.name}`); 8 | const repository = await con.getRepository(entity.name); 9 | const entities = await repository.find(); 10 | writeFileSync(`./seeds/${entity.name}.json`, JSON.stringify(entities)); 11 | } 12 | }; 13 | 14 | start() 15 | .then(() => { 16 | console.log('done'); 17 | process.exit(); 18 | }) 19 | .catch((err) => { 20 | console.error(err); 21 | process.exit(-1); 22 | }); 23 | -------------------------------------------------------------------------------- /bin/runWorkflow.ts: -------------------------------------------------------------------------------- 1 | import { runReminderWorkflow } from '../src/temporal/notifications/utils'; 2 | 3 | const afterFiveSeconds = () => Date.now() + 5000; 4 | const userId = 'B4AdaAXLKy1SdZxDhZwL1'; 5 | const postId = 's-UJPyk4i'; 6 | const params = { 7 | userId, 8 | postId, 9 | remindAt: afterFiveSeconds(), 10 | }; 11 | 12 | runReminderWorkflow(params) 13 | .then(() => { 14 | console.log('Workflow started'); 15 | }) 16 | .catch((err) => { 17 | console.log('Workflow failed:', err); 18 | }) 19 | .finally(() => { 20 | process.exit(1); 21 | }); 22 | -------------------------------------------------------------------------------- /geoip/.gitignore: -------------------------------------------------------------------------------- 1 | *.mmdb 2 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": "ts,json", 3 | "exec": "ts-node" 4 | } 5 | -------------------------------------------------------------------------------- /pg-init-scripts/create-databases.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | function create_user_and_database() { 7 | local database=$1 8 | echo " Creating user and database '$database'" 9 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 10 | CREATE USER $database; 11 | CREATE DATABASE $database; 12 | GRANT ALL PRIVILEGES ON DATABASE $database TO $database; 13 | EOSQL 14 | } 15 | 16 | if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then 17 | echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES" 18 | for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do 19 | create_user_and_database $db 20 | done 21 | echo "Multiple databases created" 22 | fi 23 | -------------------------------------------------------------------------------- /src/common/base64.ts: -------------------------------------------------------------------------------- 1 | export type Base64String = string; 2 | 3 | export function base64(i: string): Base64String { 4 | return Buffer.from(i, 'utf8').toString('base64'); 5 | } 6 | 7 | export function unbase64(i: Base64String): string { 8 | return Buffer.from(i, 'base64').toString('utf8'); 9 | } 10 | -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ONE_MINUTE_IN_SECONDS = 60; 2 | export const ONE_HOUR_IN_SECONDS = 60 * 60; 3 | export const ONE_DAY_IN_SECONDS = ONE_HOUR_IN_SECONDS * 24; 4 | export const ONE_WEEK_IN_SECONDS = ONE_DAY_IN_SECONDS * 7; 5 | export const ONE_MONTH_IN_SECONDS = ONE_DAY_IN_SECONDS * 30; 6 | export const ONE_YEAR_IN_SECONDS = ONE_DAY_IN_SECONDS * 365; 7 | 8 | export const THREE_MONTHS_IN_SECONDS = ONE_MONTH_IN_SECONDS * 3; 9 | 10 | export const MAX_FOLLOWERS_LIMIT = 5_000; 11 | 12 | export const SUCCESSFUL_CIO_SYNC_DATE = 'successful_cio_sync_date'; 13 | 14 | export const customFeedsPlusDate = new Date('2024-12-11'); 15 | 16 | export const coresBalanceExpirationSeconds = 60 * ONE_MINUTE_IN_SECONDS; 17 | -------------------------------------------------------------------------------- /src/common/experiment.ts: -------------------------------------------------------------------------------- 1 | import { ExperimentVariant, type ConnectionManager } from '../entity'; 2 | 3 | export const getExperimentVariant = ( 4 | con: ConnectionManager, 5 | feature: string, 6 | variant: string, 7 | ) => 8 | con.getRepository(ExperimentVariant).findOne({ where: { feature, variant } }); 9 | -------------------------------------------------------------------------------- /src/common/fibonacci.ts: -------------------------------------------------------------------------------- 1 | export const isPerfectSquare = (n: number) => { 2 | return Number.isInteger(Math.sqrt(n)); 3 | }; 4 | 5 | // Number is Fibonacci if (5*n^2 + 4) or (5*n^2 - 4) is a perfect square 6 | export const isFibonacci = (num: number) => { 7 | const x = 5 * Math.pow(num, 2); 8 | return isPerfectSquare(x + 4) || isPerfectSquare(x - 4); 9 | }; 10 | -------------------------------------------------------------------------------- /src/common/flags.ts: -------------------------------------------------------------------------------- 1 | import type { Settings } from '../entity'; 2 | 3 | export const transformSettingFlags = ({ flags }: Pick) => { 4 | return { 5 | sidebarSquadExpanded: flags?.sidebarSquadExpanded ?? true, 6 | sidebarCustomFeedsExpanded: flags?.sidebarCustomFeedsExpanded ?? true, 7 | sidebarOtherExpanded: flags?.sidebarOtherExpanded ?? true, 8 | sidebarResourcesExpanded: flags?.sidebarResourcesExpanded ?? true, 9 | sidebarBookmarksExpanded: flags?.sidebarBookmarksExpanded ?? true, 10 | clickbaitShieldEnabled: flags?.clickbaitShieldEnabled ?? true, 11 | ...flags, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/common/healthCheck.ts: -------------------------------------------------------------------------------- 1 | import fastJson from 'fast-json-stringify'; 2 | 3 | export const stringifyHealthCheck = fastJson({ 4 | type: 'object', 5 | properties: { 6 | status: { 7 | type: 'string', 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base64'; 2 | export * from './cloudinary'; 3 | export * from './crypto'; 4 | export * from './date'; 5 | export * from './feedGenerator'; 6 | export * from './healthCheck'; 7 | export * from './pagination'; 8 | export * from './pubsub'; 9 | export * from './reputation'; 10 | export * from './slack'; 11 | export * from './twitter'; 12 | export * from './users'; 13 | export * from './mailing'; 14 | export * from './post'; 15 | export * from './links'; 16 | export * from './utils'; 17 | export * from './typedPubsub'; 18 | export * from './personalizedDigest'; 19 | export * from './vote'; 20 | export * from './feed'; 21 | export * from './constants'; 22 | export * from './userIntegration'; 23 | -------------------------------------------------------------------------------- /src/common/plus/index.ts: -------------------------------------------------------------------------------- 1 | import type { DataSource } from 'typeorm'; 2 | import { ContentPreferenceOrganization } from '../../entity/contentPreference/ContentPreferenceOrganization'; 3 | 4 | export * from './subscription'; 5 | 6 | export const isUserPartOfOrganization = async ( 7 | con: DataSource, 8 | userId: string, 9 | ): Promise => 10 | con.getRepository(ContentPreferenceOrganization).existsBy({ 11 | userId, 12 | }); 13 | -------------------------------------------------------------------------------- /src/common/plus/subscription.ts: -------------------------------------------------------------------------------- 1 | export enum SubscriptionProvider { 2 | Paddle = 'paddle', 3 | AppleStoreKit = 'storekit', 4 | } 5 | 6 | export enum PurchaseType { 7 | Cores = 'cores', 8 | Plus = 'plus', 9 | Organization = 'organization', 10 | } 11 | 12 | export enum PlusPlanType { 13 | Organization = 'organization', 14 | Personal = 'personal', 15 | } 16 | 17 | export enum SubscriptionStatus { 18 | Active = 'active', 19 | Expired = 'expired', 20 | Cancelled = 'cancelled', 21 | } 22 | -------------------------------------------------------------------------------- /src/common/queryDataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, QueryRunner, ReplicationMode } from 'typeorm'; 2 | 3 | export const queryDataSource = async ( 4 | con: DataSource, 5 | callback: ({ queryRunner }: { queryRunner: QueryRunner }) => Promise, 6 | options?: Partial<{ 7 | mode: ReplicationMode; 8 | }>, 9 | ): Promise => { 10 | const queryRunner = con.createQueryRunner(options?.mode || 'master'); 11 | 12 | let result: T; 13 | 14 | try { 15 | result = await callback({ queryRunner }); 16 | } catch (error) { 17 | throw error; 18 | } finally { 19 | await queryRunner.release(); 20 | } 21 | 22 | return result; 23 | }; 24 | -------------------------------------------------------------------------------- /src/common/queryReadReplica.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, QueryRunner } from 'typeorm'; 2 | import { queryDataSource } from './queryDataSource'; 3 | 4 | export const queryReadReplica = async ( 5 | con: DataSource, 6 | callback: ({ queryRunner }: { queryRunner: QueryRunner }) => Promise, 7 | ): Promise => { 8 | return queryDataSource(con, callback, { 9 | mode: 'slave', 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/common/search.ts: -------------------------------------------------------------------------------- 1 | import { getLimit } from './pagination'; 2 | 3 | export type SearchSuggestionArgs = { 4 | query: string; 5 | version: number; 6 | limit?: number; 7 | includeContentPreference?: boolean; 8 | feedId?: string; 9 | }; 10 | 11 | export const defaultSearchLimit = 3; 12 | export const maxSearchLimit = 100; 13 | 14 | export const getSearchLimit = ({ 15 | limit, 16 | }: Pick) => { 17 | return getLimit({ 18 | limit: limit ?? defaultSearchLimit, 19 | defaultLimit: defaultSearchLimit, 20 | max: maxSearchLimit, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/common/storekit.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | export const generateAppAccountToken = (): string => { 4 | return uuidv4(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/twitter.ts: -------------------------------------------------------------------------------- 1 | import { Post } from '../entity'; 2 | 3 | export const truncateToTweet = (text?: string): string => { 4 | if (!text) return ''; 5 | 6 | return text.length <= 130 ? text : `${text.substring(0, 127)}...`; 7 | }; 8 | 9 | export const truncatePostToTweet = ( 10 | post: Pick | undefined, 11 | ): string => { 12 | if (!post || !post.title?.length) return ''; 13 | 14 | return truncateToTweet(post.title); 15 | }; 16 | -------------------------------------------------------------------------------- /src/compatibility/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance } from 'fastify'; 2 | 3 | import publications from './publications'; 4 | 5 | export default async function (fastify: FastifyInstance): Promise { 6 | fastify.register(publications, { prefix: '/publications' }); 7 | } 8 | -------------------------------------------------------------------------------- /src/cron.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { PubSub } from '@google-cloud/pubsub'; 3 | 4 | import './config'; 5 | 6 | import { crons } from './cron/index'; 7 | import createOrGetConnection from './db'; 8 | import { logger } from './logger'; 9 | 10 | export default async function app(cronName: string): Promise { 11 | const connection = await createOrGetConnection(); 12 | const pubsub = new PubSub(); 13 | 14 | const selectedCron = crons.find((cron) => cron.name === cronName); 15 | if (selectedCron) { 16 | logger.info({ cron: cronName }, 'running cron'); 17 | await selectedCron.handler(connection, logger, pubsub); 18 | } else { 19 | logger.warn({ cron: cronName }, 'no such cron'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cron/checkAnalyticsReport.ts: -------------------------------------------------------------------------------- 1 | import { updateFlagsStatement } from '../common'; 2 | import { Post } from '../entity'; 3 | import { Cron } from './cron'; 4 | 5 | const cron: Cron = { 6 | name: 'check-analytics-report', 7 | handler: async (con) => { 8 | await con 9 | .createQueryBuilder() 10 | .update(Post) 11 | .set({ 12 | sentAnalyticsReport: true, 13 | flags: updateFlagsStatement({ sentAnalyticsReport: true }), 14 | }) 15 | .where(`"createdAt" <= now() - interval '20 hour'`) 16 | .andWhere('"sentAnalyticsReport" = false') 17 | .execute(); 18 | }, 19 | }; 20 | 21 | export default cron; 22 | -------------------------------------------------------------------------------- /src/cron/cleanStaleUserTransactions.ts: -------------------------------------------------------------------------------- 1 | import { LessThan } from 'typeorm'; 2 | import { Cron } from './cron'; 3 | import { sub } from 'date-fns'; 4 | import { 5 | UserTransaction, 6 | UserTransactionStatus, 7 | } from '../entity/user/UserTransaction'; 8 | 9 | export const cleanStaleUserTransactions: Cron = { 10 | name: 'clean-stale-user-transactions', 11 | handler: async (con, logger) => { 12 | const cleanDate = sub(new Date(), { days: 21 }); 13 | 14 | const result = await con.getRepository(UserTransaction).delete({ 15 | status: UserTransactionStatus.Created, 16 | updatedAt: LessThan(cleanDate), 17 | }); 18 | 19 | logger.info( 20 | { 21 | count: result.affected, 22 | cleanDate, 23 | }, 24 | 'cleaned stale user transactions', 25 | ); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/cron/cleanZombieImages.ts: -------------------------------------------------------------------------------- 1 | import { Cron } from './cron'; 2 | import { IsNull, LessThan } from 'typeorm'; 3 | import { subDays } from 'date-fns'; 4 | import { ContentImage } from '../entity'; 5 | 6 | const cron: Cron = { 7 | name: 'clean-zombie-images', 8 | handler: async (con, logger) => { 9 | logger.info('cleaning zombie images...'); 10 | const timeThreshold = subDays(new Date(), 30); 11 | const { affected } = await con.getRepository(ContentImage).delete({ 12 | createdAt: LessThan(timeThreshold), 13 | usedByType: IsNull(), 14 | }); 15 | logger.info({ count: affected }, 'zombies images cleaned! 🧟'); 16 | }, 17 | }; 18 | 19 | export default cron; 20 | -------------------------------------------------------------------------------- /src/cron/cleanZombieUserCompany.ts: -------------------------------------------------------------------------------- 1 | import { Cron } from './cron'; 2 | import { LessThan } from 'typeorm'; 3 | import { subHours } from 'date-fns'; 4 | import { UserCompany } from '../entity/UserCompany'; 5 | 6 | export const cleanZombieUserCompany: Cron = { 7 | name: 'clean-zombie-user-companies', 8 | handler: async (con, logger) => { 9 | logger.info('cleaning zombie user companies...'); 10 | const timeThreshold = subHours(new Date(), 1); 11 | const { affected } = await con.getRepository(UserCompany).delete({ 12 | verified: false, 13 | updatedAt: LessThan(timeThreshold), 14 | }); 15 | logger.info({ count: affected }, 'zombies user companies cleaned! 🧟'); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/cron/cron.ts: -------------------------------------------------------------------------------- 1 | import { FastifyLoggerInstance } from 'fastify'; 2 | import { PubSub } from '@google-cloud/pubsub'; 3 | import { DataSource } from 'typeorm'; 4 | 5 | export interface Cron { 6 | name: string; 7 | handler: ( 8 | con: DataSource, 9 | logger: FastifyLoggerInstance, 10 | pubsub: PubSub, 11 | ) => Promise; 12 | } 13 | -------------------------------------------------------------------------------- /src/cron/updateSourceTagView.ts: -------------------------------------------------------------------------------- 1 | import { Cron } from './cron'; 2 | import { SourceTagView } from '../entity/SourceTagView'; 3 | 4 | const cron: Cron = { 5 | name: 'update-source-tag-view', 6 | handler: async (con, logger) => { 7 | const materializedViewName = 8 | con.getRepository(SourceTagView).metadata.tableName; 9 | 10 | try { 11 | await con.query(`REFRESH MATERIALIZED VIEW ${materializedViewName}`); 12 | 13 | logger.info({}, 'source tag view updated'); 14 | } catch (err) { 15 | logger.error({ err }, 'failed to update source tag view'); 16 | } 17 | }, 18 | }; 19 | 20 | export default cron; 21 | -------------------------------------------------------------------------------- /src/cron/updateTagRecommendations.ts: -------------------------------------------------------------------------------- 1 | import { Cron } from './cron'; 2 | import { TagRecommendation } from '../entity/TagRecommendation'; 3 | 4 | const cron: Cron = { 5 | name: 'update-tag-recommendations', 6 | handler: async (con, logger) => { 7 | const materializedViewName = 8 | con.getRepository(TagRecommendation).metadata.tableName; 9 | 10 | try { 11 | await con.query(`REFRESH MATERIALIZED VIEW ${materializedViewName}`); 12 | 13 | logger.info({}, 'tag recommendations updated'); 14 | } catch (err) { 15 | logger.error({ err }, 'failed to update tag recommendations'); 16 | } 17 | }, 18 | }; 19 | 20 | export default cron; 21 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { AppDataSource } from './data-source'; 2 | 3 | const createOrGetConnection = async () => { 4 | if (!AppDataSource.isInitialized) { 5 | await AppDataSource.initialize(); 6 | } 7 | return AppDataSource; 8 | }; 9 | 10 | export default createOrGetConnection; 11 | -------------------------------------------------------------------------------- /src/entity/ActiveView.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { Post } from './posts'; 3 | import { View } from './View'; 4 | 5 | @ViewEntity({ 6 | expression: (connection: DataSource) => 7 | connection 8 | .createQueryBuilder() 9 | .select('view.*') 10 | .from(View, 'view') 11 | .leftJoin(Post, 'post', 'post.id = view.postId') 12 | .where('post.deleted = false'), 13 | }) 14 | export class ActiveView { 15 | @ViewColumn() 16 | userId: string; 17 | 18 | @ViewColumn() 19 | postId: string; 20 | 21 | @ViewColumn() 22 | timestamp: Date; 23 | 24 | @ViewColumn() 25 | hidden: boolean; 26 | } 27 | -------------------------------------------------------------------------------- /src/entity/AdvancedSettings.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { PostType } from './posts'; 3 | import { Source } from './Source'; 4 | 5 | @Entity() 6 | export class AdvancedSettings { 7 | @PrimaryGeneratedColumn('increment') 8 | id: number; 9 | 10 | @Column({ type: 'text' }) 11 | title: string; 12 | 13 | @Column({ type: 'text' }) 14 | description: string; 15 | 16 | @Column({ type: 'text', default: 'advanced' }) 17 | group: string; 18 | 19 | @Column({ type: 'bool', default: true }) 20 | defaultEnabledState: boolean; 21 | 22 | @Column({ type: 'jsonb', default: {} }) 23 | options: { 24 | source?: Pick; 25 | type?: PostType | string; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/entity/Banner.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Banner { 5 | @PrimaryColumn({ default: () => 'now()' }) 6 | timestamp: Date; 7 | 8 | @Column({ type: 'text' }) 9 | title: string; 10 | 11 | @Column({ type: 'text' }) 12 | subtitle: string; 13 | 14 | @Column({ type: 'text' }) 15 | cta: string; 16 | 17 | @Column({ type: 'text' }) 18 | url: string; 19 | 20 | @Column({ type: 'text' }) 21 | theme: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/Category.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Category { 5 | @PrimaryColumn({ type: 'text' }) 6 | id: string; 7 | 8 | @Column({ type: 'text' }) 9 | emoji: string; 10 | 11 | @Column({ type: 'text' }) 12 | title: string; 13 | 14 | @Column({ default: () => 'now()' }) 15 | createdAt: Date; 16 | 17 | @UpdateDateColumn() 18 | updatedAt: Date; 19 | 20 | @Column({ type: 'text', array: true, default: [] }) 21 | tags: string[]; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/Checkpoint.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Checkpoint { 5 | @PrimaryColumn({ type: 'text' }) 6 | key: string; 7 | 8 | @Column() 9 | timestamp: Date; 10 | } 11 | -------------------------------------------------------------------------------- /src/entity/CommentMention.ts: -------------------------------------------------------------------------------- 1 | import { Entity, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from './user'; 3 | import type { Comment } from './Comment'; 4 | 5 | @Entity() 6 | export class CommentMention { 7 | @PrimaryColumn({ length: 14 }) 8 | commentId: string; 9 | 10 | @PrimaryColumn({ length: 36 }) 11 | commentByUserId: string; 12 | 13 | @PrimaryColumn({ length: 36 }) 14 | mentionedUserId: string; 15 | 16 | @ManyToOne('Comment', { 17 | lazy: true, 18 | onDelete: 'CASCADE', 19 | }) 20 | comment?: Promise; 21 | 22 | @ManyToOne('User', { 23 | lazy: true, 24 | onDelete: 'CASCADE', 25 | }) 26 | commentByUser?: Promise; 27 | 28 | @ManyToOne('User', { 29 | lazy: true, 30 | onDelete: 'CASCADE', 31 | }) 32 | mentionedUser?: Promise; 33 | } 34 | -------------------------------------------------------------------------------- /src/entity/CommentReport.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from './user'; 3 | import { ReportReason } from './common'; 4 | 5 | @Entity() 6 | export class CommentReport { 7 | @PrimaryColumn({ type: 'text' }) 8 | commentId: string; 9 | 10 | @PrimaryColumn({ length: 36 }) 11 | @Index('IDX_comment_report_user_id') 12 | userId: string; 13 | 14 | @Column({ default: () => 'now()' }) 15 | createdAt: Date; 16 | 17 | @Column({ length: 36, type: 'varchar' }) 18 | reason: ReportReason; 19 | 20 | @Column({ type: 'text', nullable: true }) 21 | note: string; 22 | 23 | @ManyToOne('User', { 24 | lazy: true, 25 | onDelete: 'CASCADE', 26 | }) 27 | user: Promise; 28 | } 29 | -------------------------------------------------------------------------------- /src/entity/Company.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | PrimaryColumn, 6 | UpdateDateColumn, 7 | } from 'typeorm'; 8 | 9 | @Entity() 10 | export class Company { 11 | @PrimaryColumn({ type: 'text' }) 12 | id: string; 13 | 14 | @Column({ type: 'text' }) 15 | name: string; 16 | 17 | @CreateDateColumn() 18 | createdAt: Date; 19 | 20 | @UpdateDateColumn() 21 | updatedAt: Date; 22 | 23 | @Column({ type: 'text' }) 24 | image: string; 25 | 26 | @Column({ type: 'text', array: true, default: [] }) 27 | domains: string[]; 28 | } 29 | -------------------------------------------------------------------------------- /src/entity/ContentImage.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, PrimaryColumn } from 'typeorm'; 2 | 3 | export enum ContentImageUsedByType { 4 | Post = 'post', 5 | Comment = 'comment', 6 | User = 'user', 7 | } 8 | 9 | @Entity() 10 | @Index('IDX_content_image_used_by', ['usedByType', 'usedById']) 11 | export class ContentImage { 12 | @PrimaryColumn({ type: 'text' }) 13 | url: string; 14 | 15 | @Column({ type: 'text' }) 16 | serviceId: string; 17 | 18 | @Column({ default: () => 'now()' }) 19 | createdAt: Date; 20 | 21 | @Column({ type: 'text', nullable: true }) 22 | usedByType: ContentImageUsedByType | null; 23 | 24 | @Column({ type: 'text', nullable: true }) 25 | usedById: string | null; 26 | } 27 | -------------------------------------------------------------------------------- /src/entity/DisallowHandle.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, Entity, EntityManager, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class DisallowHandle { 5 | @PrimaryColumn({ unique: true }) 6 | value: string; 7 | } 8 | 9 | export const checkDisallowHandle = async ( 10 | entityManager: EntityManager | DataSource, 11 | value: string, 12 | ): Promise => { 13 | const handle = await entityManager 14 | .getRepository(DisallowHandle) 15 | .createQueryBuilder() 16 | .select('value') 17 | .where('value = :value', { 18 | value: value?.toLowerCase(), 19 | }) 20 | .getRawOne(); 21 | return !!handle; 22 | }; 23 | -------------------------------------------------------------------------------- /src/entity/ExperimentVariant.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | export enum ExperimentVariantType { 4 | ProductPricing = 'productPricing', 5 | } 6 | 7 | @Entity() 8 | export class ExperimentVariant { 9 | @PrimaryColumn({ type: 'text' }) 10 | feature: string; 11 | 12 | @PrimaryColumn({ type: 'text' }) 13 | variant: string; 14 | 15 | @Column({ default: () => 'now()' }) 16 | createdAt: Date; 17 | 18 | @Column({ type: 'text', default: null }) 19 | value: string; 20 | 21 | @Column({ type: 'text' }) 22 | type: ExperimentVariantType; 23 | } 24 | -------------------------------------------------------------------------------- /src/entity/FeedAdvancedSettings.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { AdvancedSettings } from './AdvancedSettings'; 3 | import type { Feed } from './Feed'; 4 | 5 | @Entity() 6 | export class FeedAdvancedSettings { 7 | @PrimaryColumn({ type: 'text' }) 8 | @Index('IDX_feed_advanced_settings_feedId') 9 | feedId: string; 10 | 11 | @PrimaryColumn({ type: 'int' }) 12 | advancedSettingsId: number; 13 | 14 | @Column({ type: 'bool', default: true }) 15 | enabled: boolean; 16 | 17 | @ManyToOne('Feed', { 18 | lazy: true, 19 | onDelete: 'CASCADE', 20 | }) 21 | feed: Promise; 22 | 23 | @ManyToOne('AdvancedSettings', { 24 | lazy: true, 25 | onDelete: 'CASCADE', 26 | }) 27 | advancedSettings: Promise; 28 | } 29 | -------------------------------------------------------------------------------- /src/entity/FeedSource.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { Feed } from './Feed'; 3 | import type { Source } from './Source'; 4 | 5 | @Entity() 6 | export class FeedSource { 7 | @PrimaryColumn({ type: 'text' }) 8 | @Index() 9 | feedId: string; 10 | 11 | @PrimaryColumn({ type: 'text' }) 12 | @Index() 13 | sourceId: string; 14 | 15 | @Column({ default: true }) 16 | blocked: boolean; 17 | 18 | @ManyToOne('Feed', { 19 | lazy: true, 20 | onDelete: 'CASCADE', 21 | }) 22 | feed: Promise; 23 | 24 | @ManyToOne('Source', { 25 | lazy: true, 26 | onDelete: 'CASCADE', 27 | }) 28 | source: Promise; 29 | } 30 | -------------------------------------------------------------------------------- /src/entity/FeedTag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | Index, 6 | ManyToOne, 7 | PrimaryColumn, 8 | UpdateDateColumn, 9 | } from 'typeorm'; 10 | import type { Feed } from './Feed'; 11 | 12 | @Entity() 13 | @Index('IDX_feed_id_blocked', ['feedId', 'blocked']) 14 | export class FeedTag { 15 | @PrimaryColumn({ type: 'text' }) 16 | feedId: string; 17 | 18 | @PrimaryColumn({ type: 'text' }) 19 | tag: string; 20 | 21 | @Column({ default: false }) 22 | blocked: boolean; 23 | 24 | @CreateDateColumn() 25 | createdAt: Date; 26 | 27 | @Index() 28 | @UpdateDateColumn() 29 | updatedAt: Date; 30 | 31 | @ManyToOne('Feed', { 32 | lazy: true, 33 | onDelete: 'CASCADE', 34 | }) 35 | feed: Promise; 36 | } 37 | -------------------------------------------------------------------------------- /src/entity/PopularSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { PopularPost } from './PopularPost'; 3 | 4 | @ViewEntity({ 5 | materialized: true, 6 | expression: (dataSource: DataSource) => 7 | dataSource 8 | .createQueryBuilder() 9 | .select('"sourceId"') 10 | .addSelect('avg(r) r') 11 | .from(PopularPost, 'base') 12 | .groupBy('"sourceId"') 13 | .having('count(*) > 5') 14 | .orderBy('r', 'DESC'), 15 | }) 16 | export class PopularSource { 17 | @ViewColumn() 18 | sourceId: string; 19 | 20 | @ViewColumn() 21 | r: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/PopularTag.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { PopularPost } from './PopularPost'; 3 | 4 | @ViewEntity({ 5 | materialized: true, 6 | expression: (dataSource: DataSource) => 7 | dataSource 8 | .createQueryBuilder() 9 | .select('unnest(string_to_array("tagsStr", \',\')) tag') 10 | .addSelect('avg(r) r') 11 | .from(PopularPost, 'base') 12 | .groupBy('tag') 13 | .having('count(*) > 10') 14 | .orderBy('r', 'DESC'), 15 | }) 16 | export class PopularTag { 17 | @ViewColumn() 18 | tag: string; 19 | 20 | @ViewColumn() 21 | r: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/PopularVideoSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { PopularVideoPost } from './PopularVideoPost'; 3 | 4 | @ViewEntity({ 5 | materialized: true, 6 | expression: (dataSource: DataSource) => 7 | dataSource 8 | .createQueryBuilder() 9 | .select('"sourceId"') 10 | .addSelect('avg(r) r') 11 | .addSelect('count(*) posts') 12 | .from(PopularVideoPost, 'base') 13 | .groupBy('"sourceId"') 14 | .having('count(*) > 5') 15 | .orderBy('r', 'DESC'), 16 | }) 17 | export class PopularVideoSource { 18 | @ViewColumn() 19 | sourceId: string; 20 | 21 | @ViewColumn() 22 | r: number; 23 | 24 | @ViewColumn() 25 | posts: number; 26 | } 27 | -------------------------------------------------------------------------------- /src/entity/PostTag.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { Post } from './posts'; 3 | 4 | @Entity() 5 | export class PostTag { 6 | @PrimaryColumn({ type: 'text' }) 7 | @Index() 8 | postId: string; 9 | 10 | @PrimaryColumn({ type: 'text' }) 11 | tag: string; 12 | 13 | @ManyToOne('Post', (post: Post) => post.tags, { 14 | lazy: true, 15 | onDelete: 'CASCADE', 16 | }) 17 | post: Promise; 18 | } 19 | -------------------------------------------------------------------------------- /src/entity/SourceFeed.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { Source } from './Source'; 3 | 4 | @Entity() 5 | @Index(['sourceId', 'feed'], { unique: true }) 6 | export class SourceFeed { 7 | @Column() 8 | sourceId: string; 9 | 10 | @ManyToOne('Source', (source: Source) => source.feeds, { 11 | lazy: true, 12 | onDelete: 'CASCADE', 13 | }) 14 | source: Promise; 15 | 16 | @PrimaryColumn({ type: 'text' }) 17 | feed: string; 18 | 19 | @Column({ nullable: true }) 20 | lastFetched: Date; 21 | } 22 | -------------------------------------------------------------------------------- /src/entity/TagSegment.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class TagSegment { 5 | @PrimaryColumn({ type: 'text' }) 6 | tag: string; 7 | 8 | @Column({ type: 'text' }) 9 | @Index() 10 | segment: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/entity/TrendingSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { TrendingPost } from './TrendingPost'; 3 | 4 | @ViewEntity({ 5 | materialized: true, 6 | expression: (dataSource: DataSource) => 7 | dataSource 8 | .createQueryBuilder() 9 | .select('"sourceId"') 10 | .addSelect('avg(r) r') 11 | .from(TrendingPost, 'base') 12 | .groupBy('"sourceId"') 13 | .having('count(*) > 1') 14 | .orderBy('r', 'DESC'), 15 | }) 16 | export class TrendingSource { 17 | @ViewColumn() 18 | sourceId: string; 19 | 20 | @ViewColumn() 21 | r: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/TrendingTag.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, ViewColumn, ViewEntity } from 'typeorm'; 2 | import { TrendingPost } from './TrendingPost'; 3 | 4 | @ViewEntity({ 5 | materialized: true, 6 | expression: (dataSource: DataSource) => 7 | dataSource 8 | .createQueryBuilder() 9 | .select('unnest(string_to_array("tagsStr", \',\')) tag') 10 | .addSelect('avg(r) r') 11 | .from(TrendingPost, 'base') 12 | .groupBy('tag') 13 | .having('count(*) > 1') 14 | .orderBy('r', 'DESC'), 15 | }) 16 | export class TrendingTag { 17 | @ViewColumn() 18 | tag: string; 19 | 20 | @ViewColumn() 21 | r: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/View.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { Post } from './posts'; 3 | 4 | @Entity() 5 | @Index(['userId', 'timestamp']) 6 | @Index(['postId', 'userId']) 7 | @Index(['postId', 'userId', 'hidden']) 8 | export class View { 9 | @PrimaryColumn({ type: 'text' }) 10 | @Index() 11 | postId: string; 12 | 13 | @ManyToOne('Post', { 14 | lazy: true, 15 | onDelete: 'CASCADE', 16 | }) 17 | post: Promise; 18 | 19 | @PrimaryColumn({ length: 36, type: 'varchar' }) 20 | @Index() 21 | userId: string; 22 | 23 | @Column({ type: 'text', nullable: true }) 24 | referer?: string; 25 | 26 | @PrimaryColumn({ default: () => 'now()' }) 27 | @Index() 28 | timestamp: Date; 29 | 30 | @Column({ type: 'bool', default: false }) 31 | hidden?: boolean; 32 | } 33 | -------------------------------------------------------------------------------- /src/entity/contentPreference/ContentPreferenceKeyword.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, JoinColumn, ManyToOne } from 'typeorm'; 2 | import { ContentPreference } from './ContentPreference'; 3 | import { ContentPreferenceType } from './types'; 4 | import type { Keyword } from '../Keyword'; 5 | 6 | @ChildEntity(ContentPreferenceType.Keyword) 7 | export class ContentPreferenceKeyword extends ContentPreference { 8 | @Column({ type: 'text', default: null }) 9 | keywordId: string; 10 | 11 | @ManyToOne('Keyword', { lazy: true, onDelete: 'CASCADE' }) 12 | @JoinColumn({ name: 'keywordId' }) 13 | keyword: Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/entity/contentPreference/ContentPreferenceUser.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, Index, JoinColumn, ManyToOne } from 'typeorm'; 2 | import { ContentPreference } from './ContentPreference'; 3 | import { ContentPreferenceType } from './types'; 4 | import type { User } from '../user/User'; 5 | 6 | @ChildEntity(ContentPreferenceType.User) 7 | export class ContentPreferenceUser extends ContentPreference { 8 | @Column({ type: 'text', default: null }) 9 | @Index('IDX_content_preference_reference_user_id') 10 | referenceUserId: string; 11 | 12 | @Column({ type: 'text', default: null }) 13 | feedId: string; 14 | 15 | @ManyToOne('User', { lazy: true, onDelete: 'CASCADE' }) 16 | @JoinColumn({ name: 'referenceUserId' }) 17 | referenceUser: Promise; 18 | } 19 | -------------------------------------------------------------------------------- /src/entity/contentPreference/ContentPreferenceWord.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity } from 'typeorm'; 2 | import { ContentPreference } from './ContentPreference'; 3 | import { ContentPreferenceType } from './types'; 4 | 5 | @ChildEntity(ContentPreferenceType.Word) 6 | export class ContentPreferenceWord extends ContentPreference {} 7 | -------------------------------------------------------------------------------- /src/entity/contentPreference/types.ts: -------------------------------------------------------------------------------- 1 | export enum ContentPreferenceType { 2 | User = 'user', 3 | Keyword = 'keyword', 4 | Source = 'source', 5 | Word = 'word', 6 | Organization = 'organization', 7 | } 8 | 9 | export enum ContentPreferenceStatus { 10 | Follow = 'follow', 11 | Subscribed = 'subscribed', 12 | Blocked = 'blocked', 13 | } 14 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationAttachmentV2.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export enum NotificationAttachmentType { 4 | Post = 'post', 5 | Video = 'video', 6 | } 7 | 8 | @Entity() 9 | @Index('IDX_notification_attch_v2_type_reference_id', ['type', 'referenceId'], { 10 | unique: true, 11 | }) 12 | export class NotificationAttachmentV2 { 13 | @PrimaryGeneratedColumn('uuid') 14 | id: string; 15 | 16 | @Column({ type: 'text' }) 17 | type: NotificationAttachmentType; 18 | 19 | @Column({ type: 'text' }) 20 | image: string; 21 | 22 | @Column({ type: 'text' }) 23 | title: string; 24 | 25 | @Column({ type: 'text' }) 26 | referenceId: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationAvatarV2.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export type NotificationAvatarType = 4 | | 'source' 5 | | 'user' 6 | | 'top_reader_badge' 7 | | 'organization'; 8 | 9 | @Entity() 10 | @Index( 11 | 'IDX_notification_avatar_v2_type_reference_id', 12 | ['type', 'referenceId'], 13 | { unique: true }, 14 | ) 15 | export class NotificationAvatarV2 { 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @Column({ type: 'text' }) 20 | type: NotificationAvatarType; 21 | 22 | @Column({ type: 'text' }) 23 | name: string; 24 | 25 | @Column({ type: 'text' }) 26 | image: string; 27 | 28 | @Column({ type: 'text' }) 29 | targetUrl: string; 30 | 31 | @Column({ type: 'text' }) 32 | referenceId: string; 33 | } 34 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationPreferenceComment.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, ManyToOne } from 'typeorm'; 2 | import { NotificationPreference } from './NotificationPreference'; 3 | import { NotificationPreferenceType } from '../../notifications/common'; 4 | import type { Comment } from '../Comment'; 5 | 6 | @ChildEntity(NotificationPreferenceType.Comment) 7 | export class NotificationPreferenceComment extends NotificationPreference { 8 | @Column({ type: 'text', default: null }) 9 | commentId: string; 10 | 11 | @ManyToOne('Comment', { lazy: true, onDelete: 'CASCADE' }) 12 | comment: Promise; 13 | } 14 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationPreferencePost.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, ManyToOne } from 'typeorm'; 2 | import type { Post } from '../posts'; 3 | import { NotificationPreferenceType } from '../../notifications/common'; 4 | import { NotificationPreference } from './NotificationPreference'; 5 | 6 | @ChildEntity(NotificationPreferenceType.Post) 7 | export class NotificationPreferencePost extends NotificationPreference { 8 | @Column({ type: 'text', default: null }) 9 | postId: string; 10 | 11 | @ManyToOne('Post', { lazy: true, onDelete: 'CASCADE' }) 12 | post: Promise; 13 | } 14 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationPreferenceSource.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, ManyToOne } from 'typeorm'; 2 | import type { Source } from '../Source'; 3 | import { NotificationPreferenceType } from '../../notifications/common'; 4 | import { NotificationPreference } from './NotificationPreference'; 5 | 6 | @ChildEntity(NotificationPreferenceType.Source) 7 | export class NotificationPreferenceSource extends NotificationPreference { 8 | @Column({ type: 'text', default: null }) 9 | sourceId: string; 10 | 11 | @ManyToOne('Source', { lazy: true, onDelete: 'CASCADE' }) 12 | source: Promise; 13 | } 14 | -------------------------------------------------------------------------------- /src/entity/notifications/NotificationPreferenceUser.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, JoinColumn, ManyToOne } from 'typeorm'; 2 | import type { User } from '../user/User'; 3 | import { NotificationPreference } from './NotificationPreference'; 4 | import { NotificationPreferenceType } from '../../notifications/common'; 5 | 6 | @ChildEntity(NotificationPreferenceType.User) 7 | export class NotificationPreferenceUser extends NotificationPreference { 8 | @Column({ type: 'text', default: null }) 9 | referenceUserId: string; 10 | 11 | @ManyToOne('User', { lazy: true, onDelete: 'CASCADE' }) 12 | @JoinColumn({ name: 'referenceUserId' }) 13 | user: Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/entity/notifications/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NotificationV2'; 2 | export * from './NotificationAvatarV2'; 3 | export * from './NotificationAttachmentV2'; 4 | export * from './NotificationPreference'; 5 | export * from './NotificationPreferencePost'; 6 | export * from './NotificationPreferenceSource'; 7 | export * from './NotificationPreferenceComment'; 8 | export * from './NotificationPreferenceUser'; 9 | export * from './UserNotification'; 10 | -------------------------------------------------------------------------------- /src/entity/posts/CollectionPost.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column } from 'typeorm'; 2 | import { Post, PostType } from './Post'; 3 | 4 | @ChildEntity(PostType.Collection) 5 | export class CollectionPost extends Post { 6 | @Column({ type: 'text', nullable: true }) 7 | image?: string; 8 | 9 | @Column({ type: 'text', nullable: true }) 10 | content: string; 11 | 12 | @Column({ type: 'text', nullable: true }) 13 | contentHtml: string; 14 | 15 | @Column({ nullable: true }) 16 | readTime?: number; 17 | 18 | @Column({ type: 'text', nullable: true }) 19 | description?: string; 20 | 21 | @Column({ type: 'text', nullable: true }) 22 | summary?: string; 23 | 24 | @Column({ type: 'text', array: true, default: [] }) 25 | collectionSources: string[]; 26 | } 27 | -------------------------------------------------------------------------------- /src/entity/posts/PostMention.ts: -------------------------------------------------------------------------------- 1 | import { Entity, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from '../user'; 3 | import type { Post } from './Post'; 4 | 5 | @Entity() 6 | export class PostMention { 7 | @PrimaryColumn() 8 | postId: string; 9 | 10 | @PrimaryColumn({ length: 36 }) 11 | mentionedByUserId: string; 12 | 13 | @PrimaryColumn({ length: 36 }) 14 | mentionedUserId: string; 15 | 16 | @ManyToOne('Post', { 17 | lazy: true, 18 | onDelete: 'CASCADE', 19 | }) 20 | post?: Promise; 21 | 22 | @ManyToOne('User', { 23 | lazy: true, 24 | onDelete: 'CASCADE', 25 | }) 26 | mentionedByUser?: Promise; 27 | 28 | @ManyToOne('User', { 29 | lazy: true, 30 | onDelete: 'CASCADE', 31 | }) 32 | mentionedUser?: Promise; 33 | } 34 | -------------------------------------------------------------------------------- /src/entity/posts/PostQuestion.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | Index, 5 | ManyToOne, 6 | PrimaryGeneratedColumn, 7 | } from 'typeorm'; 8 | import type { Post } from './Post'; 9 | 10 | @Entity() 11 | export class PostQuestion { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @Index() 16 | @Column({ type: 'text' }) 17 | postId: string; 18 | 19 | @Column({ type: 'text' }) 20 | question: string; 21 | 22 | @Column({ default: () => 'now()' }) 23 | createdAt: Date; 24 | 25 | @ManyToOne('Post', { 26 | lazy: true, 27 | onDelete: 'CASCADE', 28 | }) 29 | post: Promise; 30 | } 31 | -------------------------------------------------------------------------------- /src/entity/posts/SharePost.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity, Column, Index, OneToOne } from 'typeorm'; 2 | import { Post, PostType } from './Post'; 3 | 4 | @ChildEntity(PostType.Share) 5 | export class SharePost extends Post { 6 | @Column({ type: 'text' }) 7 | @Index('IDX_sharedPostId') 8 | sharedPostId: string; 9 | 10 | @OneToOne(() => Post, { lazy: true, onDelete: 'SET NULL' }) 11 | sharedPost: Promise; 12 | } 13 | 14 | export const MAX_COMMENTARY_LENGTH = 5000; 15 | -------------------------------------------------------------------------------- /src/entity/posts/WelcomePost.ts: -------------------------------------------------------------------------------- 1 | import { ChildEntity } from 'typeorm'; 2 | import { PostType } from './Post'; 3 | import { FreeformPost } from './FreeformPost'; 4 | 5 | @ChildEntity(PostType.Welcome) 6 | export class WelcomePost extends FreeformPost {} 7 | -------------------------------------------------------------------------------- /src/entity/posts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Post'; 2 | export * from './ArticlePost'; 3 | export * from './SharePost'; 4 | export * from './FreeformPost'; 5 | export * from './WelcomePost'; 6 | export * from './PostMention'; 7 | export * from './PostQuestion'; 8 | export * from './utils'; 9 | export * from './PostRelation'; 10 | export * from './CollectionPost'; 11 | export * from './YouTubePost'; 12 | -------------------------------------------------------------------------------- /src/entity/sources/SourceReport.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from '../user'; 3 | import { ReportReason } from '../common'; 4 | 5 | @Entity() 6 | export class SourceReport { 7 | @PrimaryColumn({ type: 'text' }) 8 | @Index('IDX_source_report_source_id') 9 | sourceId: string; 10 | 11 | @PrimaryColumn({ length: 36 }) 12 | @Index('IDX_source_report_user_id') 13 | userId: string; 14 | 15 | @Column({ default: () => 'now()' }) 16 | createdAt: Date; 17 | 18 | @Column({ type: 'text' }) 19 | reason: ReportReason; 20 | 21 | @Column({ type: 'text', nullable: true }) 22 | comment?: string; 23 | 24 | @ManyToOne('User', { 25 | lazy: true, 26 | onDelete: 'CASCADE', 27 | }) 28 | user: Promise; 29 | } 30 | -------------------------------------------------------------------------------- /src/entity/user/DeletedUser.ts: -------------------------------------------------------------------------------- 1 | import { CreateDateColumn, Entity, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class DeletedUser { 5 | @PrimaryColumn({ length: 36 }) 6 | id: string; 7 | 8 | @CreateDateColumn() 9 | userDeletedAt: Date; 10 | } 11 | -------------------------------------------------------------------------------- /src/entity/user/UserMarketingCta.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from './User'; 3 | import type { MarketingCta } from '../MarketingCta'; 4 | 5 | @Entity() 6 | export class UserMarketingCta { 7 | @PrimaryColumn({ type: 'text' }) 8 | marketingCtaId: string; 9 | 10 | @PrimaryColumn({ type: 'text' }) 11 | userId: string; 12 | 13 | @Column({ default: () => 'now()' }) 14 | createdAt: Date; 15 | 16 | @Column({ nullable: true }) 17 | readAt: Date; 18 | 19 | @ManyToOne('MarketingCta', { 20 | onDelete: 'CASCADE', 21 | }) 22 | @JoinColumn({ name: 'marketingCtaId' }) 23 | marketingCta: MarketingCta; 24 | 25 | @ManyToOne('User', { 26 | lazy: true, 27 | onDelete: 'CASCADE', 28 | }) 29 | user: Promise; 30 | } 31 | -------------------------------------------------------------------------------- /src/entity/user/UserState.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from './User'; 3 | 4 | export enum UserStateKey { 5 | CommunityLinkAccess = 'community_link_access', 6 | } 7 | 8 | @Entity() 9 | export class UserState { 10 | @PrimaryColumn({ length: 36 }) 11 | userId: string; 12 | 13 | @PrimaryColumn() 14 | key: string; 15 | 16 | @Column({ default: false }) 17 | value: boolean; 18 | 19 | @ManyToOne('User', { 20 | lazy: true, 21 | onDelete: 'CASCADE', 22 | }) 23 | user: Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/entity/user/UserStreakAction.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import type { User } from './User'; 3 | 4 | export enum UserStreakActionType { 5 | Recover = 'recover', 6 | } 7 | 8 | @Entity() 9 | @Index('IDX_usa_userid_type', ['userId', 'type']) 10 | export class UserStreakAction { 11 | @PrimaryColumn({ length: 36 }) 12 | userId: string; 13 | 14 | @PrimaryColumn({ type: 'text' }) 15 | type: UserStreakActionType; 16 | 17 | @PrimaryColumn({ type: 'timestamp', default: () => 'now()' }) 18 | createdAt: Date; 19 | 20 | @ManyToOne('User', (user: User) => user.streakActions, { 21 | lazy: true, 22 | onDelete: 'CASCADE', 23 | }) 24 | @JoinColumn({ name: 'userId' }) 25 | user: Promise; 26 | } 27 | -------------------------------------------------------------------------------- /src/entity/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './User'; 2 | export * from './UserPost'; 3 | export * from './UserState'; 4 | export * from './UserAction'; 5 | export * from './UserStreak'; 6 | export * from './UserStreakAction'; 7 | export * from './UserPersonalizedDigest'; 8 | export * from './UserMarketingCta'; 9 | export * from './UserStats'; 10 | export * from './UserTopReader'; 11 | -------------------------------------------------------------------------------- /src/http.ts: -------------------------------------------------------------------------------- 1 | import { AgentOptions } from 'http'; 2 | import http from 'node:http'; 3 | import https from 'node:https'; 4 | import { RequestInit } from 'node-fetch'; 5 | 6 | const agentOpts: AgentOptions = { keepAlive: true, timeout: 1000 * 5 }; 7 | const httpAgent = new http.Agent(agentOpts); 8 | const httpsAgent = new https.Agent(agentOpts); 9 | export const fetchOptions: RequestInit = { 10 | agent: (_parsedURL) => 11 | _parsedURL.protocol === 'http:' ? httpAgent : httpsAgent, 12 | }; 13 | -------------------------------------------------------------------------------- /src/ids.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto'; 2 | import { customAlphabet } from 'nanoid/async'; 3 | import { FastifyRequest } from 'fastify'; 4 | import { counters } from './telemetry'; 5 | 6 | const alphabet = 7 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 8 | export const generateLongId = customAlphabet(alphabet, 21); 9 | export const generateShortId = customAlphabet(alphabet, 9); 10 | export const generateVerifyCode = customAlphabet('1234567890', 6); 11 | export const generateUUID = () => randomUUID(); 12 | 13 | export const generateTrackingId = ( 14 | req: FastifyRequest, 15 | origin: string, 16 | ): Promise => { 17 | counters?.api?.generateTrackingId?.add(1, { origin }); 18 | return generateLongId(); 19 | }; 20 | -------------------------------------------------------------------------------- /src/integrations/automation/automations.ts: -------------------------------------------------------------------------------- 1 | import { Automation, IAutomationService } from './types'; 2 | import { RetoolAutomationService } from './retool'; 3 | import { fetchOptions as globalFetchOptions } from '../../http'; 4 | 5 | export const automations: Record< 6 | Automation, 7 | IAutomationService, unknown> 8 | > = { 9 | roaster: new RetoolAutomationService(process.env.ROASTER_URL, { 10 | ...globalFetchOptions, 11 | timeout: 1000 * 60, 12 | }), 13 | }; 14 | -------------------------------------------------------------------------------- /src/integrations/automation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './automations'; 3 | -------------------------------------------------------------------------------- /src/integrations/automation/retool.ts: -------------------------------------------------------------------------------- 1 | import { IAutomationService } from './types'; 2 | import { RequestInit } from 'node-fetch'; 3 | import { fetchOptions as globalFetchOptions } from '../../http'; 4 | import { retryFetchParse } from '../retry'; 5 | 6 | export class RetoolAutomationService 7 | implements IAutomationService 8 | { 9 | constructor( 10 | private readonly url: string, 11 | private readonly fetchOptions: RequestInit = globalFetchOptions, 12 | ) {} 13 | 14 | async run(args: Args): Promise { 15 | return retryFetchParse( 16 | this.url, 17 | { 18 | ...this.fetchOptions, 19 | method: 'POST', 20 | body: JSON.stringify(args), 21 | }, 22 | { retries: 1 }, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/integrations/automation/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An interface for an automation service (Retool, Zapier, etc) 3 | */ 4 | export interface IAutomationService { 5 | run(args: Args): Promise; 6 | } 7 | 8 | export enum Automation { 9 | Roaster = 'roaster', 10 | } 11 | -------------------------------------------------------------------------------- /src/integrations/feed/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { 3 | FeedPreferencesConfigGenerator, 4 | SimpleFeedConfigGenerator, 5 | } from './configs'; 6 | export { FeedClient } from './clients'; 7 | export { 8 | FeedGenerator, 9 | feedGenerators, 10 | versionToFeedGenerator, 11 | feedClient, 12 | } from './generators'; 13 | -------------------------------------------------------------------------------- /src/integrations/freyja/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { FreyjaClient, freyjaClient } from './clients'; 3 | -------------------------------------------------------------------------------- /src/integrations/freyja/types.ts: -------------------------------------------------------------------------------- 1 | // Keep the type flexible to allow for future changes 2 | export type FunnelState = { 3 | session: { 4 | id: string; 5 | currentStep: string; 6 | userId: string; 7 | } & Record; 8 | funnel: { 9 | id: string; 10 | version: number; 11 | } & Record; 12 | }; 13 | 14 | export interface IFreyjaClient { 15 | createSession( 16 | userId: string, 17 | funnelId: string, 18 | version?: number, 19 | ): Promise; 20 | getSession(sessionId: string): Promise; 21 | } 22 | -------------------------------------------------------------------------------- /src/integrations/index.ts: -------------------------------------------------------------------------------- 1 | export * from './magni'; 2 | -------------------------------------------------------------------------------- /src/integrations/lofn/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { LofnClient } from './clients'; 3 | -------------------------------------------------------------------------------- /src/integrations/lofn/types.ts: -------------------------------------------------------------------------------- 1 | import { FeedConfig, FeedVersion } from '../feed'; 2 | 3 | export type UserState = 'personalised' | 'non_personalised'; 4 | 5 | export type GenericMetadata = { 6 | [key: string]: unknown; 7 | }; 8 | 9 | export type LofnFeedConfigResponse = { 10 | user_id: string; 11 | config: Omit; 12 | tyr_metadata?: GenericMetadata; 13 | extra?: GenericMetadata; 14 | }; 15 | 16 | export type LofnFeedConfigPayload = { 17 | user_id: string; 18 | feed_version: FeedVersion; 19 | cursor: string; 20 | }; 21 | 22 | export interface ILofnClient { 23 | fetchConfig(payload: LofnFeedConfigPayload): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/integrations/mimir/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { MimirClient, mimirClient } from './clients'; 3 | -------------------------------------------------------------------------------- /src/integrations/mimir/types.ts: -------------------------------------------------------------------------------- 1 | // Keep the type flexible to allow for future changes 2 | import { SearchRequest, SearchResponse } from '@dailydotdev/schema'; 3 | 4 | export interface IMimirClient { 5 | search({ 6 | query, 7 | version, 8 | offset, 9 | limit, 10 | }: SearchRequest): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /src/integrations/skadi/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { SkadiClient, skadiPersonalizedDigestClient } from './clients'; 3 | -------------------------------------------------------------------------------- /src/integrations/skadi/types.ts: -------------------------------------------------------------------------------- 1 | // Keep the type flexible to allow for future changes 2 | export type SkadiAd = { 3 | title: string; 4 | link: string; 5 | image: string; 6 | company_name: string; 7 | company_logo: string; 8 | call_to_action: string; 9 | }; 10 | export type SkadiResponse = Partial<{ 11 | type: string; 12 | value: { 13 | digest: SkadiAd; 14 | }; 15 | pixels: string[]; 16 | }>; 17 | 18 | export interface ISkadiClient { 19 | getAd( 20 | placement: string, 21 | metadata: { 22 | USERID: string; 23 | }, 24 | ): Promise; 25 | } 26 | -------------------------------------------------------------------------------- /src/integrations/snotra/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { SnotraClient } from './clients'; 3 | -------------------------------------------------------------------------------- /src/integrations/snotra/types.ts: -------------------------------------------------------------------------------- 1 | export type UserStatePayload = { 2 | user_id: string; 3 | post_rank_count?: number; 4 | providers: { 5 | personalise: Record; 6 | }; 7 | }; 8 | 9 | export type UserState = 'personalised' | 'non_personalised'; 10 | 11 | export type UserStateResponse = { 12 | personalise: { 13 | state: UserState; 14 | }; 15 | }; 16 | 17 | export interface ISnotraClient { 18 | fetchUserState(payload: UserStatePayload): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | 3 | import { createGcpLoggingPinoConfig } from '@google-cloud/pino-logging-gcp-config'; 4 | import pino, { type LoggerOptions } from 'pino'; 5 | 6 | const pinoLoggerOptions: LoggerOptions = { 7 | level: env.LOG_LEVEL || 'info', 8 | }; 9 | 10 | export const loggerConfig: LoggerOptions = 11 | env.NODE_ENV === 'production' 12 | ? createGcpLoggingPinoConfig( 13 | { 14 | serviceContext: { 15 | service: env.SERVICE_NAME || 'service', 16 | version: env.SERVICE_VERSION || 'latest', 17 | }, 18 | }, 19 | pinoLoggerOptions, 20 | ) 21 | : pinoLoggerOptions; 22 | 23 | export const logger = pino(loggerConfig); 24 | -------------------------------------------------------------------------------- /src/migration/1587564396149-Notification.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class Notification1587564396149 implements MigrationInterface { 4 | name = 'Notification1587564396149' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE TABLE "public"."notification" ("timestamp" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, "html" text NOT NULL, CONSTRAINT "PK_aaea7d16887fb591ff6228131aa" PRIMARY KEY ("timestamp"))`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP TABLE "public"."notification"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1589700555463-UpdateScoreIndex.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class UpdateScoreIndex1589700555463 implements MigrationInterface { 4 | name = 'UpdateScoreIndex1589700555463' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`DROP INDEX "public"."IDX_post_score"`, undefined); 8 | await queryRunner.query(`CREATE INDEX "IDX_post_score" ON "public"."post" ("score" DESC) `, undefined); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_post_score"`, undefined); 13 | await queryRunner.query(`CREATE INDEX "IDX_post_score" ON "public"."post" ("score") `, undefined); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1589812598274-Banner.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class Banner1589812598274 implements MigrationInterface { 4 | name = 'Banner1589812598274' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE TABLE "public"."banner" ("timestamp" TIMESTAMP NOT NULL DEFAULT now(), "title" text NOT NULL, "subtitle" text NOT NULL, "cta" text NOT NULL, "url" text NOT NULL, "theme" text NOT NULL, CONSTRAINT "PK_2964edc0a2e737dd174e32a5446" PRIMARY KEY ("timestamp"))`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP TABLE "public"."banner"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1591621000530-Integration.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class Integration1591621000530 implements MigrationInterface { 4 | name = 'Integration1591621000530' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE TABLE "public"."integration" ("timestamp" TIMESTAMP NOT NULL DEFAULT now(), "logo" text NOT NULL, "title" text NOT NULL, "subtitle" text NOT NULL, "url" text NOT NULL, CONSTRAINT "PK_32512f32a4d191de7b64373ff5f" PRIMARY KEY ("timestamp"))`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP TABLE "public"."integration"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1595072041410-OpenNewTabSetting.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class OpenNewTabSetting1595072041410 implements MigrationInterface { 4 | name = 'OpenNewTabSetting1595072041410' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."settings" ADD "openNewTab" boolean NOT NULL DEFAULT true`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."settings" DROP COLUMN "openNewTab"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1595246727426-PostShortId.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class PostShortId1595246727426 implements MigrationInterface { 4 | name = 'PostShortId1595246727426' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."post" ADD "shortId" character varying(14)`, undefined); 8 | await queryRunner.query(`CREATE INDEX "IDX_post_permalink" ON "public"."post" ("shortId") `, undefined); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_post_permalink"`, undefined); 13 | await queryRunner.query(`ALTER TABLE "public"."post" DROP COLUMN "shortId"`, undefined); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1595345916300-User.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class User1595345916300 implements MigrationInterface { 4 | name = 'User1595345916300' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE TABLE "public"."user" ("id" character varying(36) NOT NULL, "name" text NOT NULL, "image" text NOT NULL, CONSTRAINT "PK_03b91d2b8321aa7ba32257dc321" PRIMARY KEY ("id"))`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP TABLE "public"."user"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1598351006459-SourceFeedLastFetched.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class SourceFeedLastFetched1598351006459 implements MigrationInterface { 4 | name = 'SourceFeedLastFetched1598351006459' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."source_feed" ADD "lastFetched" TIMESTAMP`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."source_feed" DROP COLUMN "lastFetched"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1600267654798-Reputation.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class Reputation1600267654798 implements MigrationInterface { 4 | name = 'Reputation1600267654798' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."user" ADD "reputation" integer NOT NULL DEFAULT 0`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."user" DROP COLUMN "reputation"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1600350754278-RetroReputation.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class RetroReputation1600350754278 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`update "user" u set reputation = res.reputation from (select (sum(c.upvotes) + sum(c.featured::int) * 2) reputation, c."userId" from "comment" c where c.upvotes > 0 group by c."userId") res where u.id = res."userId";`, undefined); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query(`UPDATE "public"."user" SET "reputation" = 0`, undefined); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1602486972915-IndexViews.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class IndexViews1602486972915 implements MigrationInterface { 4 | name = 'IndexViews1602486972915' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX "IDX_post_views" ON "public"."post" ("views") `, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP INDEX "public"."IDX_post_views"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1603894888844-SourceRankBoost.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class SourceRankBoost1603894888844 implements MigrationInterface { 4 | name = 'SourceRankBoost1603894888844' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."source" ADD "rankBoost" integer NOT NULL DEFAULT 0`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."source" DROP COLUMN "rankBoost"`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1606393430015-RoomyDefault.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class RoomyDefault1606393430015 implements MigrationInterface { 4 | name = 'RoomyDefault1606393430015' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."settings" ALTER COLUMN "spaciness" SET DEFAULT 'roomy'`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."settings" ALTER COLUMN "spaciness" SET DEFAULT 'eco'`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1609057573320-EcoDefault.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class EcoDefault1609057573320 implements MigrationInterface { 4 | name = 'EcoDefault1609057573320' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."settings" ALTER COLUMN "spaciness" SET DEFAULT 'eco'`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."settings" ALTER COLUMN "spaciness" SET DEFAULT 'roomy'`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1609237342955-KeywordOccurrencesDefaultUpdate.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class KeywordOccurrencesDefaultUpdate1609237342955 implements MigrationInterface { 4 | name = 'KeywordOccurrencesDefaultUpdate1609237342955' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."keyword" ALTER COLUMN "occurrences" SET DEFAULT 1`, undefined); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."keyword" ALTER COLUMN "occurrences" SET DEFAULT 0`, undefined); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1611589310852-TagsStrCheckpoint.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class TagsStrCheckpoint1611589310852 implements MigrationInterface { 4 | name = 'TagsStrCheckpoint1611589310852' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`INSERT INTO "public"."checkpoint" ("key", "timestamp") VALUES ('last_tags_str_update', now())`) 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DELETE FROM "public"."checkpoint" WHERE "key" = 'last_tags_str_update'`) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1620311370218-CommentLastUpdate.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CommentLastUpdate1620311370218 implements MigrationInterface { 4 | name = 'CommentLastUpdate1620311370218'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."comment" ADD "lastUpdatedAt" TIMESTAMP`, 9 | undefined, 10 | ); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query( 15 | `ALTER TABLE "public"."comment" DROP COLUMN "lastUpdatedAt"`, 16 | undefined, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1622476886845-AddDisplayPropsToSource.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddDisplayPropsToSource1622476886845 4 | implements MigrationInterface { 5 | name = 'AddDisplayPropsToSource1622476886845'; 6 | 7 | public async up(queryRunner: QueryRunner): Promise { 8 | await queryRunner.query(`ALTER TABLE "public"."source" ADD "name" text`); 9 | await queryRunner.query(`ALTER TABLE "public"."source" ADD "image" text`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."source" DROP COLUMN "image"`, 15 | ); 16 | await queryRunner.query(`ALTER TABLE "public"."source" DROP COLUMN "name"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/migration/1623847855158-PostToc.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostToc1623847855158 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "public"."post" 7 | ADD "description" text`, 8 | ); 9 | await queryRunner.query(`ALTER TABLE "public"."post" 10 | ADD "toc" jsonb`); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query(`ALTER TABLE "public"."post" 15 | DROP COLUMN "toc"`); 16 | await queryRunner.query( 17 | `ALTER TABLE "public"."post" 18 | DROP COLUMN "description"`, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/migration/1627479592436-DevCardEligible.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class DevCardEligible1627479592436 implements MigrationInterface { 4 | name = 'DevCardEligible1627479592436' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."user" ADD "devcardEligible" boolean NOT NULL DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."user" DROP COLUMN "devcardEligible"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1628603355496-PostReportReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostReportReplica1628603355496 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "public"."post_report" REPLICA IDENTITY FULL`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "public"."post_report" REPLICA IDENTITY DEFAULT`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1628691360469-UserCreatedAt.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class UserCreatedAt1628691360469 implements MigrationInterface { 4 | name = 'UserCreatedAt1628691360469' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."user" ADD "createdAt" TIMESTAMP`); 8 | await queryRunner.query(`CREATE INDEX "IDX_user_createdAt" ON "public"."user" ("createdAt") `); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_user_createdAt"`); 13 | await queryRunner.query(`ALTER TABLE "public"."user" DROP COLUMN "createdAt"`); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1631197656554-DeleteViewColumns.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class DeleteViewColumns1631197656554 implements MigrationInterface { 4 | name = 'DeleteViewColumns1631197656554' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."view" DROP COLUMN "agent"`); 8 | await queryRunner.query(`ALTER TABLE "public"."view" DROP COLUMN "ip"`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "public"."view" ADD "ip" text`); 13 | await queryRunner.query(`ALTER TABLE "public"."view" ADD "agent" text`); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1635231888769-PostSummary.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class PostSummary1635231888769 implements MigrationInterface { 4 | name = 'PostSummary1635231888769' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."post" ADD "summary" text`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."post" DROP COLUMN "summary"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1635777599202-ReportReasonComment.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ReportReasonComment1635777599202 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "public"."post_report" ADD "comment" text`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "public"."post_report" DROP COLUMN "comment"`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1636372778451-AlertsFullReplication.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertsFullReplication1636372778451 implements MigrationInterface { 4 | name = 'AlertsFullReplication1636372778451'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."alerts" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."alerts" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1636431439048-ViewHiddenColumn.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class ViewHiddenColumn1636431439048 implements MigrationInterface { 4 | name = 'ViewHiddenColumn1636431439048' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."view" ADD "hidden" boolean NOT NULL DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."view" DROP COLUMN "hidden"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1636615784674-AddTimeZoneToUser.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddTimeZoneToUser1636615784674 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "public"."user" ADD "timezone" text NULL`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "public"."user" DROP COLUMN "timezone"`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1636725824921-AlertsRankColumn.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class AlertsRankColumn1636725824921 implements MigrationInterface { 4 | name = 'AlertsRankColumn1636725824921' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "public"."alerts" ADD "rankLastSeen" TIMESTAMP`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "public"."alerts" DROP COLUMN "rankLastSeen"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1637823009822-HTMLComments.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class HTMLComments1637823009822 implements MigrationInterface { 4 | name = 'HTMLComments1637823009822'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."comment" ADD "contentHtml" text NULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."comment" DROP COLUMN "contentHtml"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1638431396006-OpenSidebarSettings.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class OpenSidebarSettings1638431396006 implements MigrationInterface { 4 | name = 'OpenSidebarSettings1638431396006'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."settings" ADD "openSidebar" boolean NOT NULL DEFAULT true`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."settings" DROP COLUMN "openSidebar"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1639645108022-SidebarExpandedSettings.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SidebarExpandedSettings1639645108022 4 | implements MigrationInterface 5 | { 6 | name = 'SidebarExpandedSettings1639645108022'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "public"."settings" RENAME COLUMN "openSidebar" TO "sidebarExpanded"`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "public"."settings" RENAME COLUMN "sidebarExpanded" TO "openSidebar"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1640931306714-AlertsMyFeedColumn.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertsMyFeedColumn1640931306714 implements MigrationInterface { 4 | name = 'AlertsMyFeedColumn1640931306714'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."alerts" ADD "myFeed" text NULL DEFAULT 'created'`, 9 | ); 10 | await queryRunner.query( 11 | `update "public"."alerts" set "myFeed" = 'migrated' where filter is FALSE`, 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | `ALTER TABLE "public"."alerts" DROP COLUMN "myFeed"`, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/migration/1641904779220-AlertMyFeedDefaultValue.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertMyFeedDefaultValue1641904779220 4 | implements MigrationInterface 5 | { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "alerts" ALTER COLUMN "myFeed" DROP NOT NULL`); 8 | await queryRunner.query(`ALTER TABLE "alerts" ALTER COLUMN "myFeed" DROP DEFAULT`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "alerts" ALTER COLUMN "myFeed" SET DEFAULT 'created'`); 13 | await queryRunner.query(`ALTER TABLE "alerts" ALTER COLUMN "myFeed" SET NOT NULL`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1642055432413-SettingsEnabledSorting.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class SettingsEnabledSorting1642055432413 implements MigrationInterface { 4 | name = 'SettingsEnabledSorting1642055432413' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" ADD "sortingEnabled" boolean NOT NULL DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "settings" DROP COLUMN "sortingEnabled"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1642656948858-SettingsCustomLinks.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class SettingsCustomLinks1642656948858 implements MigrationInterface { 4 | name = 'SettingsCustomLinks1642656948858' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" ADD "customLinks" text array`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "settings" DROP COLUMN "customLinks"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1643634601290-BookmarkSlug.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class BookmarkSlug1643634601290 implements MigrationInterface { 4 | name = 'BookmarkSlug1643634601290'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" 8 | ADD "bookmarkSlug" uuid`); 9 | await queryRunner.query( 10 | `CREATE UNIQUE INDEX "IDX_settings_bookmarkslug" ON "settings" ("bookmarkSlug") `, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query(`DROP INDEX "public"."IDX_settings_bookmarkslug"`); 16 | await queryRunner.query( 17 | `ALTER TABLE "settings" DROP COLUMN "bookmarkSlug"`, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/migration/1643720074970-OptOutWeeklyGoal.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class OptOutWeeklyGoal1643720074970 implements MigrationInterface { 4 | name = 'OptOutWeeklyGoal1643720074970'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" 8 | ADD "optOutWeeklyGoal" boolean NOT NULL DEFAULT false`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query( 13 | `ALTER TABLE "settings" DROP COLUMN "optOutWeeklyGoal"`, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1644408031624-addViewHiddenIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class addViewHiddenIndex1644408031624 implements MigrationInterface { 4 | name = 'addViewHiddenIndex1644408031624'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_a91d81ad0de50fab4688a665c1" ON "view" ("postId", "userId", "hidden") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_a91d81ad0de50fab4688a665c1"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1645627361402-ActiveView.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ActiveView1645627361402 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `CREATE VIEW "public"."active_view" AS SELECT "view"."postId" AS "postId", "view"."userId" AS "userId", "view"."timestamp" AS "timestamp", "view"."hidden" AS "hidden" FROM "public"."view" LEFT JOIN "public"."post" "post" ON "post"."id" = "view"."postId" WHERE "post"."deleted" = false`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP VIEW "public"."active_view"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1646231933553-AddOptOutCompanion.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddOptOutCompanion1646231933553 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query(`ALTER TABLE "settings" 6 | ADD "optOutCompanion" boolean NOT NULL DEFAULT false`); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "settings" DROP COLUMN "optOutCompanion"`, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1649851992869-AddCompanionHelper.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddCompanionHelper1649851992869 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "public"."alerts" ADD "companionHelper" boolean NOT NULL DEFAULT true`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "public"."alerts" DROP COLUMN "companionHelper"`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1650459501417-AddCompanionExpanded.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddCompanionExpanded1650459501417 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query(`ALTER TABLE "settings" 6 | ADD "companionExpanded" boolean NULL DEFAULT NULL`); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "settings" DROP COLUMN "companionExpanded"`, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1651214210491-AutoDismissSettings.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AutoDismissSettings1651214210491 implements MigrationInterface { 4 | name = 'AutoDismissSettings1651214210491'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "settings" ADD "autoDismissNotifications" boolean NOT NULL DEFAULT true`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "settings" DROP COLUMN "autoDismissNotifications"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1655452252620-ChangeSubmissionStatus.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ChangeSubmissionStatus1655452252620 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | `ALTER TABLE "submission" ALTER COLUMN "status" SET DEFAULT 'STARTED'`, 7 | ); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "submission" ALTER COLUMN "status" SET DEFAULT 'NOT_STARTED'`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1660205249491-AddReferralIncreaseUsername.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddReferralIncreaseUsername1660205249491 4 | implements MigrationInterface 5 | { 6 | name = 'AddReferralIncreaseUsername1660205249491'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query(`ALTER TABLE "user" 10 | ADD "referral" text`); 11 | await queryRunner.query( 12 | `ALTER TABLE "user" ALTER COLUMN "username" type character varying(39)`, 13 | ); 14 | } 15 | 16 | public async down(queryRunner: QueryRunner): Promise { 17 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "referral"`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1669630762042-DeleteLegacyNotification.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class DeleteLegacyNotification1669630762042 4 | implements MigrationInterface 5 | { 6 | name = 'DeleteLegacyNotification1669630762042'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query(`DROP TABLE "notification"`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise {} 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1669735640941-AvatarName.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AvatarName1669735640941 implements MigrationInterface { 4 | name = 'AvatarName1669735640941'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "notification_avatar" 8 | ADD "name" text NOT NULL`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query( 13 | `ALTER TABLE "notification_avatar" DROP COLUMN "name"`, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1671161744023-EmailNotificationPreference.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class EmailNotificationPreference1671161744023 4 | implements MigrationInterface 5 | { 6 | name = 'EmailNotificationPreference1671161744023'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "user" ADD "notificationEmail" boolean NOT NULL DEFAULT true`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "user" DROP COLUMN "notificationEmail"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1671719516235-NullableSourceImage.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class NullableSourceImage1671719516235 implements MigrationInterface { 4 | name = 'NullableSourceImage1671719516235'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" 8 | ALTER COLUMN "image" DROP NOT NULL`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "source" 13 | ALTER COLUMN "image" SET NOT NULL`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1671781195018-RequiredHandle.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class RequiredHandle1671781195018 implements MigrationInterface { 4 | name = 'RequiredHandle1671781195018'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`UPDATE "source" 8 | SET handle = id 9 | WHERE handle is null`); 10 | await queryRunner.query(`ALTER TABLE "source" 11 | ALTER COLUMN "handle" SET NOT NULL`); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query(`ALTER TABLE "source" 16 | ALTER COLUMN "handle" DROP NOT NULL`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/migration/1671874590903-SourceMemberCDC.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceMemberCDC1671874590903 implements MigrationInterface { 4 | name = 'SourceMemberCDC1671874590903'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."source_member" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."source_member" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1675235844097-SourceReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceReplica1675235844097 implements MigrationInterface { 4 | name = 'SourceReplica1675235844097'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."source" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."source" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1675687066898-SourceCreatedAt.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceCreatedAt1675687066898 implements MigrationInterface { 4 | name = 'SourceCreatedAt1675687066898'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "source" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "createdAt"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1675936666556-AlertsChangelog.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertsChangelog1675936666556 implements MigrationInterface { 4 | name = 'AlertsChangelog1675936666556'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "lastChangelog" TIMESTAMP NOT NULL DEFAULT now()`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "alerts" DROP COLUMN "lastChangelog"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1677030838294-AlertViewedTour.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertViewedTour1677030838294 implements MigrationInterface { 4 | name = 'AlertViewedTour1677030838294'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "squadTour" boolean NOT NULL DEFAULT true`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "alerts" DROP COLUMN "squadTour"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1680519844392-MemberPostingRank.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class MemberPostingRank1680519844392 implements MigrationInterface { 4 | name = 'MemberPostingRank1680519844392' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" ADD "memberPostingRank" integer DEFAULT 0 NOT NULL`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "memberPostingRank"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1681297594048-OwnerToAdmin.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class OwnerToAdmin1681297594048 implements MigrationInterface { 4 | name = 'OwnerToAdmin1681297594048'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `UPDATE "public"."source_member" SET "role" = 'admin' WHERE "role" = 'owner'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `UPDATE "public"."source_member" SET "role" = 'owner' WHERE "role" = 'admin'`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1681817520844-MemberInviteRank.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class MemberInviteRank1681817520844 implements MigrationInterface { 4 | name = 'MemberInviteRank1681817520844' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" ADD "memberInviteRank" integer DEFAULT 0 NOT NULL`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "memberInviteRank"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1682690930599-ContentCuration.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ContentCuration1682690930599 implements MigrationInterface { 4 | name = 'ContentCuration1682690930599'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "post" ADD "contentCuration" text array NOT NULL DEFAULT '{}'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "contentCuration"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1683721525235-DummyUser.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class DummyUser1683721525235 implements MigrationInterface { 4 | name = 'DummyUser1683721525235'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `INSERT INTO "public"."user" ("id", "name", "image", "username") VALUES ('404', 'Inactive user', 'https://daily-now-res.cloudinary.com/image/upload/f_auto,q_auto/placeholders/placeholder3', 'inactive_user')`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DELETE FROM "public"."user" where id = "404"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1683868176744-PostMentionTracking.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | export class PostMentionTracking1683868176744 implements MigrationInterface { 3 | name = 'PostMentionTracking1683868176744'; 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query( 7 | `ALTER TABLE "public"."post_mention" REPLICA IDENTITY FULL`, 8 | ); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query( 13 | `ALTER TABLE "public"."post_mention" REPLICA IDENTITY DEFAULT`, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1684485068564-DropActivePost.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class DropActivePost1684485068564 implements MigrationInterface { 4 | name = 'DropActivePost1684485068564'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`DROP VIEW "active_post"`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `CREATE VIEW "active_post" AS SELECT p.* FROM "public"."post" "p" WHERE "p"."deleted" = false AND "p"."visible" = true`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1684512961790-FixFeedTagIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class FixFeedTagIndex1684512961790 implements MigrationInterface { 4 | name = 'FixFeedTagIndex1684512961790'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`DROP INDEX "public"."IDX_feedTag_blocked"`); 8 | await queryRunner.query( 9 | `CREATE INDEX "IDX_feed_id_blocked" ON "feed_tag" ("feedId", "blocked") `, 10 | ); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query(`DROP INDEX "public"."IDX_feed_id_blocked"`); 15 | await queryRunner.query( 16 | `CREATE INDEX "IDX_feedTag_blocked" ON "feed_tag" ("blocked") `, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1685509975573-PostOrder.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostOrder1685509975573 implements MigrationInterface { 4 | name = 'PostOrder1685509975573'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "post" ADD "showOnFeed" boolean NOT NULL DEFAULT true`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "showOnFeed"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1685524779381-UserReferralOrigin.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserReferralOrigin1685524779381 implements MigrationInterface { 4 | name = 'UserReferralOrigin1685524779381' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "referralOrigin" text`); 8 | await queryRunner.query(`CREATE INDEX "IDX_user_referral_origin" ON "user" ("referralOrigin") `); 9 | await queryRunner.query(`UPDATE "user" SET "referralOrigin" = 'squad' WHERE "referralId" IS NOT NULL`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "referralOrigin"`); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1685957377259-PostTitleHtml.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostTitleHtml1685957377259 implements MigrationInterface { 4 | name = 'PostTitleHtml1685957377259'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "titleHtml" text`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "titleHtml"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1686736666472-PostDownvotes.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostDownvotes1686736666472 implements MigrationInterface { 4 | name = 'PostDownvotes1686736666472' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "downvotes" integer NOT NULL DEFAULT '0'`); 8 | await queryRunner.query(`CREATE INDEX "IDX_post_downvotes" ON "post" ("downvotes") `); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_post_downvotes"`); 13 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "downvotes"`); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1687171948216-PostReportTags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostReportTags1687171948216 implements MigrationInterface { 4 | name = 'PostReportTags1687171948216'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post_report" ADD "tags" text array`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post_report" DROP COLUMN "tags"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1687422430370-DisallowHandle.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class DisallowHandle1687422430370 implements MigrationInterface { 4 | name = 'DisallowHandle1687422430370'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE TABLE "disallow_handle" ("value" character varying NOT NULL, CONSTRAINT "PK_8c84e48365905231826700dd8b4" PRIMARY KEY ("value"))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP TABLE "disallow_handle"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1688567430880-CommentReportReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CommentReportReplica1688567430880 implements MigrationInterface { 4 | name = 'CommentReportReplica1688567430880'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."comment_report" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."comment_report" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1689854830946-WelcomeShowOnFeed.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class WelcomeShowOnFeed1689854830946 implements MigrationInterface { 4 | name = 'WelcomeShowOnFeed1689854830946'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `UPDATE post SET flags = flags || '{"showOnFeed": false}', "showOnFeed" = false WHERE type = 'welcome'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `UPDATE post SET flags = flags || '{"showOnFeed": true}', "showOnFeed" = true WHERE type = 'welcome'`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1689858892658-PublicSourceData.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PublicSourceData1689858892658 implements MigrationInterface { 4 | name = 'PublicSourceData1689858892658'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" ADD "headerImage" text`); 8 | await queryRunner.query(`ALTER TABLE "source" ADD "color" text`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "color"`); 13 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "headerImage"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1690963996461-PostYggdrasilId.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostYggdrasilId1690963996461 implements MigrationInterface { 4 | name = 'PostYggdrasilId1690963996461'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "yggdrasilId" uuid`); 8 | await queryRunner.query( 9 | `CREATE INDEX "IDX_yggdrasil_id" ON "post" ("yggdrasilId") `, 10 | ); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query(`DROP INDEX "public"."IDX_yggdrasil_id"`); 15 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "yggdrasilId"`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1692536195223-AlertsBanner.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertsBanner1692536195223 implements MigrationInterface { 4 | name = 'AlertsBanner1692536195223'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "lastBanner" TIMESTAMP NOT NULL DEFAULT now()`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "alerts" DROP COLUMN "lastBanner"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1693820674317-SourceImageDefault.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class SourceImageDefault1693820674317 implements MigrationInterface { 4 | name = 'SourceImageDefault1693820674317' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" ALTER COLUMN "image" SET DEFAULT 'https://daily-now-res.cloudinary.com/image/upload/s--LrHsyt2T--/f_auto/v1692632054/squad_placeholder_sfwkmj'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "source" ALTER COLUMN "image" SET DEFAULT 'https://daily-now-res.cloudinary.com/image/upload/v1672041320/squads/squad_placeholder.jpg'`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1693903931385-UserPostVoteIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserPostVoteIndex1693903931385 implements MigrationInterface { 4 | name = 'UserPostVoteIndex1693903931385' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX "IDX_1d63afaba1fa8e566ec9b62519" ON "user_post" ("userId", "vote", "votedAt") `); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP INDEX "public"."IDX_1d63afaba1fa8e566ec9b62519"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1694443626041-ViewIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ViewIndex1694443626041 implements MigrationInterface { 4 | name = 'ViewIndex1694443626041'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_3aaca4c7b6bd877a50443bed34" ON "view" ("userId", "timestamp") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_3aaca4c7b6bd877a50443bed34"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1695795022369-FeatureValue.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class FeatureValue1695795022369 implements MigrationInterface { 4 | name = 'FeatureValue1695795022369'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "feature" ADD "value" smallint NOT NULL DEFAULT '1'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "feature" DROP COLUMN "value"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1696422431723-SourceIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceIndex1696422431723 implements MigrationInterface { 4 | name = 'SourceIndex1696422431723'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_source_active_private_image" ON "source" ("active", "private", "image") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_source_active_private_image"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1696520516809-IndexEmail.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class IndexEmail1696520516809 implements MigrationInterface { 4 | name = 'IndexEmail1696520516809' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX "IDX_user_email" ON "user" ("email") `); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP INDEX "public"."IDX_user_email"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1697642145710-TagSearchIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class TagSearchIndex1697642145710 implements MigrationInterface { 4 | name = 'TagSearchIndex1697642145710' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX "IDX_status_value" ON "keyword" ("status", "value") `); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP INDEX "public"."IDX_status_value"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1698317123443-DigestVariation.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class DigestVariation1698317123443 implements MigrationInterface { 4 | name = 'DigestVariation1698317123443' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user_personalized_digest" ADD "variation" integer NOT NULL DEFAULT '1'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "user_personalized_digest" DROP COLUMN "variation"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1699372102745-UserPersonalizedDigestLastSendDate.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserPersonalizedDigestLastSendDate1699372102745 implements MigrationInterface { 4 | name = 'UserPersonalizedDigestLastSendDate1699372102745' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user_personalized_digest" ADD "lastSendDate" TIMESTAMP`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "user_personalized_digest" DROP COLUMN "lastSendDate"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1700559262252-AlertsReplicaRemoval.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertsReplicaRemoval1700559262252 implements MigrationInterface { 4 | name = 'AlertsReplicaRemoval1700559262252'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."alerts" REPLICA IDENTITY DEFAULT`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."alerts" REPLICA IDENTITY FULL`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1700661785471-PostCollectionSources.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostCollectionSources1700661785471 implements MigrationInterface { 4 | name = 'PostCollectionSources1700661785471' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "collectionSources" text array DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "collectionSources"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1700735236452-YouTubePost.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class YouTubePost1700735236452 implements MigrationInterface { 4 | name = 'YouTubePost1700735236452' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "videoId" text`); 8 | await queryRunner.query(`ALTER TABLE "advanced_settings" ADD "group" text NOT NULL DEFAULT 'advanced'`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "videoId"`); 13 | await queryRunner.query(`ALTER TABLE "advanced_settings" DROP COLUMN "group"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1700836062543-Notification.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class Notification1700836062543 implements MigrationInterface { 4 | name = 'Notification1700836062543' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "notification" ADD "numTotalAvatars" integer`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "numTotalAvatars"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1701100478531-PostRelationReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostRelationReplica1701100478531 implements MigrationInterface { 4 | name = 'PostRelationReplica1701100478531' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "post_relation" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "post_relation" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/migration/1702655844110-AdvancedSettings.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class AdvancedSettings1702655844110 implements MigrationInterface { 4 | name = 'AdvancedSettings1702655844110' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "advanced_settings" ADD "options" jsonb NOT NULL DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "advanced_settings" DROP COLUMN "options"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1702899818874-DeleteNotificationV1.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class DeleteNotificationV11702899818874 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query(`DROP TABLE "notification_attachment"`); 6 | await queryRunner.query(`DROP TABLE "notification_avatar"`); 7 | await queryRunner.query(`DROP TABLE "notification"`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise {} 11 | } 12 | -------------------------------------------------------------------------------- /src/migration/1703668189004-ProfileV2.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ProfileV21703668189004 implements MigrationInterface { 4 | name = 'ProfileV21703668189004'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "cover" text`); 8 | await queryRunner.query(`ALTER TABLE "user" ADD "readme" text`); 9 | await queryRunner.query(`ALTER TABLE "user" ADD "readmeHtml" text`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "readmeHtml"`); 14 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "readme"`); 15 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "cover"`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1704816100142-RemoveUpvoteDownvoteHiddenPostTables.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class RemoveUpvoteDownvoteHiddenPostTables1704816100142 implements MigrationInterface { 4 | name = 'RemoveUpvoteDownvoteHiddenPostTables1704816100142' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`DROP TABLE "upvote"`); 8 | await queryRunner.query(`DROP TABLE "downvote"`); 9 | await queryRunner.query(`DROP TABLE "hidden_post"`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise {} 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1704948666033-GhostUserProtect.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class GhostUserProtect1704948666033 implements MigrationInterface { 4 | name = 'GhostUserProtect1704948666033' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`UPDATE "user" SET "infoConfirmed" = true WHERE "id" = '404';`); 8 | await queryRunner.query(`CREATE RULE prototect_ghostuser_deletion AS ON DELETE TO "user" WHERE old.id IN ('404') DO INSTEAD nothing;`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`UPDATE "user" SET "infoConfirmed" = false WHERE "id" = '404';`); 13 | await queryRunner.query(`DROP RULE prototect_ghostuser_deletion on "user";`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1707921624847-BotUser.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class BotUser1707921624847 implements MigrationInterface { 4 | name = 'BotUser1707921624847'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE TABLE "bot_user" ("id" character varying(36) NOT NULL, "name" text, "email" text, "company" text, "title" text, "username" character varying(39), "bio" text, "portfolio" text, "timezone" text, "createdAt" TIMESTAMP NOT NULL, "readme" text, CONSTRAINT "PK_a3190460df612af6ab214b44a92" PRIMARY KEY ("id"))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP TABLE "bot_user"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1708024370161-readingStreaksAlert.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ReadingStreaksAlert1708024370161 implements MigrationInterface { 4 | name = 'ReadingStreaksAlert1708024370161'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "showStreakMilestone" boolean NOT NULL DEFAULT false`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "alerts" DROP COLUMN "showStreakMilestone"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1708364779258-PostGin.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class PostGin1708364779258 implements MigrationInterface { 4 | name = 'PostGin1708364779258'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_post_tsv" ON "post" USING GIN(tsv)`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP INDEX "public"."IDX_post_tsv"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1708961227968-PostLanguage.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostLanguage1708961227968 implements MigrationInterface { 4 | name = 'PostLanguage1708961227968' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "language" text DEFAULT 'en'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "language"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1709002546321-UserAcquisitionChannel.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserAcquisitionChannel1709002546321 implements MigrationInterface { 4 | name = 'UserAcquisitionChannel1709002546321'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "acquisitionChannel" text`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query( 12 | `ALTER TABLE "user" DROP COLUMN "acquisitionChannel"`, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1711384423929-CommentDownvotes.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class CommentDownvotes1711384423929 implements MigrationInterface { 4 | name = 'CommentDownvotes1711384423929' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "comment" ADD "downvotes" integer NOT NULL DEFAULT '0'`); 8 | await queryRunner.query(`CREATE INDEX "IDX_comment_downvotes" ON "comment" ("downvotes") `); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_comment_downvotes"`); 13 | await queryRunner.query(`ALTER TABLE "comment" DROP COLUMN "downvotes"`); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1713194178000-PostContentMeta.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostContentMeta1713194178000 implements MigrationInterface { 4 | name = 'PostContentMeta1713194178000' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "contentMeta" jsonb NOT NULL DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "contentMeta"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1713775430475-MarketingCta.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class MarketingCta1713775430475 implements MigrationInterface { 4 | name = 'MarketingCta1713775430475' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "marketing_cta" ADD "status" text NOT NULL DEFAULT 'active'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "marketing_cta" DROP COLUMN "status"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1713784472035-UserExperienceLevel.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserExperienceLevel1713784472035 implements MigrationInterface { 4 | name = 'UserExperienceLevel1713784472035'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "experienceLevel" text`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "experienceLevel"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1714045647906-LastBootPopup.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class LastBootPopup1714045647906 implements MigrationInterface { 4 | name = 'LastBootPopup1714045647906'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "lastBootPopup" TIMESTAMP`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "alerts" DROP COLUMN "lastBootPopup"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1714063032247-YggdrasilIdUnique.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class YggdrasilIdUnique1714063032247 implements MigrationInterface { 4 | name = 'YggdrasilIdUnique1714063032247' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`DROP INDEX "public"."IDX_yggdrasil_id"`); 8 | await queryRunner.query(`CREATE UNIQUE INDEX "IDX_yggdrasil_id" ON "post" ("yggdrasilId") `); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX "public"."IDX_yggdrasil_id"`); 13 | await queryRunner.query(`CREATE INDEX "IDX_yggdrasil_id" ON "post" ("yggdrasilId") `); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/migration/1714140296134-SourceFlags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceFlags1714140296134 implements MigrationInterface { 4 | name = 'SourceFlags1714140296134'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "source" ADD "flags" jsonb NOT NULL DEFAULT '{}'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "source" DROP COLUMN "flags"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1714395908762-PostContentQuality.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostContentQuality1714395908762 implements MigrationInterface { 4 | name = 'PostContentQuality1714395908762' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "contentQuality" jsonb NOT NULL DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "contentQuality"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1715592299433-SourceFlagFeaturedIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceFlagFeaturedIndex1715592299433 4 | implements MigrationInterface 5 | { 6 | name = 'SourceFlagFeaturedIndex1715592299433'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `CREATE INDEX "IDX_source_flags_featured" ON source USING HASH (((flags->'featured')::boolean))`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query(`DROP INDEX "IDX_source_flags_featured"`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1715682646033-SourceAdvancedSettingsRemoval.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceAdvancedSettingsRemoval1715682646033 4 | implements MigrationInterface 5 | { 6 | name = 'SourceAdvancedSettingsRemoval1715682646033'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "source" DROP COLUMN "advancedSettings"`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "source" ADD "advancedSettings" integer array DEFAULT '{}'`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1716379554138-SquadPublicRequestReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SquadPublicRequestReplica1716379554138 4 | implements MigrationInterface 5 | { 6 | name = 'SquadPublicRequestReplica1716379554138'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "public"."squad_public_request" REPLICA IDENTITY FULL`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "public"."squad_public_request" REPLICA IDENTITY DEFAULT`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1716976350324-AlertFeedSettingsFeedback.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertFeedSettingsFeedback1716976350324 4 | implements MigrationInterface 5 | { 6 | name = 'AlertFeedSettingsFeedback1716976350324'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "alerts" ADD "lastFeedSettingsFeedback" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "alerts" DROP COLUMN "lastFeedSettingsFeedback"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1717144015912-OptOutReadingStreak.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class OptOutReadingStreak1717144015912 implements MigrationInterface { 4 | name = 'OptOutReadingStreak1717144015912' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" ADD "optOutReadingStreak" boolean NOT NULL DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "settings" DROP COLUMN "optOutReadingStreak"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1718023112446-SidebarExpanded.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class SidebarExpanded1718023112446 implements MigrationInterface { 4 | name = 'SidebarExpanded1718023112446' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" ALTER COLUMN "sidebarExpanded" SET DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "settings" ALTER COLUMN "sidebarExpanded" SET DEFAULT true`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1718116498943-OnboardingChecklistSetting.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class OnboardingChecklistSetting1718116498943 implements MigrationInterface { 4 | name = 'OnboardingChecklistSetting1718116498943' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "settings" ADD "onboardingChecklistView" text NOT NULL DEFAULT 'hidden'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "settings" DROP COLUMN "onboardingChecklistView"`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1719406451367-ReplicaUserStreak.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ReplicaUserStreak1719406451367 implements MigrationInterface { 4 | name = 'ReplicaUserStreak1719406451367'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."user_streak" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."user_streak" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1720502712657-BookmarkRemindAt.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class BookmarkRemindAt1720502712657 implements MigrationInterface { 4 | name = 'BookmarkRemindAt1720502712657'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "bookmark" ADD "remindAt" TIMESTAMP`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "bookmark" DROP COLUMN "remindAt"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1721316842386-BookmarkReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class BookmarkReplica1721316842386 implements MigrationInterface { 4 | name = 'BookmarkReplica1721316842386'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."bookmark" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."bookmark" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1721377128858-FeedSettingsBlocked.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class FeedSettingsBlocked1721377128858 implements MigrationInterface { 4 | name = 'FeedSettingsBlocked1721377128858' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "feed_source" ADD "blocked" boolean NOT NULL DEFAULT true`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "feed_source" DROP COLUMN "blocked"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1722585215836-UserWeekStart.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserWeekStart1722585215836 implements MigrationInterface { 4 | name = 'UserWeekStart1722585215836' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "weekStart" integer DEFAULT '1'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "weekStart"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1722942338711-UserEmailIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserEmailIndex1722942338711 implements MigrationInterface { 4 | name = 'UserEmailIndex1722942338711' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_user_loweremail" ON "user" ((lower(email)));`); 8 | await queryRunner.query(`DROP INDEX IF EXISTS "public"."user_email_index";`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`CREATE INDEX IF NOT EXISTS "user_email_index" ON "user" ("email");`); 13 | await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_user_loweremail";`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1723135865854-SubmissionFlags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class SubmissionFlags1723135865854 implements MigrationInterface { 4 | name = 'SubmissionFlags1723135865854' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "submission" ADD "flags" jsonb NOT NULL DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "submission" DROP COLUMN "flags"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1723215750644-UserGinIndexes.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserGinIndexes1723215750644 implements MigrationInterface { 4 | name = 'UserGinIndexes1723215750644'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE INDEX IDX_user_gin_username ON "user" USING GIN ("username" gin_trgm_ops)`); 9 | await queryRunner.query(` 10 | CREATE INDEX IDX_user_gin_name ON "user" USING GIN ("name" gin_trgm_ops)`); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query(`DROP INDEX "public"."IDX_user_gin_username"`); 15 | await queryRunner.query(`DROP INDEX "public"."IDX_user_gin_name"`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1723751266939-AlertShowRecoverStreak.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AlertShowRecoverStreak1723751266939 implements MigrationInterface { 4 | name = 'AlertShowRecoverStreak1723751266939'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "alerts" ADD "showRecoverStreak" boolean NOT NULL DEFAULT false`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "alerts" DROP COLUMN "showRecoverStreak"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1724168427191-UserLanguage.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserLanguage1724168427191 implements MigrationInterface { 4 | name = 'UserLanguage1724168427191'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ADD "language" text`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "language"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1724657443097-CompanyImage.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CompanyImage1724657443097 implements MigrationInterface { 4 | name = 'CompanyImage1724657443097'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "company" ALTER COLUMN "image" SET NOT NULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "company" ALTER COLUMN "image" DROP NOT NULL`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1724925076759-UserCompanyFlags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserCompanyFlags1724925076759 implements MigrationInterface { 4 | name = 'UserCompanyFlags1724925076759'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user_company" ADD "flags" jsonb NOT NULL DEFAULT '{}'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "user_company" DROP COLUMN "flags"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1725988464760-SourceMembersIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceMembersIndex1725988464760 implements MigrationInterface { 4 | name = 'SourceMembersIndex1725988464760'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_source_flags_total_members" ON source USING BTREE (((flags->>'totalMembers')::integer))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP INDEX "IDX_source_flags_total_members"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1726691862710-User.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class User1726691862710 implements MigrationInterface { 4 | name = 'User1726691862710' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "followingEmail" boolean NOT NULL DEFAULT true`); 8 | await queryRunner.query(`ALTER TABLE "user" ADD "followNotifications" boolean NOT NULL DEFAULT true`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "followNotifications"`); 13 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "followingEmail"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1727025212763-SourceReportReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceReportReplica1727025212763 implements MigrationInterface { 4 | name = 'SourceReportReplica1727025212763'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."source_report" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."source_report" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1727627009736-UserCompanyUserIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserCompanyUserIndex1727627009736 implements MigrationInterface { 4 | name = 'UserCompanyUserIndex1727627009736'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_user_company_user_id" ON "user_company" ("userId") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP INDEX "public"."IDX_user_company_user_id"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1727848363303-SourceType.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceType1727848363303 implements MigrationInterface { 4 | name = 'SourceType1727848363303'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `INSERT INTO advanced_settings 9 | ("title", "description", "group", "options") 10 | VALUES 11 | ('Squads', 'Developer-created posts from various Squads on the platform.', 'source_types', '{"type": "squad"}')`, 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | `DELETE FROM advanced_settings WHERE "group" = 'source_types'`, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/migration/1727958325934-SourceModerationRequired.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourceModerationRequired1727958325934 4 | implements MigrationInterface 5 | { 6 | name = 'SourceModerationRequired1727958325934'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "source" ADD "moderationRequired" boolean DEFAULT false`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "source" DROP COLUMN "moderationRequired"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1728894860612-UserNotificationIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserNotificationIndex1728894860612 implements MigrationInterface { 4 | name = 'UserNotificationIndex1728894860612'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_user_notification_userid_public_readat" ON "user_notification" ("userId", "public", "readAt") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_user_notification_userid_public_readat"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1728910864611-UserStreakActionIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserStreakActionIndex1728910864611 implements MigrationInterface { 4 | name = 'UserStreakActionIndex1728910864611'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_usa_userid_type" ON "user_streak_action" ("userId", "type") `, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP INDEX "public"."IDX_usa_userid_type"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1730214092490-SettingsFlags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SettingsFlags1730214092490 implements MigrationInterface { 4 | name = 'SettingsFlags1730214092490'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "settings" ADD "flags" jsonb NOT NULL DEFAULT '{}'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "settings" DROP COLUMN "flags"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1730377577679-ContentPreferenceUserStatusTypeIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ContentPreferenceUserStatusTypeIndex1730377577679 4 | implements MigrationInterface 5 | { 6 | name = 'ContentPreferenceUserStatusTypeIndex1730377577679'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `CREATE INDEX "IDX_669e1fba44617e2a2f3939deec" ON "content_preference" ("userId", "status", "type") `, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `DROP INDEX "public"."IDX_669e1fba44617e2a2f3939deec"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1730379505031-AlertsTopReader.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class AlertsTopReader1730379505031 implements MigrationInterface { 4 | name = 'AlertsTopReader1730379505031' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "alerts" ADD "showTopReader" boolean NOT NULL DEFAULT false`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "alerts" DROP COLUMN "showTopReader"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1730390077794-ContentPreferenceSquad.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ContentPreferenceSquad1730390077794 implements MigrationInterface { 4 | name = 'ContentPreferenceSquad1730390077794'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_content_preference_flags_referralToken" ON "content_preference" ((flags->>'referralToken'))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "IDX_content_preference_flags_referralToken"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1730860362949-UserStatsIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserStatsIndex1730860362949 implements MigrationInterface { 4 | name = 'UserStatsIndex1730860362949' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_user_stats_id" ON "public"."user_stats" ("id")`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_user_stats_id"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1730978012371-SourcePostModerationReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SourcePostModerationReplica1730978012371 4 | implements MigrationInterface 5 | { 6 | name = 'SourcePostModerationReplica1730978012371'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "public"."source_post_moderation" REPLICA IDENTITY FULL`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "public"."source_post_moderation" REPLICA IDENTITY DEFAULT`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1731065998183-SourceDefaultImage.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class SourceDefaultImage1731065998183 implements MigrationInterface { 4 | name = 'SourceDefaultImage1731065998183' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "source" ALTER COLUMN "image" SET DEFAULT 'https://media.daily.dev/image/upload/s--LrHsyt2T--/f_auto/v1692632054/squad_placeholder_sfwkmj'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "source" ALTER COLUMN "image" SET DEFAULT 'https://daily-now-res.cloudinary.com/image/upload/s--LrHsyt2T--/f_auto/v1692632054/squad_placeholder_sfwkmj'`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1731398655167-UserSubscriptionFlags.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserSubscriptionFlags1731398655167 implements MigrationInterface { 4 | name = 'UserSubscriptionFlags1731398655167'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ADD "subscriptionFlags" jsonb NOT NULL DEFAULT '{}'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "user" DROP COLUMN "subscriptionFlags"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1731424255967-ContentPreferenceReplicaFull.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ContentPreferenceReplicaFull1731424255967 4 | implements MigrationInterface 5 | { 6 | name = 'ContentPreferenceReplicaFull1731424255967'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "content_preference" REPLICA IDENTITY FULL`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "content_preference" REPLICA IDENTITY DEFAULT`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1731913456006-UserSubscriptionIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserSubscriptionIndex1731913456006 implements MigrationInterface { 4 | name = 'UserSubscriptionIndex1731913456006'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "IDX_user_subflags_subscriptionid" ON "public"."user" USING gin("subscriptionFlags")`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `DROP INDEX "public"."IDX_user_subflags_subscriptionid"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1733918463705-BookmarkListLowerIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class BookmarkListLowerIndex1733918463705 implements MigrationInterface { 4 | name = 'BookmarkListLowerIndex1733918463705' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE INDEX "bookmark_list_idx_lowername_asc" ON "bookmark_list" (LOWER(name) ASC)`, 9 | ); 10 | } 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP INDEX IF EXISTS "bookmark_list_idx_lowername_asc"`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1733932616522-BookmarkUpdatedAt.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class BookmarkUpdatedAt1733932616522 implements MigrationInterface { 4 | name = 'BookmarkUpdatedAt1733932616522' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "bookmark" ADD "updatedAt" TIMESTAMP NOT NULL DEFAULT now()`); 8 | await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_bookmark_updatedAt" ON "bookmark" ("updatedAt" DESC) `); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "bookmark" DROP COLUMN "updatedAt"`); 13 | await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bookmark_updatedAt"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1734294820179-UserCioRegistered.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserCioRegistered1734294820179 implements MigrationInterface { 4 | name = 'UserCioRegistered1734294820179'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ADD "cioRegistered" boolean NOT NULL DEFAULT true`, 9 | ); 10 | await queryRunner.query( 11 | `CREATE INDEX "IDX_user_cioRegistered" ON "user" ("cioRegistered") `, 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`DROP INDEX "public"."IDX_user_cioRegistered"`); 17 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "cioRegistered"`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1736519518183-PostTranslations.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostTranslations1736519518183 implements MigrationInterface { 4 | name = 'PostTranslations1736519518183' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" ADD "translation" jsonb NOT NULL DEFAULT '{}'`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "translation"`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1736761342821-UserReportReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserReportReplica1736761342821 implements MigrationInterface { 4 | name = 'UserReportReplica1736761342821' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."user_report" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."user_report" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/migration/1737231506705-Users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class Users1737231506705 implements MigrationInterface { 4 | name = 'Users1737231506705'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ADD "bluesky" character varying(100)`, 9 | ); 10 | await queryRunner.query( 11 | `CREATE UNIQUE INDEX IF NOT EXISTS "users_bluesky_unique" ON "user" ("bluesky") `, 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`DROP INDEX IF EXISTS "public"."users_bluesky_unique"`); 17 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bluesky"`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1739337003828-UserDropProfileConfirmed.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserDropProfileConfirmed1739337003828 implements MigrationInterface { 4 | name = 'UserDropProfileConfirmed1739337003828' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "profileConfirmed"`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "user" ADD "profileConfirmed" boolean NOT NULL DEFAULT false`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1739339702842-PostDropRationPlaceholder.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostDropRationPlaceholder1739339702842 implements MigrationInterface { 4 | name = 'PostDropRationPlaceholder1739339702842' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "ratio"`); 8 | await queryRunner.query(`ALTER TABLE "post" DROP COLUMN "placeholder"`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "post" ADD "placeholder" text`); 13 | await queryRunner.query(`ALTER TABLE "post" ADD "ratio" double precision`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1739429058124-UserEmailConfirmed.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserEmailConfirmed1739429058124 implements MigrationInterface { 4 | name = 'UserEmailConfirmed1739429058124' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "emailConfirmed" boolean NOT NULL DEFAULT true`); 8 | await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "emailConfirmed" SET DEFAULT false`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "emailConfirmed"`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/migration/1739787441374-UserInfoEmailConfirmedIdx.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserInfoEmailConfirmedIdx1739787441374 implements MigrationInterface { 4 | name = 'UserInfoEmailConfirmedIdx1739787441374' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(/*sql*/`CREATE INDEX IF NOT EXISTS "IDX_user_info_email_unconfirmed" 8 | ON "public"."user" ("infoConfirmed", "emailConfirmed") 9 | WHERE 10 | ( 11 | "infoConfirmed" = FALSE 12 | OR "emailConfirmed" = FALSE 13 | ); 14 | `); 15 | } 16 | 17 | public async down(queryRunner: QueryRunner): Promise { 18 | await queryRunner.query(/*sql*/`DROP INDEX IF EXISTS "IDX_user_info_email_unconfirmed";`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/migration/1740757299661-SettingsCommentsAlgo.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class SettingsCommentsAlgo1740757299661 implements MigrationInterface { 4 | name = 'SettingsCommentsAlgo1740757299661'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "settings" ADD "sortCommentsBy" text NOT NULL DEFAULT 'oldest'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "settings" DROP COLUMN "sortCommentsBy"`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1741863600700-UserUniqueAppAccountToken.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserUniqueAppAccountToken1741863600700 implements MigrationInterface { 4 | name = 'UserUniqueAppAccountToken1741863600700' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(/* sql */`CREATE UNIQUE INDEX IF NOT EXISTS IDX_user_app_account_token_unique 8 | ON "user" (("subscriptionFlags"->>'appAccountToken')); 9 | `); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(/* sql */`DROP INDEX IF EXISTS IDX_user_app_account_token_unique;`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1741868793950-UserTransactionProcessor.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserTransactionProcessor1741868793950 4 | implements MigrationInterface 5 | { 6 | name = 'UserTransactionPaddleFlags1741868793950'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "user_transaction" ADD "processor" text NOT NULL`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "user_transaction" DROP COLUMN "processor"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1742566045271-UserCoresRole.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserCoresRole1742566045271 implements MigrationInterface { 4 | name = 'UserCoresRole1742566045271'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ADD "coresRole" smallint NOT NULL DEFAULT '3'`, 9 | ); 10 | 11 | await queryRunner.query( 12 | `CREATE INDEX "IDX_39740d60f36a9779356d6019b2" ON "user" ("coresRole") `, 13 | ); 14 | } 15 | 16 | public async down(queryRunner: QueryRunner): Promise { 17 | await queryRunner.query( 18 | `DROP INDEX "public"."IDX_39740d60f36a9779356d6019b2"`, 19 | ); 20 | 21 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "coresRole"`); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/migration/1743501278867-UserTransactionProviderId.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserTransactionProviderId1743501278867 4 | implements MigrationInterface 5 | { 6 | name = 'UserTransactionProviderId1743501278867'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query(/* sql */ `CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_transaction_flags_providerId" 10 | ON "user_transaction" (("flags"->>'providerId')); 11 | `); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | /* sql */ `DROP INDEX IF EXISTS "IDX_user_transaction_flags_providerId";`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1743605489726-ExperimentVariant.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class ExperimentVariant1743605489726 implements MigrationInterface { 4 | name = 'ExperimentVariant1743605489726'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `CREATE TABLE "experiment_variant" ("feature" text NOT NULL, "variant" text NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "value" text, CONSTRAINT "PK_7ad1025f0a1a8674a557253db8e" PRIMARY KEY ("feature", "variant"))`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`DROP TABLE "experiment_variant"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1743776947033-UserTransactionValueFee.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserTransactionValueFee1743776947033 4 | implements MigrationInterface 5 | { 6 | name = 'UserTransactionValueFee1743776947033'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `ALTER TABLE "user_transaction" ADD COLUMN IF NOT EXISTS "valueIncFees" integer NOT NULL`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "user_transaction" DROP COLUMN IF EXISTS "valueIncFees"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1744015053751-UserNotificationReplica.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserNotificationReplica1744015053751 4 | implements MigrationInterface 5 | { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "public"."user_transaction" REPLICA IDENTITY FULL`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "public"."user_transaction" REPLICA IDENTITY DEFAULT`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1744027556819-UserAwardEmail.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class UserAwardEmail1744027556819 implements MigrationInterface { 4 | name = 'UserAwardEmail1744027556819' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "user" ADD "awardEmail" boolean NOT NULL DEFAULT true`); 8 | await queryRunner.query(`ALTER TABLE "user" ADD "awardNotifications" boolean NOT NULL DEFAULT true`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "awardNotifications"`); 13 | await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "awardEmail"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1744110320719-CoresRoleDefaultNone.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CoresRoleDefaultNone1744110320719 implements MigrationInterface { 4 | name = 'CoresRoleDefaultNone1744110320719'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "user" ALTER COLUMN "coresRole" SET DEFAULT '0'`, 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | `ALTER TABLE "user" ALTER COLUMN "coresRole" SET DEFAULT '3'`, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1745511937419-PostSourceIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostSourceIndex1745511937419 implements MigrationInterface { 4 | name = 'PostSourceIndex1745511937419' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(/* sql */` 8 | CREATE INDEX IF NOT EXISTS "IDX_post_source_id_pinned_at_null_pinned_at_created_at" ON "post" ( 9 | "sourceId", ("pinnedAt" IS NULL), "pinnedAt" DESC, "createdAt" DESC 10 | ); 11 | `); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query(/* sql */`DROP INDEX IF EXISTS "IDX_post_source_id_pinned_at_null_pinned_at_created_at";`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/migration/1745812404446-PostReportLength.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class PostReportLength1745812404446 implements MigrationInterface { 4 | name = 'PostReportLength1745812404446' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "post_report" ALTER COLUMN "reason" TYPE character varying(30)`); 8 | await queryRunner.query(`ALTER TABLE "post_report" ALTER COLUMN "reason" SET NOT NULL`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "post_report" ALTER COLUMN "reason" TYPE character varying(12)`); 13 | await queryRunner.query(`ALTER TABLE "post_report" ALTER COLUMN "reason" SET NOT NULL`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/migration/1746779086984-DropRoleContentPreference.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class DropRoleContentPreference1746779086984 implements MigrationInterface { 4 | name = 'DropRoleContentPreference1746779086984' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "content_preference" DROP COLUMN "role"`); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query(`ALTER TABLE "content_preference" ADD "role" text DEFAULT 'member'`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/migration/1747300279523-StaleUserTransactionIndex.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class StaleUserTransactionIndex1747300279523 4 | implements MigrationInterface 5 | { 6 | name = 'StaleUserTransactionIndex1747300279523'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `CREATE INDEX IF NOT EXISTS "idx_user_transaction_status_updated_at" ON user_transaction ("status", "updatedAt")`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `DROP INDEX IF EXISTS "idx_user_transaction_status_updated_at"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1747309505414-UserTransactionValueDesc.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class UserTransactionValueDesc1747309505414 4 | implements MigrationInterface 5 | { 6 | name = 'UserTransactionValueDesc1747309505414'; 7 | 8 | public async up(queryRunner: QueryRunner): Promise { 9 | await queryRunner.query( 10 | `CREATE INDEX IF NOT EXISTS "idx_user_transaction_value_desc" ON user_transaction ("value" DESC)`, 11 | ); 12 | } 13 | 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `DROP INDEX IF EXISTS "idx_user_transaction_value_desc"`, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1748371994832-ExperimentVariationType.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class ExperimentVariationType1748371994832 implements MigrationInterface { 4 | name = 'ExperimentVariationType1748371994832' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(`ALTER TABLE "experiment_variant" ADD "type" text`); 8 | await queryRunner.query(`UPDATE "experiment_variant" SET "type" = 'productPricing' WHERE "type" IS NULL`); 9 | await queryRunner.query(`ALTER TABLE "experiment_variant" ALTER COLUMN "type" SET NOT NULL`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query(`ALTER TABLE "experiment_variant" DROP COLUMN "type"`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/notifications/icons.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationIcon { 2 | DailyDev = 'DailyDev', 3 | CommunityPicks = 'CommunityPicks', 4 | Comment = 'Comment', 5 | Upvote = 'Upvote', 6 | Bell = 'Bell', 7 | View = 'View', 8 | Block = 'Block', 9 | User = 'User', 10 | Star = 'Star', 11 | DevCard = 'DevCard', 12 | BookmarkReminder = 'BookmarkReminder', 13 | Timer = 'Timer', 14 | Streak = 'Streak', 15 | TopReaderBadge = 'TopReaderBadge', 16 | Core = 'Core', 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/integrations/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance } from 'fastify'; 2 | import slack from './slack'; 3 | 4 | export default async function (fastify: FastifyInstance): Promise { 5 | fastify.register(slack, { prefix: '/slack' }); 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/notifications.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance } from 'fastify'; 2 | import { injectGraphql } from '../compatibility/utils'; 3 | 4 | export default async function (fastify: FastifyInstance): Promise { 5 | fastify.get('/', async (req, res) => { 6 | const query = `{ 7 | unreadNotificationsCount 8 | }`; 9 | 10 | return injectGraphql(fastify, { query }, (obj) => obj['data'], req, res); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/telemetry/index.ts: -------------------------------------------------------------------------------- 1 | export * from './opentelemetry'; 2 | export * from './metrics'; 3 | export * from './common'; 4 | -------------------------------------------------------------------------------- /src/temporal/client.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@temporalio/client'; 2 | import { Connection as TemporalConnection } from '@temporalio/client/lib/connection'; 3 | import { getTemporalServerOptions } from './config'; 4 | 5 | let client: Client; 6 | 7 | export const getTemporalClient = async (): Promise => { 8 | if (client) { 9 | return client; 10 | } 11 | 12 | const { namespace, tls, address } = getTemporalServerOptions(); 13 | const connection = await TemporalConnection.connect({ tls, address }); 14 | 15 | client = new Client({ connection, namespace }); 16 | 17 | return client; 18 | }; 19 | -------------------------------------------------------------------------------- /src/temporal/local.ts: -------------------------------------------------------------------------------- 1 | import { run } from './notifications'; 2 | 3 | run() 4 | .then(() => { 5 | console.log('registered worker'); 6 | }) 7 | .catch((err) => { 8 | console.log('error registering worker'); 9 | console.error(err); 10 | process.exit(1); 11 | }); 12 | -------------------------------------------------------------------------------- /src/temporal/notifications/index.ts: -------------------------------------------------------------------------------- 1 | import { Worker } from '@temporalio/worker'; 2 | import { createActivities } from './activities'; 3 | import createOrGetConnection from '../../db'; 4 | import { WorkflowQueue } from '../common'; 5 | import { getTemporalWorkerConnection } from '../worker'; 6 | import { TEMPORAL_NAMESPACE } from '../config'; 7 | 8 | export async function run() { 9 | const connection = await getTemporalWorkerConnection(); 10 | const dbCon = await createOrGetConnection(); 11 | const worker = await Worker.create({ 12 | connection, 13 | namespace: TEMPORAL_NAMESPACE, 14 | workflowsPath: require.resolve('./workflows'), 15 | taskQueue: WorkflowQueue.Notification, 16 | activities: createActivities({ con: dbCon }), 17 | }); 18 | 19 | await worker.run(); 20 | } 21 | 22 | export default run; 23 | -------------------------------------------------------------------------------- /src/temporal/notifications/workflows.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import { BookmarkActivities } from './activities'; 3 | 4 | export interface BookmarkReminderParams { 5 | userId: string; 6 | postId: string; 7 | } 8 | 9 | export async function bookmarkReminderWorkflow({ 10 | userId, 11 | postId, 12 | }: BookmarkReminderParams): Promise { 13 | const { validateBookmark, sendBookmarkReminder } = 14 | proxyActivities({ scheduleToCloseTimeout: '15s' }); // the amount of time the process is willing to wait for the activity to complete 15 | 16 | const isValid = await validateBookmark({ userId, postId }); 17 | 18 | if (!isValid) { 19 | return; 20 | } 21 | 22 | await sendBookmarkReminder({ userId, postId }); 23 | } 24 | -------------------------------------------------------------------------------- /src/temporal/worker.ts: -------------------------------------------------------------------------------- 1 | import { NativeConnection } from '@temporalio/worker'; 2 | import { getTemporalServerOptions } from './config'; 3 | 4 | let connection: NativeConnection; 5 | 6 | export const getTemporalWorkerConnection = async () => { 7 | if (connection) { 8 | return connection; 9 | } 10 | 11 | const { tls, address } = getTemporalServerOptions(); 12 | 13 | connection = await NativeConnection.connect({ address, tls }); 14 | 15 | return connection; 16 | }; 17 | -------------------------------------------------------------------------------- /src/workers/bannerDeleted.ts: -------------------------------------------------------------------------------- 1 | import { Worker } from './worker'; 2 | import { setRedisObject } from '../redis'; 3 | import { REDIS_BANNER_KEY } from '../config'; 4 | 5 | const worker: Worker = { 6 | subscription: 'api.banner-deleted', 7 | handler: async (message, con, logger): Promise => { 8 | try { 9 | await setRedisObject(REDIS_BANNER_KEY, 'false'); 10 | } catch (err) { 11 | logger.error( 12 | { messageId: message.messageId, err }, 13 | 'failed to remove redis cache for banner', 14 | ); 15 | } 16 | }, 17 | }; 18 | 19 | export default worker; 20 | -------------------------------------------------------------------------------- /src/workers/commentEditedImages.ts: -------------------------------------------------------------------------------- 1 | import { ContentImageUsedByType } from '../entity'; 2 | import { Worker } from './worker'; 3 | import { generateEditImagesHandler } from './generators'; 4 | 5 | export const commentEditedWorker: Worker = { 6 | subscription: 'api.comment-edited-images', 7 | handler: generateEditImagesHandler('comment', ContentImageUsedByType.Comment), 8 | }; 9 | 10 | export const commentDeletedWorker: Worker = { 11 | subscription: 'api.comment-deleted-images', 12 | handler: generateEditImagesHandler( 13 | 'comment', 14 | ContentImageUsedByType.Comment, 15 | { shouldClearOnly: true }, 16 | ), 17 | }; 18 | -------------------------------------------------------------------------------- /src/workers/digestDeadLetterLog.ts: -------------------------------------------------------------------------------- 1 | import { messageToJson, Worker } from './worker'; 2 | 3 | type Data = unknown; 4 | 5 | const worker: Worker = { 6 | subscription: 'api.personalized-digest-email-dead-letter-log', 7 | handler: async (message, con, logger) => { 8 | const data = messageToJson(message); 9 | 10 | logger.info( 11 | { 12 | data, 13 | messageId: message.messageId, 14 | }, 15 | 'dead message, hel awaits', 16 | ); 17 | }, 18 | }; 19 | 20 | export default worker; 21 | -------------------------------------------------------------------------------- /src/workers/generators/generateNewImagesHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContentImageUsedByType, 3 | updateUsedImagesInContent, 4 | } from '../../entity'; 5 | import { DataSource } from 'typeorm'; 6 | 7 | export const generateNewImagesHandler = async ( 8 | data: { id: string; contentHtml: string }, 9 | type: ContentImageUsedByType, 10 | con: DataSource, 11 | ) => { 12 | if (!data || !data?.id || !data?.contentHtml) return; 13 | 14 | await updateUsedImagesInContent(con, type, data.id, data.contentHtml); 15 | }; 16 | -------------------------------------------------------------------------------- /src/workers/generators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generateNewImagesHandler'; 2 | export * from './generateEditImagesHandler'; 3 | -------------------------------------------------------------------------------- /src/workers/notifications/articleNewCommentCommentCommented.ts: -------------------------------------------------------------------------------- 1 | import { messageToJson } from '../worker'; 2 | import { NotificationWorker } from './worker'; 3 | import { articleNewCommentHandler } from './utils'; 4 | 5 | export interface Data { 6 | userId: string; 7 | childCommentId: string; 8 | postId: string; 9 | } 10 | 11 | const worker: NotificationWorker = { 12 | subscription: 'api.article-new-comment-notification.comment-commented', 13 | handler: (message, con) => { 14 | const data: Data = messageToJson(message); 15 | return articleNewCommentHandler(con, data.childCommentId); 16 | }, 17 | }; 18 | 19 | export default worker; 20 | -------------------------------------------------------------------------------- /src/workers/notifications/articleNewCommentPostCommented.ts: -------------------------------------------------------------------------------- 1 | import { messageToJson } from '../worker'; 2 | import { NotificationWorker } from './worker'; 3 | import { articleNewCommentHandler } from './utils'; 4 | 5 | export interface Data { 6 | userId: string; 7 | commentId: string; 8 | postId: string; 9 | } 10 | 11 | const worker: NotificationWorker = { 12 | subscription: 'api.article-new-comment-notification.post-commented', 13 | handler: (message, con) => { 14 | const data: Data = messageToJson(message); 15 | return articleNewCommentHandler(con, data.commentId); 16 | }, 17 | }; 18 | 19 | export default worker; 20 | -------------------------------------------------------------------------------- /src/workers/notifications/communityPicksGranted.ts: -------------------------------------------------------------------------------- 1 | import { messageToJson } from '../worker'; 2 | import { NotificationBaseContext } from '../../notifications'; 3 | import { NotificationType } from '../../notifications/common'; 4 | import { NotificationWorker } from './worker'; 5 | 6 | interface Data { 7 | userId: string; 8 | } 9 | 10 | const worker: NotificationWorker = { 11 | subscription: 'api.community-picks-granted-notification', 12 | handler: async (message) => { 13 | const data: Data = messageToJson(message); 14 | const ctx: NotificationBaseContext = { 15 | userIds: [data.userId], 16 | }; 17 | return [{ type: NotificationType.CommunityPicksGranted, ctx }]; 18 | }, 19 | }; 20 | 21 | export default worker; 22 | -------------------------------------------------------------------------------- /src/workers/postEditedFreeformImages.ts: -------------------------------------------------------------------------------- 1 | import { ContentImageUsedByType } from '../entity'; 2 | import { Worker } from './worker'; 3 | import { generateEditImagesHandler } from './generators'; 4 | 5 | const worker: Worker = { 6 | subscription: 'api.post-edited-freeform-images', 7 | handler: generateEditImagesHandler('post', ContentImageUsedByType.Post), 8 | }; 9 | 10 | export default worker; 11 | -------------------------------------------------------------------------------- /src/workers/postFreeformImages.ts: -------------------------------------------------------------------------------- 1 | import { ContentImageUsedByType, FreeformPost } from '../entity'; 2 | import { messageToJson, Worker } from './worker'; 3 | import { generateNewImagesHandler } from './generators'; 4 | import { ChangeObject } from '../types'; 5 | 6 | const worker: Worker = { 7 | subscription: 'api.post-freeform-images', 8 | handler: async (message, con): Promise => { 9 | const data: { 10 | post: ChangeObject; 11 | } = messageToJson(message); 12 | await generateNewImagesHandler( 13 | { id: data.post?.id, contentHtml: data.post?.contentHtml }, 14 | ContentImageUsedByType.Post, 15 | con, 16 | ); 17 | }, 18 | }; 19 | 20 | export default worker; 21 | -------------------------------------------------------------------------------- /src/workers/transactionBalanceLog.ts: -------------------------------------------------------------------------------- 1 | import { TransferResponse } from '@dailydotdev/schema'; 2 | import type { TypedWorker } from './worker'; 3 | 4 | export const transactionBalanceLogWorker: TypedWorker<'njord.v1.balance-log'> = 5 | { 6 | subscription: 'api.transaction-balance-log', 7 | handler: async (message, con, logger): Promise => { 8 | const { data } = message; 9 | 10 | logger.info({ data }, 'transaction log'); 11 | }, 12 | parseMessage: (message) => { 13 | return TransferResponse.fromBinary(message.data); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/workers/userDeletedCio.ts: -------------------------------------------------------------------------------- 1 | import { TypedWorker } from './worker'; 2 | import { cio } from '../cio'; 3 | 4 | const worker: TypedWorker<'user-deleted'> = { 5 | subscription: 'api.user-deleted-cio', 6 | handler: async (message, _, log) => { 7 | if (!process.env.CIO_SITE_ID) { 8 | return; 9 | } 10 | 11 | await cio.destroy(message.data.id); 12 | log.info({ userId: message.data.id }, 'deleted user from customerio'); 13 | }, 14 | }; 15 | 16 | export default worker; 17 | -------------------------------------------------------------------------------- /src/workers/userReadmeImages.ts: -------------------------------------------------------------------------------- 1 | import { ContentImageUsedByType } from '../entity'; 2 | import { Worker } from './worker'; 3 | import { generateEditImagesHandler } from './generators'; 4 | 5 | const worker: Worker = { 6 | subscription: 'api.user-readme-images', 7 | handler: generateEditImagesHandler( 8 | 'user', 9 | ContentImageUsedByType.User, 10 | {}, 11 | 'readmeHtml', 12 | ), 13 | }; 14 | 15 | export default worker; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "outDir": "build", 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "strictPropertyInitialization": false, 13 | "skipLibCheck": true 14 | }, 15 | "exclude": [ 16 | "__tests__" 17 | ], 18 | "ts-node": { 19 | "swc": true 20 | }, 21 | "files": ["node_modules/jest-extended/types/index.d.ts"], 22 | "include": [ 23 | "bin/**/*.ts", 24 | "src/**/*.ts" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------