├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── NuGet.config ├── PopForums.sln ├── PopForums.sln.DotSettings ├── README.md ├── SECURITY.md ├── docs ├── _config.yml ├── azurekitlibrary.md ├── customization.md ├── elastickitlibrary.md ├── externalloginconfig.md ├── faq.md ├── features.md ├── index.md ├── multitenant.md ├── oauthonly.md ├── scoringgame.md ├── starthere.md └── versionhistory.md ├── resources ├── bootstrap icons │ ├── arrow-counterclockwise.svg │ ├── arrow-down-circle.svg │ ├── arrow-left-circle.svg │ ├── arrow-right-circle.svg │ ├── arrow-up-circle.svg │ ├── bell-fill.svg │ ├── bell-slash.svg │ ├── bell.svg │ ├── box-arrow-up-right.svg │ ├── card-list.svg │ ├── chat-dots.svg │ ├── chat.svg │ ├── check-circle-fill.svg │ ├── check-circle.svg │ ├── clock-history.svg │ ├── cloud-arrow-up-fill.svg │ ├── cloud-arrow-up.svg │ ├── dash-circle.svg │ ├── envelope.svg │ ├── exclamation-circle-fill.svg │ ├── exclamation-octagon-fill.svg │ ├── eye-fill.svg │ ├── eye.svg │ ├── facebook.svg │ ├── file-earmark-text-fill.svg │ ├── file-earmark-text.svg │ ├── file-text-fill.svg │ ├── file-text.svg │ ├── gear-fill.svg │ ├── google.svg │ ├── hand-thumbs-down-fill.svg │ ├── hand-thumbs-down.svg │ ├── hand-thumbs-up-fill.svg │ ├── hand-thumbs-up.svg │ ├── heart-fill.svg │ ├── heart.svg │ ├── house-door.svg │ ├── image.svg │ ├── images.svg │ ├── info-circle-fill.svg │ ├── info-circle.svg │ ├── instagram.svg │ ├── link.svg │ ├── lock-fill.svg │ ├── lock.svg │ ├── microsoft.svg │ ├── paperclip.svg │ ├── pencil-square.svg │ ├── people-fill.svg │ ├── people.svg │ ├── person-fill.svg │ ├── person.svg │ ├── pin-angle-fill.svg │ ├── pin-angle.svg │ ├── plus-square-fill.svg │ ├── plus-square.svg │ ├── question-circle-fill.svg │ ├── question-circle.svg │ ├── quote.svg │ ├── recycle.svg │ ├── reply-fill.svg │ ├── reply.svg │ ├── rss-fill.svg │ ├── search.svg │ ├── share-fill.svg │ ├── skip-backward-fill.svg │ ├── skip-end-fill.svg │ ├── skip-forward-fill.svg │ ├── skip-start-fill.svg │ ├── star-fill.svg │ ├── star.svg │ ├── trash3-fill.svg │ ├── trophy-fill.svg │ ├── trophy.svg │ ├── twitter.svg │ ├── unlock-fill.svg │ └── youtube.svg └── icomoon-v2.0.zip └── src ├── .editorconfig ├── PopForums.AzureKit.Functions ├── .gitignore ├── AwardCalculationProcessor.cs ├── BrokerSink.cs ├── CacheHelper.cs ├── CloseAgedTopicsProcessor.cs ├── EmailProcessor.cs ├── NotificationTunnel.cs ├── PopForums.AzureKit.Functions.csproj ├── PostImageCleanupProcessor.cs ├── Program.cs ├── SearchIndexProcessor.cs ├── SubscribeNotificationProcessor.cs ├── UserSessionProcessor.cs ├── host.json └── local.settings.json ├── PopForums.AzureKit ├── Logging │ └── ErrorLogRepository.cs ├── PopForums.AzureKit.csproj ├── PostImage │ └── PostImageRepository.cs ├── Queue │ ├── AwardCalculationQueueRepository.cs │ ├── EmailQueueRepository.cs │ ├── SearchIndexQueueRepository.cs │ └── SubscribeNotificationRepository.cs ├── Redis │ ├── CacheHelper.cs │ ├── CacheTelemetrySink.cs │ └── ICacheTelemetry.cs ├── Search │ ├── SearchIndexSubsystem.cs │ ├── SearchRepository.cs │ └── SearchTopic.cs └── ServiceCollectionExtensions.cs ├── PopForums.ElasticKit ├── PopForums.ElasticKit.csproj ├── Search │ ├── ElasticSearchClientWrapper.cs │ ├── SearchIndexSubsystem.cs │ ├── SearchRepository.cs │ └── SearchTopic.cs └── ServiceCollectionExtensions.cs ├── PopForums.Mvc ├── Areas │ └── Forums │ │ ├── Authentication │ │ ├── PopForumsAuthenticationDefaults.cs │ │ ├── PopForumsAuthenticationIgnoreAttribute.cs │ │ └── PopForumsAuthenticationMiddleware.cs │ │ ├── Authorization │ │ ├── OAuthOnlyForbidAttribute.cs │ │ ├── PopForumsPrivateForumsFilter.cs │ │ └── PopForumsUserAttribute.cs │ │ ├── BackgroundJobs │ │ ├── AwardCalculatorJob.cs │ │ ├── CloseAgedTopicsJob.cs │ │ ├── EmailJob.cs │ │ ├── PostImageCleanupJob.cs │ │ ├── SearchIndexJob.cs │ │ ├── SubscribeNotificationJob.cs │ │ └── UserSessionJob.cs │ │ ├── Controllers │ │ ├── AccountController.cs │ │ ├── AdminApiController.cs │ │ ├── AdminController.cs │ │ ├── ApiController.cs │ │ ├── FavoritesController.cs │ │ ├── ForumController.cs │ │ ├── HomeController.cs │ │ ├── IdentityController.cs │ │ ├── ImageController.cs │ │ ├── ModeratorController.cs │ │ ├── PrivateMessagesController.cs │ │ ├── ResourcesController.cs │ │ ├── SearchController.cs │ │ ├── SetupController.cs │ │ ├── SitemapController.cs │ │ └── SubscriptionController.cs │ │ ├── Extensions │ │ ├── ApplicationBuilders.cs │ │ ├── AuthorizationOptionsExtensions.cs │ │ ├── Logger.cs │ │ ├── LoggerFactories.cs │ │ ├── LoggerProvider.cs │ │ ├── ServiceCollections.cs │ │ └── WebApplications.cs │ │ ├── ForumRouteConstraint.cs │ │ ├── Messaging │ │ ├── Broker.cs │ │ ├── PopForumsHub.cs │ │ └── PopForumsUserIdProvider.cs │ │ ├── Models │ │ ├── AwardConditionDeleteContainer.cs │ │ ├── EmailUsersContainer.cs │ │ ├── ExternalLoginState.cs │ │ ├── ExternalLoginTypeMetadata.cs │ │ ├── IPHistoryQuery.cs │ │ ├── ManualEvent.cs │ │ ├── SecurityLogQuery.cs │ │ ├── UserEditPhoto.cs │ │ ├── UserEditWithFiles.cs │ │ └── UserState.cs │ │ ├── Services │ │ ├── ExternalLoginRoutingService.cs │ │ ├── ExternalLoginTempService.cs │ │ ├── ForumAdapterFactory.cs │ │ ├── IForumAdapter.cs │ │ ├── OAuthOnlyService.cs │ │ ├── TopicViewCountService.cs │ │ ├── UserRetrievalShim.cs │ │ └── UserStateComposer.cs │ │ ├── TagHelpers │ │ ├── ForumReadIndicatorTagHelper.cs │ │ ├── PMReadIndicatorTagHelper.cs │ │ ├── PagerLinksTagHelper.cs │ │ ├── TopicReadIndicatorTagHelper.cs │ │ └── ValidationClassTagHelper.cs │ │ ├── ViewComponents │ │ ├── UserNavigationViewComponent.cs │ │ └── UserStateViewComponent.cs │ │ └── Views │ │ ├── Account │ │ ├── AccountCreated.cshtml │ │ ├── Create.cshtml │ │ ├── EditAccountNoUser.cshtml │ │ ├── EditProfile.cshtml │ │ ├── ExternalLogins.cshtml │ │ ├── Forgot.cshtml │ │ ├── Login.cshtml │ │ ├── ManagePhotos.cshtml │ │ ├── MiniProfile.cshtml │ │ ├── MiniUserNotFound.cshtml │ │ ├── OAuthLogin.cshtml │ │ ├── Posts.cshtml │ │ ├── ResetPassword.cshtml │ │ ├── ResetPasswordSuccess.cshtml │ │ ├── Security.cshtml │ │ ├── Unsubscribe.cshtml │ │ ├── UnsubscribeFailure.cshtml │ │ ├── Verify.cshtml │ │ ├── VerifyFail.cshtml │ │ └── ViewProfile.cshtml │ │ ├── Admin │ │ └── App.cshtml │ │ ├── Favorites │ │ └── Topics.cshtml │ │ ├── Forum │ │ ├── Edit.cshtml │ │ ├── Index.cshtml │ │ ├── IndexQA.cshtml │ │ ├── ModeratorPanel.cshtml │ │ ├── NewComment.cshtml │ │ ├── NewReply.cshtml │ │ ├── NewTopic.cshtml │ │ ├── PostItem.cshtml │ │ ├── QAPost.cshtml │ │ ├── Recent.cshtml │ │ ├── Topic.cshtml │ │ ├── TopicPage.cshtml │ │ ├── TopicQA.cshtml │ │ └── Voters.cshtml │ │ ├── Home │ │ └── Index.cshtml │ │ ├── Identity │ │ ├── ExternalError.cshtml │ │ └── ExternalLoginCallback.cshtml │ │ ├── Moderator │ │ ├── PostModerationLog.cshtml │ │ └── TopicModerationLog.cshtml │ │ ├── PrivateMessages │ │ ├── Archive.cshtml │ │ ├── Create.cshtml │ │ ├── Index.cshtml │ │ └── View.cshtml │ │ ├── Search │ │ └── Index.cshtml │ │ ├── Setup │ │ ├── Exception.cshtml │ │ ├── Index.cshtml │ │ ├── NoConnection.cshtml │ │ └── Success.cshtml │ │ ├── Shared │ │ ├── Components │ │ │ ├── UserNavigation │ │ │ │ └── Default.cshtml │ │ │ └── UserState │ │ │ │ └── Default.cshtml │ │ ├── Forbidden.cshtml │ │ ├── NotFound.cshtml │ │ └── PopForumsMaster.cshtml │ │ ├── Subscription │ │ └── Topics.cshtml │ │ └── _ViewImports.cshtml ├── Client │ ├── Components │ │ ├── AnswerButton.ts │ │ ├── CommentButton.ts │ │ ├── FavoriteButton.ts │ │ ├── FormattedTime.ts │ │ ├── FullText.ts │ │ ├── HomeUpdater.ts │ │ ├── LoginForm.ts │ │ ├── MorePostsBeforeReplyButton.ts │ │ ├── MorePostsButton.ts │ │ ├── NotificationItem.ts │ │ ├── NotificationList.ts │ │ ├── NotificationMarkAllButton.ts │ │ ├── NotificationToggle.ts │ │ ├── PMCount.ts │ │ ├── PMForm.ts │ │ ├── PostMiniProfile.ts │ │ ├── PostModerationLogButton.ts │ │ ├── PreviewButton.ts │ │ ├── PreviousPostsButton.ts │ │ ├── QuoteButton.ts │ │ ├── ReplyButton.ts │ │ ├── ReplyForm.ts │ │ ├── SearchNavForm.ts │ │ ├── SubscribeButton.ts │ │ ├── TopicButton.ts │ │ ├── TopicForm.ts │ │ ├── TopicModerationLogButton.ts │ │ └── VoteCount.ts │ ├── Declarations.ts │ ├── ElementBase.ts │ ├── Models │ │ ├── Notification.ts │ │ ├── PrivateMessage.ts │ │ └── PrivateMessageUser.ts │ ├── Services │ │ ├── LocalizationService.ts │ │ ├── MessagingService.ts │ │ └── NotificationService.ts │ ├── State │ │ ├── ForumState.ts │ │ ├── Localizations.ts │ │ ├── PrivateMessageState.ts │ │ ├── TopicState.ts │ │ └── UserState.ts │ ├── StateBase.ts │ ├── WatchPropertyAttribute.ts │ └── tsconfig.json ├── Global.cs ├── PopForums.Mvc.csproj ├── gulpfile.js ├── package.json └── wwwroot │ ├── Admin.js │ ├── Editor.css │ ├── Fonts │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff │ └── PopForums.css ├── PopForums.Sql ├── CacheHelper.cs ├── Extensions.cs ├── Global.cs ├── ISqlObjectFactory.cs ├── JsonElementTypeHandler.cs ├── PopForums.Sql.csproj ├── PopForums.sql ├── PopForums13to14.sql ├── PopForums14to15.sql ├── PopForums15to16.sql ├── PopForums16to21.sql ├── PopForums19to20.sql ├── PopForums20to21.sql ├── Repositories │ ├── AwardCalculationQueueRepository.cs │ ├── AwardConditionRepository.cs │ ├── AwardDefinitionRepository.cs │ ├── BanRepository.cs │ ├── CategoryRepository.cs │ ├── EmailQueueRepository.cs │ ├── ErrorLogRepository.cs │ ├── EventDefinitionRepository.cs │ ├── ExternalUserAssociationRepository.cs │ ├── FavoriteTopicsRepository.cs │ ├── FeedRepository.cs │ ├── ForumRepository.cs │ ├── LastReadRepository.cs │ ├── ModerationLogRepository.cs │ ├── NotificationRepository.cs │ ├── PointLedgerRepository.cs │ ├── PostImageRepository.cs │ ├── PostImageTempRepository.cs │ ├── PostRepository.cs │ ├── PrivateMessageRepository.cs │ ├── ProfileRepository.cs │ ├── QueuedEmailMessageRepository.cs │ ├── RoleRepository.cs │ ├── SearchIndexQueueRepository.cs │ ├── SearchRepository.cs │ ├── SecurityLogRepository.cs │ ├── ServiceHeartbeatRepository.cs │ ├── SettingsRepository.cs │ ├── SetupRepository.cs │ ├── SubscribeNotificationRepository.cs │ ├── SubscribedTopicsRepository.cs │ ├── TopicRepository.cs │ ├── TopicViewLogRepository.cs │ ├── UserAvatarRepository.cs │ ├── UserAwardRepository.cs │ ├── UserImageRepository.cs │ ├── UserRepository.cs │ └── UserSessionRepository.cs ├── SqlObjectFactory.cs └── StreamResponse.cs ├── PopForums.Test ├── Composers │ ├── ForumStateComposerTests.cs │ ├── PrivateMessageStateComposerTests.cs │ └── TopicStateComposerTests.cs ├── Configuration │ └── SettingsTests.cs ├── Email │ ├── EmailWorkerTests.cs │ └── NewAccountMailerTests.cs ├── Extensions │ └── StringTests.cs ├── ExternalLogin │ └── ExternalUserAssociationManagerTests.cs ├── Global.cs ├── Messaging │ ├── NotificationAdapterTests.cs │ └── NotificationManagerTests.cs ├── Models │ ├── ForumHomeContainerTests.cs │ ├── UserEditSecurityTests.cs │ └── UserTest.cs ├── Mvc │ ├── Authorization │ │ └── PopForumsPrivateForumsFilterTests.cs │ ├── Controllers │ │ ├── AccountControllerTests.cs │ │ └── AdminApiControllerTests.cs │ └── Services │ │ └── OAuthOnlyServiceTests.cs ├── PopForums.Test.csproj ├── ScoringGame │ ├── AwardCalculatorTests.cs │ ├── AwardCalculatorWorkerTests.cs │ ├── AwardDefinitionServiceTests.cs │ ├── EventDefintionServiceTests.cs │ ├── EventPublisherTests.cs │ ├── FeedServiceTests.cs │ └── UserAwardServiceTests.cs └── Services │ ├── BanServiceTests.cs │ ├── CategoryServiceTests.cs │ ├── ClaimsToRoleMapperTests.cs │ ├── CloseAgedTopicsWorkerTests.cs │ ├── FavoriteTopicServiceTests.cs │ ├── ForumPermissionServiceTests.cs │ ├── ForumServiceTests.cs │ ├── ImageServiceTests.cs │ ├── LastReadServiceTests.cs │ ├── PostImageCleanupWorkerTests.cs │ ├── PostImageServiceTests.cs │ ├── PostMasterServiceTests.cs │ ├── PostServiceTests.cs │ ├── PrivateMessageServiceTests.cs │ ├── ProfileServiceTests.cs │ ├── QueuedEmailServiceTests.cs │ ├── SearchIndexWorkerTests.cs │ ├── SearchServiceTests.cs │ ├── SecurityLogServiceTests.cs │ ├── SetupServiceTests.cs │ ├── SitemapServiceTests.cs │ ├── SubscribeNotificationWorkerTests.cs │ ├── SubscribedTopicsServiceTests.cs │ ├── TextParsingServiceCleanForumCodeTests.cs │ ├── TextParsingServiceClientHtmlToForumCodeTests.cs │ ├── TextParsingServiceForumCodeToHtmlTests.cs │ ├── TextParsingServiceOtherTests.cs │ ├── TopicServiceTests.cs │ ├── TopicViewLogServiceTests.cs │ ├── UserEmailReconcilerTests.cs │ ├── UserNameReconcilerTests.cs │ ├── UserServiceTests.cs │ ├── UserSessionServiceTests.cs │ └── UserSessionWorkerTests.cs ├── PopForums.Web ├── Controllers │ └── HomeController.cs ├── PopForums.Web.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── Views │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.json └── wwwroot │ └── favicon.ico └── PopForums ├── Composers ├── ForumStateComposer.cs ├── PrivateMessageStateComposer.cs ├── ResourceComposer.cs └── TopicStateComposer.cs ├── Configuration ├── Config.cs ├── ConfigContainer.cs ├── ConfigLoader.cs ├── ErrorLog.cs ├── ErrorLogException.cs ├── ErrorSeverity.cs ├── ICacheHelper.cs ├── Settings.cs └── SettingsManager.cs ├── Email ├── EmailQueuePayload.cs ├── EmailQueuePayloadType.cs ├── EmailWorker.cs ├── ForgotPasswordMailer.cs ├── MailingListComposer.cs ├── NewAccountMailer.cs ├── SmtpStatusCode.cs └── SmtpWrapper.cs ├── Extensions ├── ServiceCollections.cs ├── Streams.cs ├── Strings.cs └── Users.cs ├── ExternalLogin ├── ExternalAuthenticationResult.cs ├── ExternalLoginInfo.cs ├── ExternalUserAssociation.cs ├── ExternalUserAssociationManager.cs └── ExternalUserAssociationMatchResult.cs ├── Feeds └── FeedService.cs ├── Global.cs ├── Messaging ├── IBroker.cs ├── Models │ ├── AwardData.cs │ ├── AwardPayload.cs │ ├── QuestionData.cs │ ├── ReplyData.cs │ ├── ReplyPayload.cs │ └── VoteData.cs ├── Notification.cs ├── NotificationAdapter.cs ├── NotificationManager.cs ├── NotificationTunnel.cs └── NotificationType.cs ├── Models ├── AwardCalculationPayload.cs ├── BasicJsonMessage.cs ├── BasicServiceResponse.cs ├── CategorizedForumContainer.cs ├── Category.cs ├── CategoryContainerWithForums.cs ├── ClientPrivateMessagePost.cs ├── DisplayProfile.cs ├── EmailMessage.cs ├── ErrorLogEntry.cs ├── ExpiredUserSession.cs ├── FeedEvent.cs ├── Forum.cs ├── ForumPermissionContainer.cs ├── ForumPermissionContext.cs ├── ForumState.cs ├── ForumTopicContainer.cs ├── IPHistoryEvent.cs ├── IStreamResponse.cs ├── ModerationLogEntry.cs ├── ModerationType.cs ├── ModifyForumRolesContainer.cs ├── ModifyForumRolesType.cs ├── NewPost.cs ├── PagedListOfT.cs ├── PagedTopicContainer.cs ├── PagerContext.cs ├── PasswordResetContainer.cs ├── PermanentRoles.cs ├── Post.cs ├── PostEdit.cs ├── PostImage.cs ├── PostImagePersistPayload.cs ├── PostItemContainer.cs ├── PostWithChildren.cs ├── PrivateMessage.cs ├── PrivateMessageBoxType.cs ├── PrivateMessagePost.cs ├── PrivateMessageState.cs ├── PrivateMessageUser.cs ├── PrivateMessageView.cs ├── Profile.cs ├── QAPostItemContainer.cs ├── QueuedEmailMessage.cs ├── ReadStatus.cs ├── ResponseOfT.cs ├── SearchIndexPayload.cs ├── SearchType.cs ├── SearchWord.cs ├── SecurityLogEntry.cs ├── SecurityLogType.cs ├── ServiceHeartbeat.cs ├── SetupVariables.cs ├── SignupData.cs ├── SingleString.cs ├── SubscribeNotificationPayload.cs ├── TimeFormats.cs ├── Topic.cs ├── TopicContainer.cs ├── TopicContainerForQA.cs ├── TopicState.cs ├── TopicUnsubscribeContainer.cs ├── User.cs ├── UserEdit.cs ├── UserEditProfile.cs ├── UserEditSecurity.cs ├── UserImage.cs ├── UserImageApprovalContainer.cs ├── UserResult.cs ├── UserSearch.cs └── VotePostContainer.cs ├── PopForums.csproj ├── Repositories ├── IAwardCalculationQueueRepository.cs ├── IAwardConditionRepository.cs ├── IAwardDefinitionRepository.cs ├── IBanRepository.cs ├── ICategoryRepository.cs ├── IEmailQueueRepository.cs ├── IErrorLogRepository.cs ├── IEventDefinitionRepository.cs ├── IExternalUserAssociationRepository.cs ├── IFavoriteTopicsRepository.cs ├── IFeedRepository.cs ├── IForumRepository.cs ├── ILastReadRepository.cs ├── IModerationLogRepository.cs ├── INotificationRepository.cs ├── IPointLedgerRepository.cs ├── IPostImageRepository.cs ├── IPostImageTempRepository.cs ├── IPostRepository.cs ├── IPrivateMessageRepository.cs ├── IProfileRepository.cs ├── IQueuedEmailMessageRepository.cs ├── IRoleRepository.cs ├── ISearchIndexQueueRepository.cs ├── ISearchRepository.cs ├── ISecurityLogRepository.cs ├── IServiceHeartbeatRepository.cs ├── ISettingsRepository.cs ├── ISetupRepository.cs ├── ISubscribeNotificationRepository.cs ├── ISubscribedTopicsRepository.cs ├── ITopicRepository.cs ├── ITopicViewLogRepository.cs ├── IUserAvatarRepository.cs ├── IUserAwardRepository.cs ├── IUserImageRepository.cs ├── IUserRepository.cs └── IUserSessionRepository.cs ├── Resources ├── Resources.Designer.cs ├── Resources.de.resx ├── Resources.es.resx ├── Resources.nl.resx ├── Resources.resx ├── Resources.uk.resx └── Resources.zh-TW.resx ├── ScoringGame ├── AwardCalculator.cs ├── AwardCalculatorWorker.cs ├── AwardCondition.cs ├── AwardDefinition.cs ├── AwardDefinitionService.cs ├── EventDefinition.cs ├── EventDefinitionService.cs ├── EventPublisher.cs ├── PointLedgerEntry.cs ├── UserAward.cs └── UserAwardService.cs └── Services ├── BanService.cs ├── CategoryService.cs ├── ClaimsToRoleMapper.cs ├── CloseAgedTopicsWorker.cs ├── FavoriteTopicService.cs ├── ForumPermissionService.cs ├── ForumService.cs ├── IPHistoryService.cs ├── ITopicViewCountService.cs ├── IUserRetrievalShim.cs ├── ImageService.cs ├── LastReadService.cs ├── MailingListService.cs ├── ModerationLogService.cs ├── PostImageCleanupWorker.cs ├── PostImageService.cs ├── PostMasterService.cs ├── PostService.cs ├── PrivateMessageService.cs ├── ProfileService.cs ├── QueuedEmailService.cs ├── ReCaptchaService.cs ├── SearchIndexSubsystem.cs ├── SearchIndexWorker.cs ├── SearchService.cs ├── SecurityLogService.cs ├── ServiceHeartbeatService.cs ├── SetupService.cs ├── SitemapService.cs ├── SubscribeNotificationWorker.cs ├── SubscribedTopicsService.cs ├── TenantService.cs ├── TextParsingService.cs ├── TimeFormatStringService.cs ├── TopicService.cs ├── TopicViewLogService.cs ├── UserEmailReconciler.cs ├── UserNameReconciler.cs ├── UserService.cs ├── UserSessionService.cs └── UserSessionWorker.cs /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | /.vs/config 3 | /.vs 4 | /*.user 5 | *.user 6 | .idea 7 | .idea/.idea.PopForums/.idea 8 | .idea/.idea.PopForums/riderModule.iml 9 | 10 | bin 11 | obj 12 | packages 13 | *.pubxml 14 | node_modules 15 | 16 | src/PopForums.AzureKit.Functions/Properties/ServiceDependencies 17 | 18 | src/PopForums.Mvc/wwwroot/lib/* 19 | src/PopForums.Mvc/wwwroot/PopForums.js 20 | src/PopForums.Mvc/package-lock.json 21 | src/PopForums.Mvc/node_modules 22 | 23 | src/PopForums.Web/Areas/Forums 24 | /src/PopForums.Web/appsettings.development.json 25 | /src/PopForums.AzureKit.Functions/local.settings.dev.json 26 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PopForums.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | IP 3 | PM 4 | PMID 5 | QA 6 | UI 7 | True -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ----------- | ------------------ | 10 | | 17.x | :white_check_mark: | 11 | | 17.99.0.x | :x: | 12 | | < 17.0 | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | If you find something suboptimal, add an issue to the Github project. 17 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: just-the-docs/just-the-docs@v0.7.0 2 | aux_links: 3 | "POP Forums on GitHub": 4 | - "https://github.com/POPWorldMedia/POPForums" 5 | footer_content: "©2025, POP World Media, LLC" 6 | title: "POP Forums" 7 | -------------------------------------------------------------------------------- /resources/bootstrap icons/arrow-counterclockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/arrow-down-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/arrow-left-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/arrow-right-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/arrow-up-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/bell-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/bell-slash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/box-arrow-up-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/card-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/chat-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/check-circle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/clock-history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/bootstrap icons/cloud-arrow-up-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/cloud-arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/dash-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/envelope.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/exclamation-circle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/exclamation-octagon-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/eye-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/file-earmark-text-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/file-earmark-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/file-text-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/file-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/gear-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/hand-thumbs-down-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/hand-thumbs-up-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/heart-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/house-door.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/images.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/info-circle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/lock-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/paperclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/pencil-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/people-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/bootstrap icons/people.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/person-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/person.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/pin-angle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/pin-angle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/plus-square-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/plus-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/question-circle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/question-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/bootstrap icons/quote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/recycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/reply-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/reply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/rss-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/share-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/skip-backward-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/skip-end-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/skip-forward-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/skip-start-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/star-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/trash3-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/trophy-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/trophy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/unlock-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/bootstrap icons/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/icomoon-v2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/POPWorldMedia/POPForums/2a6d6e5cb34b37b5ae26b5006eabf24efbcda319/resources/icomoon-v2.0.zip -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [ "*" ] 8 | indent_style = tab 9 | end_of_line = lf 10 | insert_final_newline = true 11 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 12 | 13 | # Code files 14 | [*.{cs,csx,vb,vbx,cshtml}] 15 | indent_style = tab 16 | indent_size = 4 17 | 18 | # Xml project files 19 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 20 | indent_style = tab 21 | indent_size = 2 22 | 23 | # Xml config files 24 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 25 | indent_style = tab 26 | indent_size = 2 27 | 28 | # JSON files 29 | [*.json] 30 | indent_style = space 31 | indent_size = 2 32 | -------------------------------------------------------------------------------- /src/PopForums.AzureKit.Functions/BrokerSink.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Messaging; 2 | using PopForums.Models; 3 | 4 | namespace PopForums.AzureKit.Functions; 5 | 6 | public class BrokerSink : IBroker 7 | { 8 | public void NotifyNewPosts(Topic topic, int lasPostID) 9 | { 10 | throw new System.NotImplementedException(); 11 | } 12 | 13 | public void NotifyForumUpdate(Forum forum) 14 | { 15 | throw new System.NotImplementedException(); 16 | } 17 | 18 | public void NotifyTopicUpdate(Topic topic, Forum forum, string topicLink) 19 | { 20 | throw new System.NotImplementedException(); 21 | } 22 | 23 | public void NotifyNewPost(Topic topic, int postID) 24 | { 25 | throw new System.NotImplementedException(); 26 | } 27 | 28 | public void NotifyPMCount(int userID, int pmCount) 29 | { 30 | throw new System.NotImplementedException(); 31 | } 32 | 33 | public void NotifyUser(Notification notification) 34 | { 35 | throw new System.NotImplementedException(); 36 | } 37 | 38 | public void NotifyUser(Notification notification, string tenantID) 39 | { 40 | throw new System.NotImplementedException(); 41 | } 42 | 43 | public void SendPMMessage(PrivateMessagePost post) 44 | { 45 | throw new System.NotImplementedException(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/PopForums.AzureKit.Functions/CacheHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PopForums.Configuration; 4 | 5 | namespace PopForums.AzureKit.Functions; 6 | 7 | public class CacheHelper : ICacheHelper 8 | { 9 | public void SetCacheObject(string key, object value) 10 | { 11 | } 12 | 13 | public void SetCacheObject(string key, object value, double seconds) 14 | { 15 | } 16 | 17 | public void SetLongTermCacheObject(string key, object value) 18 | { 19 | } 20 | 21 | public void SetPagedListCacheObject(string rootKey, int page, List value) 22 | { 23 | } 24 | 25 | public void RemoveCacheObject(string key) 26 | { 27 | } 28 | 29 | public T GetCacheObject(string key) 30 | { 31 | return default; 32 | } 33 | 34 | public List GetPagedListCacheObject(string rootKey, int page) 35 | { 36 | return null; 37 | } 38 | 39 | public event Action OnRemoveCacheKey; 40 | 41 | public string GetEffectiveCacheKey(string key) 42 | { 43 | return key; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/PopForums.AzureKit.Functions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/PopForums.AzureKit.Functions/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" 6 | }, 7 | "PopForums": { 8 | "WebAppUrlAndArea": "https://localhost:5091/Forums", 9 | "Database": { 10 | "ConnectionString": "server=localhost;Database=popforums21;Trusted_Connection=True;TrustServerCertificate=True;" 11 | }, 12 | "Search": { 13 | "Url": "https://localhost:9200", 14 | "Key": "elastic|1+GhSZd-+ActqOHZLkAL", 15 | "Provider": "" 16 | }, 17 | "Queue": { 18 | "ConnectionString": "UseDevelopmentStorage=true" 19 | }, 20 | "Storage": { 21 | "ConnectionString": "UseDevelopmentStorage=true" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/PopForums.AzureKit/Redis/CacheTelemetrySink.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.AzureKit.Redis; 2 | 3 | public class CacheTelemetrySink : ICacheTelemetry 4 | { 5 | public void Start() 6 | { 7 | } 8 | 9 | public void End(string eventName, string key) 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/PopForums.AzureKit/Redis/ICacheTelemetry.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.AzureKit.Redis; 2 | 3 | public interface ICacheTelemetry 4 | { 5 | void Start(); 6 | void End(string eventName, string key); 7 | } -------------------------------------------------------------------------------- /src/PopForums.AzureKit/Search/SearchTopic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PopForums.AzureKit.Search; 4 | 5 | public class SearchTopic 6 | { 7 | public string Key { get; set; } 8 | public string TopicID { get; set; } 9 | public int ForumID { get; set; } 10 | public string Title { get; set; } 11 | public DateTime LastPostTime { get; set; } 12 | public string StartedByName { get; set; } 13 | public int Replies { get; set; } 14 | public int Views { get; set; } 15 | public bool IsClosed { get; set; } 16 | public bool IsPinned { get; set; } 17 | public string UrlName { get; set; } 18 | public string LastPostName { get; set; } 19 | public string Posts { get; set; } 20 | public string TenantID { get; set; } 21 | } -------------------------------------------------------------------------------- /src/PopForums.ElasticKit/PopForums.ElasticKit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | PopForums ElasticKit Class Library 5 | 21.0.0 6 | Jeff Putz 7 | net9.0 8 | PopForums.ElasticKit 9 | PopForums.ElasticKit 10 | true 11 | https://github.com/POPWorldMedia/POPForums 12 | https://github.com/POPWorldMedia/POPForums 13 | 2024, POP World Media, LLC 14 | MIT 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/PopForums.ElasticKit/Search/SearchTopic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PopForums.ElasticKit.Search; 4 | 5 | public class SearchTopic 6 | { 7 | public string Id { get; set; } 8 | public int TopicID { get; set; } 9 | public int ForumID { get; set; } 10 | public string Title { get; set; } 11 | public DateTime LastPostTime { get; set; } 12 | public string StartedByName { get; set; } 13 | public int Replies { get; set; } 14 | public int Views { get; set; } 15 | public bool IsClosed { get; set; } 16 | public bool IsPinned { get; set; } 17 | public string UrlName { get; set; } 18 | public string LastPostName { get; set; } 19 | public string FirstPost { get; set; } 20 | public string[] Posts { get; set; } 21 | public string TenantID { get; set; } 22 | } -------------------------------------------------------------------------------- /src/PopForums.ElasticKit/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using PopForums.ElasticKit.Search; 4 | using PopForums.Repositories; 5 | using PopForums.Services; 6 | using SearchIndexSubsystem = PopForums.ElasticKit.Search.SearchIndexSubsystem; 7 | 8 | namespace PopForums.ElasticKit; 9 | 10 | public static class ServiceCollectionExtensions 11 | { 12 | public static IServiceCollection AddPopForumsElasticSearch(this IServiceCollection services) 13 | { 14 | services.Replace(ServiceDescriptor.Transient()); 15 | services.Replace(ServiceDescriptor.Transient()); 16 | services.AddTransient(); 17 | return services; 18 | } 19 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Authentication/PopForumsAuthenticationDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Authentication; 2 | 3 | public static class PopForumsAuthenticationDefaults 4 | { 5 | public const string AuthenticationScheme = "PopForumsAuthScheme"; 6 | public const string ForumsClaimType = "http://popforums.com/forumclaims"; 7 | public const string ForumsUserIDType = "http://popforums.com/forumuserid"; 8 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Authentication/PopForumsAuthenticationIgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Authentication; 2 | 3 | public class PopForumsAuthenticationIgnoreAttribute : Attribute 4 | { 5 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Authorization/OAuthOnlyForbidAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Authorization; 2 | 3 | public class OAuthOnlyForbidAttribute(IConfig config) : Attribute, IResourceFilter 4 | { 5 | public void OnResourceExecuting(ResourceExecutingContext context) 6 | { 7 | if (config.IsOAuthOnly) 8 | context.Result = new ForbidResult(); 9 | } 10 | 11 | public void OnResourceExecuted(ResourceExecutedContext context) 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Authorization/PopForumsPrivateForumsFilter.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Authorization; 2 | 3 | public class PopForumsPrivateForumsFilter(IUserRetrievalShim userRetrievalShim, ISettingsManager settingsManager, IConfig config) : IActionFilter 4 | { 5 | public void OnActionExecuting(ActionExecutingContext context) 6 | { 7 | if (!settingsManager.Current.IsPrivateForumInstance && !config.IsOAuthOnly) 8 | return; 9 | if (userRetrievalShim.GetUser() == null) 10 | context.Result = new RedirectToActionResult("Login", AccountController.Name, null); 11 | } 12 | 13 | public void OnActionExecuted(ActionExecutedContext context) 14 | { 15 | } 16 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Controllers/AdminController.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Mvc.Areas.Forums.Authentication; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Controllers; 4 | 5 | [Authorize(Policy = PermanentRoles.Admin, AuthenticationSchemes = PopForumsAuthenticationDefaults.AuthenticationScheme)] 6 | [Area("Forums")] 7 | public class AdminController : Controller 8 | { 9 | public static string Name = "Admin"; 10 | 11 | public ViewResult App(string vue = "") 12 | { 13 | return View(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Controllers/ResourcesController.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Composers; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Controllers; 4 | 5 | [Area("Forums")] 6 | public class ResourcesController : Controller 7 | { 8 | private readonly IResourceComposer _resourceComposer; 9 | 10 | public ResourcesController(IResourceComposer resourceComposer) 11 | { 12 | _resourceComposer = resourceComposer; 13 | } 14 | 15 | [ResponseCache(Duration = 600, Location = ResponseCacheLocation.Client)] 16 | public JsonResult Index() 17 | { 18 | var result = _resourceComposer.GetForCurrentThread(); 19 | return Json(result); 20 | } 21 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Controllers/SitemapController.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Controllers; 2 | 3 | [Area("Forums")] 4 | [TypeFilter(typeof(PopForumsPrivateForumsFilter))] 5 | public class SitemapController : Controller 6 | { 7 | private readonly ISitemapService _sitemapService; 8 | 9 | public static string Name = "Sitemap"; 10 | 11 | public SitemapController(ISitemapService sitemapService) 12 | { 13 | _sitemapService = sitemapService; 14 | } 15 | 16 | [HttpGet("/Forums/Sitemap.xml")] 17 | [ResponseCache(Duration = 900)] 18 | public async Task Index() 19 | { 20 | string SitemapPageLinkGenerator(int page) => Url.Action("Page", Name, new { page }, Request.Scheme); 21 | var sitemapIndex = await _sitemapService.GenerateIndex(SitemapPageLinkGenerator); 22 | return Content(sitemapIndex, "text/xml"); 23 | } 24 | 25 | [HttpGet("/Forums/Sitemap.{page}.xml")] 26 | [ResponseCache(Duration = 900)] 27 | public async Task Page(int page) 28 | { 29 | string TopicLinkGenerator(string id) => Url.Action("Topic", ForumController.Name, new { id }, Request.Scheme); 30 | var sitemap = await _sitemapService.GeneratePage(TopicLinkGenerator, page); 31 | return Content(sitemap, "text/xml"); 32 | } 33 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Extensions/ApplicationBuilders.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Mvc.Areas.Forums.Authentication; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Extensions; 4 | 5 | public static class ApplicationBuilders 6 | { 7 | /// 8 | /// Enables the POP Forums middleware to identify PF users. 9 | /// 10 | public static IApplicationBuilder UsePopForumsAuth(this IApplicationBuilder app) 11 | { 12 | app.UseMiddleware(); 13 | return app; 14 | } 15 | 16 | /// 17 | /// Enables the localization (languages) for POP Forums. Call this before UseMvc. 18 | /// 19 | public static IApplicationBuilder UsePopForumsCultures(this IApplicationBuilder app) 20 | { 21 | var supportedCultures = new List { new CultureInfo("en"), new CultureInfo("de"), new CultureInfo("es"), new CultureInfo("nl"), new CultureInfo("uk"), new CultureInfo("zh-TW") }; 22 | app.UseRequestLocalization(new RequestLocalizationOptions 23 | { 24 | DefaultRequestCulture = new RequestCulture("en", "en"), 25 | SupportedCultures = supportedCultures, 26 | SupportedUICultures = supportedCultures 27 | }); 28 | return app; 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Extensions/AuthorizationOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Mvc.Areas.Forums.Authentication; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Extensions; 4 | 5 | public static class AuthorizationOptionsExtensions 6 | { 7 | /// 8 | /// Adds authorization options to require certain claims for moderator and admin roles in POP Forums. 9 | /// 10 | /// 11 | public static void AddPopForumsPolicies(this AuthorizationOptions options) 12 | { 13 | options.AddPolicy(PermanentRoles.Admin, policy => policy.RequireClaim(PopForumsAuthenticationDefaults.ForumsClaimType, PermanentRoles.Admin)); 14 | options.AddPolicy(PermanentRoles.Moderator, policy => policy.RequireClaim(PopForumsAuthenticationDefaults.ForumsClaimType, PermanentRoles.Moderator)); 15 | } 16 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Extensions/LoggerFactories.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Extensions; 2 | 3 | public static class LoggerFactories 4 | { 5 | public static ILoggerFactory AddPopForumsLogger(this ILoggerFactory logger, IApplicationBuilder app) 6 | { 7 | var setupService = app.ApplicationServices.GetService(); 8 | if (!setupService.IsConnectionPossible() || !setupService.IsDatabaseSetup()) 9 | return logger; 10 | var errorLog = app.ApplicationServices.GetService(); 11 | var settingsManager = app.ApplicationServices.GetService(); 12 | var contextAccessor = app.ApplicationServices.GetService(); 13 | logger.AddProvider(new LoggerProvider(errorLog, settingsManager, contextAccessor)); 14 | return logger; 15 | } 16 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Extensions/LoggerProvider.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Extensions; 2 | 3 | public class LoggerProvider : ILoggerProvider 4 | { 5 | private readonly IErrorLog _errorLog; 6 | private readonly ISettingsManager _settingsManager; 7 | private readonly IHttpContextAccessor _httpContextAccessor; 8 | 9 | public LoggerProvider(IErrorLog errorLog, ISettingsManager settingsManager, IHttpContextAccessor httpContextAccessor) 10 | { 11 | _errorLog = errorLog; 12 | _settingsManager = settingsManager; 13 | _httpContextAccessor = httpContextAccessor; 14 | } 15 | 16 | public void Dispose() 17 | { 18 | } 19 | 20 | public ILogger CreateLogger(string categoryName) 21 | { 22 | return new Logger(_errorLog, _settingsManager, _httpContextAccessor); 23 | } 24 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/ForumRouteConstraint.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums; 2 | 3 | public class ForumRouteConstraint : IRouteConstraint 4 | { 5 | public ForumRouteConstraint(IForumRepository forumRepository) 6 | { 7 | _forumRepository = forumRepository; 8 | } 9 | 10 | private readonly IForumRepository _forumRepository; 11 | 12 | public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, 13 | RouteDirection routeDirection) 14 | { 15 | if (!values.Keys.Contains("urlName")) 16 | return false; 17 | IEnumerable forumUrlNames; 18 | try 19 | { 20 | forumUrlNames = _forumRepository.GetAllForumUrlNames().Result; 21 | } 22 | catch (Exception exc) 23 | { 24 | throw new Exception("Can't read forum URL names from data store.", exc); 25 | } 26 | if (forumUrlNames.Contains(values["urlName"])) 27 | return true; 28 | return false; 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Messaging/PopForumsUserIdProvider.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Messaging; 2 | 3 | public class PopForumsUserIdProvider : IUserIdProvider 4 | { 5 | private readonly IUserRetrievalShim _userRetrievalShim; 6 | private readonly ITenantService _tenantService; 7 | 8 | public PopForumsUserIdProvider(IUserRetrievalShim userRetrievalShim, ITenantService tenantService) 9 | { 10 | _userRetrievalShim = userRetrievalShim; 11 | _tenantService = tenantService; 12 | } 13 | 14 | public string GetUserId(HubConnectionContext connection) 15 | { 16 | var user = _userRetrievalShim.GetUser(); 17 | if (user == null) 18 | return null; 19 | var tenantID = _tenantService.GetTenant(); 20 | var id = FormatUserID(tenantID, user.UserID); 21 | return id; 22 | } 23 | 24 | public static string FormatUserID(string tenantID, int userID) 25 | { 26 | return $"{tenantID}:{userID}"; 27 | } 28 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/AwardConditionDeleteContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class AwardConditionDeleteContainer 4 | { 5 | public string AwardDefinitionID { get; set; } 6 | public string EventDefinitionID { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/EmailUsersContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class EmailUsersContainer 4 | { 5 | public string Subject { get; set; } 6 | public string Body { get; set; } 7 | public string HtmlBody { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/ExternalLoginState.cs: -------------------------------------------------------------------------------- 1 | using PopIdentity; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Models; 4 | 5 | public class ExternalLoginState 6 | { 7 | public ResultData ResultData { get; set; } 8 | public string ReturnUrl { get; set; } 9 | public ProviderType ProviderType { get; set; } 10 | public DateTime? Expiration { get; set; } 11 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/ExternalLoginTypeMetadata.cs: -------------------------------------------------------------------------------- 1 | using PopIdentity; 2 | 3 | namespace PopForums.Mvc.Areas.Forums.Models; 4 | 5 | public class ExternalLoginTypeMetadata 6 | { 7 | public string Name { get; set; } 8 | public string CssClass { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/IPHistoryQuery.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class IPHistoryQuery 4 | { 5 | public string IP { get; set; } 6 | public DateTime Start { get; set; } 7 | public DateTime End { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/ManualEvent.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class ManualEvent 4 | { 5 | public int UserID { get; set; } 6 | public string Message { get; set; } 7 | public int? Points { get; set; } 8 | public string EventDefinitionID { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/SecurityLogQuery.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class SecurityLogQuery 4 | { 5 | public string SearchTerm { get; set; } 6 | public string Type { get; set; } 7 | public DateTime Start { get; set; } 8 | public DateTime End { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/UserEditPhoto.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class UserEditPhoto 4 | { 5 | public UserEditPhoto() { } 6 | 7 | public UserEditPhoto(Profile profile) 8 | { 9 | AvatarID = profile.AvatarID; 10 | ImageID = profile.ImageID; 11 | } 12 | 13 | public int? AvatarID { get; set; } 14 | public int? ImageID { get; set; } 15 | public bool? IsImageApproved { get; set; } 16 | public bool DeleteAvatar { get; set; } 17 | public bool DeleteImage { get; set; } 18 | public IFormFile AvatarFile { get; set; } 19 | public IFormFile PhotoFile { get; set; } 20 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/UserEditWithFiles.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class UserEditWithFiles : UserEdit 4 | { 5 | public UserEditWithFiles() 6 | { 7 | 8 | } 9 | 10 | public UserEditWithFiles(User user, Profile profile) : base(user, profile) 11 | { 12 | } 13 | 14 | public IFormFile AvatarFile { get; set; } 15 | public IFormFile PhotoFile { get; set; } 16 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Models/UserState.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Models; 2 | 3 | public class UserState 4 | { 5 | public bool IsImageEnabled { get; set; } 6 | public bool IsPlainText { get; set; } 7 | public int NewPmCount { get; set; } 8 | public int NotificationCount { get; set; } 9 | public int UserID { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Services/ForumAdapterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Services; 2 | 3 | public class ForumAdapterFactory 4 | { 5 | public ForumAdapterFactory(Forum forum) 6 | { 7 | if (!string.IsNullOrWhiteSpace(forum.ForumAdapterName)) 8 | { 9 | var type = Type.GetType(forum.ForumAdapterName); 10 | if (type == null) 11 | throw new Exception($"Can't find ForumAdapter \"{forum.ForumAdapterName}\" (Forum ID: {forum.ForumID}, Title: {forum.Title})"); 12 | var instance = Activator.CreateInstance(type); 13 | if (!typeof(IForumAdapter).IsAssignableFrom(instance.GetType())) 14 | throw new Exception($"ForumAdapter \"{forum.ForumAdapterName}\" does not implement IForumAdapter (Forum ID: {forum.ForumID}, Title: {forum.Title})"); 15 | ForumAdapter = (IForumAdapter)instance; 16 | } 17 | } 18 | 19 | public bool IsAdapterEnabled => ForumAdapter != null; 20 | public IForumAdapter ForumAdapter { get; } 21 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Services/UserRetrievalShim.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.Services; 2 | 3 | public class UserRetrievalShim : IUserRetrievalShim 4 | { 5 | private readonly IHttpContextAccessor _httpContextAccessor; 6 | 7 | public UserRetrievalShim(IHttpContextAccessor httpContextAccessor) 8 | { 9 | _httpContextAccessor = httpContextAccessor; 10 | } 11 | 12 | public User GetUser() 13 | { 14 | var user = _httpContextAccessor.HttpContext?.Items["PopForumsUser"] as User; 15 | return user; 16 | } 17 | 18 | public Profile GetProfile() 19 | { 20 | var profile = _httpContextAccessor.HttpContext?.Items["PopForumsProfile"] as Profile; 21 | return profile; 22 | } 23 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/TagHelpers/PMReadIndicatorTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.TagHelpers; 2 | 3 | [HtmlTargetElement("pf-pmReadIndicator", Attributes = "privateMessage")] 4 | public class PMReadIndicatorTagHelper : TagHelper 5 | { 6 | [HtmlAttributeName("class")] 7 | public string Class { get; set; } 8 | [HtmlAttributeName("privateMessage")] 9 | public PrivateMessage PrivateMessage { get; set; } 10 | 11 | public override void Process(TagHelperContext context, TagHelperOutput output) 12 | { 13 | if (PrivateMessage.LastPostTime > PrivateMessage.LastViewDate) 14 | { 15 | output.Attributes.Add("title", Resources.NewPosts); 16 | output.PostElement.AppendHtml(""); 17 | } 18 | else 19 | { 20 | output.Attributes.Add("title", Resources.NoNewPosts); 21 | output.PostElement.AppendHtml(""); 22 | } 23 | 24 | output.TagName = "div"; 25 | if (!String.IsNullOrWhiteSpace(Class)) 26 | output.Attributes.Add("class", $"topicIndicator {Class}"); 27 | else 28 | output.Attributes.Add("class", "topicIndicator"); 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/TagHelpers/ValidationClassTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.TagHelpers; 2 | 3 | [HtmlTargetElement("div", Attributes = ValidationForAttributeName + "," + ValidationErrorClassName)] 4 | public class ValidationClassTagHelper : TagHelper 5 | { 6 | private const string ValidationForAttributeName = "pf-validation-for"; 7 | private const string ValidationErrorClassName = "pf-validationerror-class"; 8 | 9 | [HtmlAttributeName(ValidationForAttributeName)] 10 | public ModelExpression For { get; set; } 11 | 12 | [HtmlAttributeName(ValidationErrorClassName)] 13 | public string ValidationErrorClass { get; set; } 14 | 15 | [HtmlAttributeNotBound] 16 | [ViewContext] 17 | public ViewContext ViewContext { get; set; } 18 | 19 | public override void Process(TagHelperContext context, TagHelperOutput output) 20 | { 21 | ModelStateEntry entry; 22 | ViewContext.ViewData.ModelState.TryGetValue(For.Name, out entry); 23 | if (entry == null || !entry.Errors.Any()) 24 | return; 25 | var tagBuilder = new TagBuilder("div"); 26 | tagBuilder.AddCssClass(ValidationErrorClass); 27 | output.MergeAttributes(tagBuilder); 28 | } 29 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/ViewComponents/UserNavigationViewComponent.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 2 | namespace PopForums.Mvc.Areas.Forums.ViewComponents; 3 | 4 | public class UserNavigationViewComponent : ViewComponent 5 | { 6 | public async Task InvokeAsync() 7 | { 8 | return View(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/ViewComponents/UserStateViewComponent.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Mvc.Areas.Forums.ViewComponents; 2 | 3 | public class UserStateViewComponent : ViewComponent 4 | { 5 | private readonly IUserStateComposer _userStateComposer; 6 | 7 | public UserStateViewComponent(IUserStateComposer userStateComposer) 8 | { 9 | _userStateComposer = userStateComposer; 10 | } 11 | 12 | public async Task InvokeAsync() 13 | { 14 | var container = await _userStateComposer.GetState(); 15 | return View(container); 16 | } 17 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/AccountCreated.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.AccountCreated; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |

@PopForums.Resources.AccountCreated

7 | 10 | 11 | @if (ViewData["EmailProblem"] == null) 12 | { 13 |

@ViewData["Result"]

14 | } 15 | else 16 | { 17 |

@ViewData["EmailProblem"] @ViewData["Result"]

18 | } 19 | 20 |

@PopForums.Resources.EditYourProfile

21 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/EditAccountNoUser.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.EditAccount; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |

@PopForums.Resources.EditAccount

7 | 10 | 11 |

@PopForums.Resources.MustBeRegisteredToEditAccount

12 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/Forgot.cshtml: -------------------------------------------------------------------------------- 1 | @using PopForums.Configuration 2 | @inject ISettingsManager SettingsManager 3 | @{ 4 | ViewBag.Title = PopForums.Resources.ForgotPassword; 5 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 6 | } 7 | 8 |

@PopForums.Resources.ForgotPassword

9 | @if (!SettingsManager.Current.IsPrivateForumInstance) 10 | { 11 | 14 | } 15 | 16 |

@PopForums.Resources.ForgotInstructions

17 | 18 |
19 |
20 | 21 |
22 | 23 | @if (ViewBag.Result != null) { @ViewBag.Result } 24 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/MiniUserNotFound.cshtml: -------------------------------------------------------------------------------- 1 | 

@PopForums.Resources.UserNotFound

-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/OAuthLogin.cshtml: -------------------------------------------------------------------------------- 1 | @using PopForums 2 | @model string 3 | @inject ISettingsManager SettingsManager 4 | 5 | @{ 6 | Layout = null; 7 | } 8 | 9 | 10 | 11 | 12 | 13 | @Resources.Login 14 | 15 | 16 | 17 |
18 |

@SettingsManager.Current.ForumTitle

19 |
20 | @Resources.Login 21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/ResetPasswordSuccess.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.PasswordResetSuccess; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |

@PopForums.Resources.PasswordResetSuccess

7 | 10 | 11 |

@PopForums.Resources.PasswordResetNote

-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/Unsubscribe.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.Unsubscribe; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |

@PopForums.Resources.Unsubscribe

7 | 10 | 11 |

@PopForums.Resources.UnsubscribeNote

-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/UnsubscribeFailure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.UnsubscribeFail; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |

@PopForums.Resources.UnsubscribeFail

7 | 10 | 11 |

@PopForums.Resources.UnsubscribeLinkBad

-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Account/VerifyFail.cshtml: -------------------------------------------------------------------------------- 1 | @using PopForums.Configuration 2 | @inject ISettingsManager SettingsManager 3 | @{ 4 | ViewBag.Title = PopForums.Resources.VerificationFailure; 5 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 6 | } 7 | 8 |

@PopForums.Resources.VerificationFailure

9 | @if (!SettingsManager.Current.IsPrivateForumInstance) 10 | { 11 | 14 | } 15 | 16 |

@PopForums.Resources.VerificationLinkBad @PopForums.Resources.NeedToVerifyExistingAccount

-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Forum/NewComment.cshtml: -------------------------------------------------------------------------------- 1 | @model NewPost 2 | 3 | 24 | 25 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Forum/NewTopic.cshtml: -------------------------------------------------------------------------------- 1 | @model NewPost 2 | 3 | 27 | 28 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Forum/TopicPage.cshtml: -------------------------------------------------------------------------------- 1 | @model TopicContainer 2 | @inject IUserRetrievalShim UserRetrievalShim 3 | @{ 4 | var user = UserRetrievalShim.GetUser(); 5 | var profile = UserRetrievalShim.GetProfile(); 6 | var routeParameters = new Dictionary {{"id", Model.Topic.UrlName}}; 7 | } 8 |
9 | @foreach (var post in Model.Posts) 10 | { 11 | @await Html.PartialAsync("~/Areas/Forums/Views/Forum/PostItem.cshtml", new PostItemContainer { Post = post, VotedPostIDs = Model.VotedPostIDs, Signatures = Model.Signatures, Avatars = Model.Avatars, User = user, Profile = profile, Topic = Model.Topic }); 12 | } 13 | 14 | @Html.Hidden("LastPostID", Model.Posts.Last().PostID, new { @class = "lastPostID" }) 15 | @Html.Hidden("PageCount", Model.PagerContext.PageCount, new { @class = "pageCount" }) 16 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Forum/Voters.cshtml: -------------------------------------------------------------------------------- 1 | @model VotePostContainer 2 |
    3 | @foreach (var user in Model.Voters) 4 | { 5 |
  • @Html.ActionLink(user.Value, "ViewProfile", "Account", new { id = user.Key }, new { target = "_blank" })
  • 6 | } 7 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Identity/ExternalError.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | @{ 3 | ViewBag.Title = PopForums.Resources.Login + " - " + PopForums.Resources.Error; 4 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 5 | } 6 | 7 |
8 |

@PopForums.Resources.Login - @PopForums.Resources.Error

9 | 12 |
13 | 14 |
15 |
16 |

@PopForums.Resources.Error: @Model

17 | 18 |

@PopForums.Resources.Login

19 |
20 |
21 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Moderator/PostModerationLog.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 3 |
4 | 5 | @foreach (var entry in Model) 6 | { 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | } 19 |
@entry.UserName@entry.ModerationType
14 |

@PopForums.Resources.Comment: @entry.Comment

15 | @Html.Raw(entry.OldText) 16 |
20 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Moderator/TopicModerationLog.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 3 |
4 |

@PopForums.Resources.ModerationLog

5 | 6 | @if (Model.Count == 0) 7 | { 8 |

@PopForums.Resources.None

9 | } 10 | 11 | @foreach (var entry in Model) 12 | { 13 | 14 | 15 | @if (entry.UserID == 0) 16 | { 17 | 18 | } 19 | else 20 | { 21 | 22 | } 23 | 24 | 25 | } 26 |
@entry.UserName@entry.UserName@entry.ModerationType
27 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Setup/Exception.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Exception 2 | 3 | 4 | 5 | 6 | @PopForums.Resources.PopForumsSetup - @PopForums.Resources.Error 7 | 8 | 9 | 10 |
11 |

@PopForums.Resources.ErrorSettingUpDb:

12 | 13 |

@Model.Message

14 | 15 |

@Html.Raw(Model.StackTrace.Replace(Environment.NewLine, "
"))

16 |
17 | 18 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Setup/NoConnection.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | @PopForums.Resources.PopForumsSetup - @PopForums.Resources.NoDataConnection 6 | 7 | 8 | 9 |
10 |

@PopForums.Resources.PopForumsSetup

11 | 12 |

@PopForums.Resources.SetupCantConnect

13 |
14 | 15 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Setup/Success.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | @PopForums.Resources.PopForumsSetup - @PopForums.Resources.Success 6 | 7 | 8 | 9 |
10 |

@PopForums.Resources.PopForumsSetup - @PopForums.Resources.Success

11 |

Congratulations!

12 |

@PopForums.Resources.ForumReady

13 |

@PopForums.Resources.AppRestartRequired

14 |
15 | 16 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Shared/Components/UserState/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model UserState 2 | @inject IUserRetrievalShim UserRetrievalShim 3 | @inject ITimeFormatStringService TimeFormatStringService 4 | @{ 5 | var serialized = JsonSerializer.Serialize(Model, new JsonSerializerOptions{ PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); 6 | var user = UserRetrievalShim.GetUser(); 7 | } 8 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Shared/Forbidden.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.Forbidden; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |
7 |

@PopForums.Resources.Forbidden

8 | 11 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/Shared/NotFound.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = PopForums.Resources.PageNotFound; 3 | Layout = "~/Areas/Forums/Views/Shared/PopForumsMaster.cshtml"; 4 | } 5 | 6 |
7 |

@PopForums.Resources.PageNotFound

8 | 11 |
-------------------------------------------------------------------------------- /src/PopForums.Mvc/Areas/Forums/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using PopForums.Extensions 2 | @using PopForums.Models 3 | @using PopForums.Services 4 | @using PopForums.Mvc.Areas.Forums.Controllers 5 | @using PopForums.Mvc.Areas.Forums.Models 6 | @using PopForums.Mvc.Areas.Forums.Services 7 | @using System.Threading.Tasks 8 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 9 | @addTagHelper *, PopForums.Mvc -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Components/NotificationList.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class NotificationList extends ElementBase { 4 | constructor() { 5 | super(); 6 | } 7 | 8 | connectedCallback() { 9 | super.connectedCallback(); 10 | } 11 | 12 | getDependentReference(): [UserState, string] { 13 | return [PopForums.userState, "notifications"]; 14 | } 15 | 16 | updateUI(data: Array): void { 17 | if (!data || data.length === 0) { 18 | this.replaceChildren(); 19 | return; 20 | } 21 | data.forEach(item => { 22 | let n = new NotificationItem(item); 23 | this.append(n); 24 | }); 25 | } 26 | } 27 | 28 | customElements.define('pf-notificationlist', NotificationList); 29 | 30 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Components/NotificationMarkAllButton.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class NotificationMarkAllButton extends HTMLElement { 4 | constructor() { 5 | super(); 6 | } 7 | 8 | get buttonclass(): string { 9 | return this.getAttribute("buttonclass"); 10 | } 11 | get buttontext(): string { 12 | return this.getAttribute("buttontext"); 13 | } 14 | 15 | connectedCallback() { 16 | this.innerHTML = ``; 17 | this.querySelector("input").addEventListener("click", () => { 18 | PopForums.userState.MarkAllRead(); 19 | }); 20 | } 21 | } 22 | 23 | customElements.define('pf-notificationmarkallbutton', NotificationMarkAllButton); 24 | 25 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Components/PMCount.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class PMCount extends ElementBase { 4 | constructor() { 5 | super(); 6 | this.isInit = false; 7 | } 8 | 9 | private isInit: boolean; 10 | 11 | getDependentReference(): [UserState, string] { 12 | return [PopForums.userState, "newPmCount"]; 13 | } 14 | 15 | updateUI(data: number): void { 16 | if (data === 0) 17 | this.innerHTML = ""; 18 | else { 19 | this.innerHTML = `${data}`; 20 | if (this.isInit) 21 | this.innerHTML = `${data}`; 22 | } 23 | this.isInit = true; 24 | } 25 | } 26 | 27 | customElements.define('pf-pmcount', PMCount); 28 | 29 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/ElementBase.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export abstract class ElementBase extends HTMLElement { 4 | 5 | connectedCallback() { 6 | if (this.state && this.propertyToWatch) 7 | return; 8 | let stateAndWatchProperty = this.getDependentReference(); 9 | this.state = stateAndWatchProperty[0]; 10 | this.propertyToWatch = stateAndWatchProperty[1]; 11 | const delegate = this.update.bind(this); 12 | this.state.subscribe(this.propertyToWatch, delegate); 13 | } 14 | 15 | private state: B; 16 | private propertyToWatch: string; 17 | 18 | update() { 19 | const externalValue = this.state[this.propertyToWatch as keyof B]; 20 | this.updateUI(externalValue); 21 | } 22 | 23 | // Implementation should return the StateBase and property (as a string) to monitor 24 | abstract getDependentReference(): [B, string]; 25 | 26 | // Use in the implementation to manipulate the shadow or light DOM or straight markup as needed in response to the new data. 27 | abstract updateUI(data: any): void; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Models/Notification.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class Notification { 4 | userID: number; 5 | timeStamp: Date; 6 | isRead: boolean; 7 | notificationType: number; 8 | contextID: number; 9 | data: any; 10 | unreadCount: number; 11 | } 12 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Models/PrivateMessage.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class PrivateMessage { 4 | pmPostID: number; 5 | userID: number; 6 | name: string; 7 | postTime: Date; 8 | fullText: string; 9 | } 10 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Models/PrivateMessageUser.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | export class PrivateMessageUser { 3 | userID: number; 4 | name: string; 5 | } 6 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Services/LocalizationService.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class LocalizationService { 4 | static init(): void { 5 | const path = PopForums.AreaPath + "/Resources"; 6 | fetch(path) 7 | .then(response => { 8 | return response.json(); 9 | }) 10 | .then(json => { 11 | PopForums.localizations = Object.assign(new Localizations(), json); 12 | return this.signal(); 13 | }); 14 | } 15 | 16 | private static signal() { 17 | PopForums.Ready(() => { 18 | if (this.readies) { 19 | for (let i of this.readies) { 20 | i(); 21 | } 22 | } 23 | this.isSignaled = true; 24 | }); 25 | } 26 | 27 | static readies: Array; 28 | private static isSignaled: boolean = false; 29 | 30 | static subscribe(ready: Function): boolean { 31 | if (!this.readies) 32 | this.readies = new Array(); 33 | this.readies.push(ready); 34 | return this.isSignaled; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/Services/MessagingService.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export class MessagingService { 4 | private static service: MessagingService; 5 | private static promise: Promise; 6 | 7 | static async GetService(): Promise { 8 | if (!this.promise) { 9 | const service = new MessagingService(); 10 | this.promise = service.start(); 11 | this.service = service; 12 | } 13 | await Promise.all([this.promise]); 14 | return this.service; 15 | } 16 | 17 | connection: any; 18 | 19 | private async start() { 20 | this.connection = new signalR.HubConnectionBuilder().withUrl("/PopForumsHub").withAutomaticReconnect().build(); 21 | await this.connection.start(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/State/Localizations.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | export class Localizations { 3 | todayTime: string; 4 | yesterdayTime: string; 5 | minutesAgo: string; 6 | oneMinuteAgo: string; 7 | lessThanMinute: string; 8 | 9 | notifications: string; 10 | newReplyNotification: string; 11 | award: string; 12 | voteUpNotification: string; 13 | questionAnsweredNotification: string; 14 | send: string; 15 | 16 | uploadImage: string; 17 | } 18 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/StateBase.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | // Properties to watch require the @WatchProperty attribute. 4 | export class StateBase { 5 | constructor() { 6 | this._subs = new Map>(); 7 | } 8 | 9 | private _subs: Map>; 10 | 11 | subscribe(propertyName: string, eventHandler: Function) { 12 | if (!this._subs.has(propertyName)) 13 | this._subs.set(propertyName, new Array()); 14 | const callbacks = this._subs.get(propertyName); 15 | callbacks.push(eventHandler); 16 | eventHandler(); 17 | } 18 | 19 | notify(propertyName: string) { 20 | const callbacks = this._subs.get(propertyName); 21 | if (callbacks) 22 | for (let i of callbacks) { 23 | i(); 24 | } 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/WatchPropertyAttribute.ts: -------------------------------------------------------------------------------- 1 | namespace PopForums { 2 | 3 | export const WatchProperty = (target: any, memberName: string) => { 4 | let currentValue: any = target[memberName]; 5 | Object.defineProperty(target, memberName, { 6 | set(this: any, newValue: any) { 7 | currentValue = newValue; 8 | this.notify(memberName); 9 | }, 10 | get() {return currentValue;} 11 | }); 12 | }; 13 | 14 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2018", 5 | "inlineSources": true, 6 | "inlineSourceMap": true, 7 | "outFile": "../wwwroot/PopForums.js", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "Node" 11 | } 12 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": {}, 3 | "dependencies": { 4 | "@microsoft/signalr": "8.0.7", 5 | "axios": "1.8.2", 6 | "bootstrap": "5.3.3", 7 | "tinymce": "7.5.1", 8 | "vue": "3.5.13", 9 | "vue-router": "4.4.5" 10 | }, 11 | "devDependencies": { 12 | "typescript": "5.2.2", 13 | "gulp-typescript": "5.0.1", 14 | "@babel/core": "7.23.3", 15 | "@babel/preset-env": "7.23.3", 16 | "gulp": "4.0.2", 17 | "gulp-babel": "8.0.0", 18 | "gulp-clean-css": "4.3.0", 19 | "gulp-rename": "2.0.0", 20 | "gulp-sourcemaps": "3.0.0", 21 | "gulp-uglify": "3.0.2", 22 | "merge-stream": "2.0.0" 23 | }, 24 | "babel": { 25 | "presets": [ 26 | "@babel/preset-env" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/PopForums.Mvc/wwwroot/Editor.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | } 4 | blockquote { 5 | font-size: 100%; 6 | border-left: 5px solid #aaaaaa; 7 | padding: 0 1em; 8 | } 9 | 10 | img { 11 | max-width: 100%; 12 | } -------------------------------------------------------------------------------- /src/PopForums.Mvc/wwwroot/Fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/POPWorldMedia/POPForums/2a6d6e5cb34b37b5ae26b5006eabf24efbcda319/src/PopForums.Mvc/wwwroot/Fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/PopForums.Mvc/wwwroot/Fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/POPWorldMedia/POPForums/2a6d6e5cb34b37b5ae26b5006eabf24efbcda319/src/PopForums.Mvc/wwwroot/Fonts/icomoon.woff -------------------------------------------------------------------------------- /src/PopForums.Sql/Global.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Data; 4 | global using System.Data.Common; 5 | global using System.IO; 6 | global using System.Linq; 7 | global using System.Reflection; 8 | global using System.Text; 9 | global using System.Text.RegularExpressions; 10 | global using System.Text.Json; 11 | global using System.Threading.Tasks; 12 | 13 | global using Microsoft.Data.SqlClient; 14 | global using Microsoft.Extensions.Caching.Memory; 15 | global using Microsoft.Extensions.DependencyInjection; 16 | 17 | global using Dapper; 18 | 19 | global using PopForums.Configuration; 20 | global using PopForums.Email; 21 | global using PopForums.ExternalLogin; 22 | global using PopForums.Models; 23 | global using PopForums.Repositories; 24 | global using PopForums.ScoringGame; 25 | global using PopForums.Services; 26 | 27 | global using PopForums.Sql.Repositories; -------------------------------------------------------------------------------- /src/PopForums.Sql/ISqlObjectFactory.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Sql; 2 | 3 | public interface ISqlObjectFactory 4 | { 5 | DbConnection GetConnection(); 6 | DbCommand GetCommand(); 7 | DbCommand GetCommand(string sql); 8 | DbCommand GetCommand(string sql, DbConnection connection); 9 | DbParameter GetParameter(string parameterName, object value); 10 | } -------------------------------------------------------------------------------- /src/PopForums.Sql/JsonElementTypeHandler.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Sql; 2 | 3 | public class JsonElementTypeHandler : SqlMapper.TypeHandler 4 | { 5 | public override void SetValue(IDbDataParameter parameter, JsonElement value) 6 | { 7 | parameter.DbType = DbType.String; 8 | parameter.Size = int.MaxValue; 9 | parameter.Value = value.ToString(); 10 | } 11 | 12 | public override JsonElement Parse(object value) 13 | { 14 | var o = JsonSerializer.Deserialize((string)value); 15 | var element = JsonSerializer.SerializeToElement(o, new JsonSerializerOptions {PropertyNamingPolicy = JsonNamingPolicy.CamelCase}); 16 | return element; 17 | } 18 | } -------------------------------------------------------------------------------- /src/PopForums.Sql/PopForums13to14.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('pf_EmailQueue', 'U') IS NULL 2 | BEGIN 3 | CREATE TABLE [dbo].[pf_EmailQueue]( 4 | [Id] [int] IDENTITY(1,1) NOT NULL, 5 | [Payload] [nvarchar](256) NOT NULL 6 | ) ON [PRIMARY] 7 | 8 | CREATE CLUSTERED INDEX IX_pf_EmailQueue_Id ON pf_EmailQueue (Id) 9 | END 10 | 11 | 12 | 13 | IF OBJECT_ID('pf_SearchQueue', 'U') IS NULL 14 | BEGIN 15 | CREATE TABLE [dbo].[pf_SearchQueue]( 16 | [ID] [int] IDENTITY(1,1) NOT NULL, 17 | [TopicID] [int] NOT NULL 18 | ) 19 | 20 | CREATE CLUSTERED INDEX IX_pf_SearchQueue_ID ON pf_SearchQueue (ID) 21 | END 22 | 23 | 24 | 25 | IF OBJECT_ID('pf_ServiceHeartbeat', 'U') IS NULL 26 | BEGIN 27 | CREATE TABLE [dbo].[pf_ServiceHeartbeat]( 28 | [ServiceName] [nvarchar](256) NOT NULL, 29 | [MachineName] [nvarchar](256) NOT NULL, 30 | [LastRun] [datetime] NOT NULL, 31 | CONSTRAINT [PK_pf_ServiceHeartbeat] PRIMARY KEY CLUSTERED 32 | ( 33 | [ServiceName] ASC, 34 | [MachineName] ASC 35 | ) 36 | ) 37 | END 38 | 39 | IF EXISTS( SELECT TOP 1 1 FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id WHERE o.name = 'pf_Profile' AND c.name = 'AIM') 40 | ALTER TABLE dbo.pf_Profile DROP COLUMN [AIM] 41 | GO 42 | -------------------------------------------------------------------------------- /src/PopForums.Sql/PopForums19to20.sql: -------------------------------------------------------------------------------- 1 | IF COL_LENGTH('dbo.pf_PopForumsUser', 'TokenExpiration') IS NULL 2 | BEGIN 3 | ALTER TABLE pf_PopForumsUser ADD [TokenExpiration] [datetime] NULL; 4 | END 5 | 6 | IF COL_LENGTH('dbo.pf_UserActivity', 'RefreshToken') IS NULL 7 | BEGIN 8 | ALTER TABLE pf_UserActivity ADD [RefreshToken] [nvarchar](MAX) NULL; 9 | END -------------------------------------------------------------------------------- /src/PopForums.Sql/PopForums20to21.sql: -------------------------------------------------------------------------------- 1 | IF COL_LENGTH('dbo.pf_Profile', 'Twitter') IS NOT NULL 2 | BEGIN 3 | ALTER TABLE pf_Profile DROP COLUMN [Twitter]; 4 | END 5 | -------------------------------------------------------------------------------- /src/PopForums.Sql/Repositories/TopicViewLogRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Sql.Repositories; 2 | 3 | public class TopicViewLogRepository : ITopicViewLogRepository 4 | { 5 | private readonly ISqlObjectFactory _sqlObjectFactory; 6 | 7 | public TopicViewLogRepository(ISqlObjectFactory sqlObjectFactory) 8 | { 9 | _sqlObjectFactory = sqlObjectFactory; 10 | } 11 | 12 | public async Task Log(int? userID, int topicID, DateTime timeStamp) 13 | { 14 | await _sqlObjectFactory.GetConnection().UsingAsync(connection => 15 | connection.ExecuteAsync("INSERT INTO pf_TopicViewLog (UserID, TopicID, [TimeStamp]) VALUES (@UserID, @TopicID, @TimeStamp)", new {UserID = userID, TopicID = topicID, TimeStamp = timeStamp})); 16 | } 17 | } -------------------------------------------------------------------------------- /src/PopForums.Sql/SqlObjectFactory.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Sql; 2 | 3 | public class SqlObjectFactory : ISqlObjectFactory 4 | { 5 | private readonly IConfig _config; 6 | 7 | public SqlObjectFactory(IConfig config) 8 | { 9 | _config = config; 10 | } 11 | 12 | public DbConnection GetConnection() 13 | { 14 | if (string.IsNullOrWhiteSpace(_config.DatabaseConnectionString)) 15 | throw new Exception("No database connection string found for POP Forums."); 16 | var connectionString = _config.DatabaseConnectionString; 17 | return new SqlConnection(connectionString); 18 | } 19 | 20 | public DbCommand GetCommand() 21 | { 22 | return new SqlCommand(); 23 | } 24 | 25 | public DbCommand GetCommand(string sql) 26 | { 27 | return new SqlCommand(sql); 28 | } 29 | 30 | public DbCommand GetCommand(string sql, DbConnection connection) 31 | { 32 | return new SqlCommand(sql, (SqlConnection)connection); 33 | } 34 | 35 | public DbParameter GetParameter(string parameterName, object value) 36 | { 37 | return new SqlParameter(parameterName, value); 38 | } 39 | } -------------------------------------------------------------------------------- /src/PopForums.Sql/StreamResponse.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Sql; 2 | 3 | public class StreamResponse(Stream stream, SqlConnection connection, SqlDataReader reader) : IStreamResponse 4 | { 5 | public Stream Stream => stream; 6 | 7 | public void Dispose() 8 | { 9 | reader.Close(); 10 | connection.Close(); 11 | stream.Close(); 12 | 13 | reader.Dispose(); 14 | connection.Dispose(); 15 | stream.Dispose(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Composers/ForumStateComposerTests.cs: -------------------------------------------------------------------------------- 1 | using PopForums.Composers; 2 | 3 | namespace PopForums.Test.Composers; 4 | 5 | public class ForumStateComposerTests 6 | { 7 | protected ForumStateComposer GetComposer() 8 | { 9 | return new ForumStateComposer(); 10 | } 11 | 12 | public class GetState : ForumStateComposerTests 13 | { 14 | [Fact] 15 | public void MapsCorrectly() 16 | { 17 | var composer = GetComposer(); 18 | var pagerContext = new PagerContext {PageCount = 1, PageIndex = 2, PageSize = 3}; 19 | var forum = new Forum {ForumID = 4}; 20 | 21 | var result = composer.GetState(forum, pagerContext); 22 | 23 | Assert.Equal(forum.ForumID, result.ForumID); 24 | Assert.Equal(pagerContext.PageIndex, result.PageIndex); 25 | Assert.Equal(pagerContext.PageSize, result.PageSize); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Email/NewAccountMailerTests.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Email; 2 | 3 | public class NewAccountMailerTests 4 | { 5 | [Fact] 6 | public void SendCallsSmtpWrapper() 7 | { 8 | var wrapper = Substitute.For(); 9 | var resultMessage = new EmailMessage(); 10 | wrapper.Send(Arg.Do(msg => resultMessage = msg)).Returns(SmtpStatusCode.Ok); 11 | const string mailerAddress = "a@b.com"; 12 | const string forumTitle = "superawesome"; 13 | var user = UserTest.GetTestUser(); 14 | var settings = new Settings { MailerAddress = mailerAddress, ForumTitle = forumTitle}; 15 | var settingsManager = Substitute.For(); 16 | settingsManager.Current.Returns(settings); 17 | var mailer = new NewAccountMailer(settingsManager, wrapper); 18 | 19 | var result = mailer.Send(user, "http://blah/"); 20 | 21 | Assert.Equal(SmtpStatusCode.Ok, result); 22 | Assert.Equal(resultMessage.ToName, user.Name); 23 | Assert.Equal(resultMessage.ToEmail, user.Email); 24 | Assert.Equal(forumTitle, resultMessage.FromName); 25 | } 26 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Global.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | global using System.Reflection; 5 | global using System.Security; 6 | global using System.Text.RegularExpressions; 7 | global using System.Threading.Tasks; 8 | 9 | global using Microsoft.AspNetCore.Mvc; 10 | 11 | global using PopForums.Configuration; 12 | global using PopForums.Email; 13 | global using PopForums.Extensions; 14 | global using PopForums.ExternalLogin; 15 | global using PopForums.Feeds; 16 | global using PopForums.Messaging; 17 | global using PopForums.Models; 18 | global using PopForums.Repositories; 19 | global using PopForums.ScoringGame; 20 | global using PopForums.Services; 21 | 22 | global using PopForums.Mvc.Areas.Forums.Controllers; 23 | global using PopForums.Mvc.Areas.Forums.Services; 24 | 25 | global using PopForums.Test.Models; 26 | 27 | global using NSubstitute; 28 | global using Xunit; -------------------------------------------------------------------------------- /src/PopForums.Test/Models/UserEditSecurityTests.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Models; 2 | 3 | public class UserEditSecurityTests 4 | { 5 | [Fact] 6 | public void PasswordsMatch() 7 | { 8 | var edit = new UserEditSecurity(); 9 | edit.NewPassword = "blah"; 10 | edit.NewPasswordRetype = "blah"; 11 | Assert.True(edit.NewPasswordsMatch()); 12 | } 13 | 14 | [Fact] 15 | public void PasswordsNoMatch() 16 | { 17 | var edit = new UserEditSecurity(); 18 | edit.NewPassword = "blasjspvjsh"; 19 | edit.NewPasswordRetype = "blah"; 20 | Assert.False(edit.NewPasswordsMatch()); 21 | } 22 | 23 | [Fact] 24 | public void EmailMatch() 25 | { 26 | var edit = new UserEditSecurity(); 27 | edit.NewEmail = "blah"; 28 | edit.NewEmailRetype = "blah"; 29 | Assert.True(edit.NewEmailsMatch()); 30 | } 31 | 32 | [Fact] 33 | public void EmailNoMatch() 34 | { 35 | var edit = new UserEditSecurity(); 36 | edit.NewEmail = "blah"; 37 | edit.NewEmailRetype = "bloidsvosah"; 38 | Assert.False(edit.NewEmailsMatch()); 39 | } 40 | 41 | [Fact] 42 | public void IsNewUserApprovedMapped() 43 | { 44 | var edit = new UserEditSecurity(new User { UserID = 1 }, true); 45 | Assert.True(edit.IsNewUserApproved); 46 | } 47 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Models/UserTest.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Models; 2 | 3 | public class UserTest 4 | { 5 | [Fact] 6 | public void IsRoleWiredToRoles() 7 | { 8 | var user = GetTestUser(); 9 | user.Roles = new List {"blah", "three", PermanentRoles.Admin}; 10 | Assert.True(user.IsInRole(PermanentRoles.Admin)); 11 | } 12 | 13 | public static User GetTestUser() 14 | { 15 | const int userID = 123; 16 | const string name = "Jeff"; 17 | const string email = "a@b.com"; 18 | var createDate = DateTime.UtcNow; 19 | const bool approved = true; 20 | var authKey = Guid.NewGuid(); 21 | return new User { UserID = userID, Name = name, Email = email, CreationDate = createDate, IsApproved = approved, AuthorizationKey = authKey, Roles = new List() }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/BanServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Services; 2 | 3 | public class BanServiceTests 4 | { 5 | private IBanRepository _banRepo; 6 | 7 | private IBanService GetService() 8 | { 9 | _banRepo = Substitute.For(); 10 | return new BanService(_banRepo); 11 | } 12 | 13 | [Fact] 14 | public async Task IPTrimmedOnSave() 15 | { 16 | var service = GetService(); 17 | await service.BanIP(" 1.1.1.1 "); 18 | await _banRepo.Received().BanIP("1.1.1.1"); 19 | } 20 | 21 | [Fact] 22 | public async Task EmailTrimmedOnSave() 23 | { 24 | var service = GetService(); 25 | await service.BanEmail(" a@b.com "); 26 | await _banRepo.Received().BanEmail("a@b.com"); 27 | } 28 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/CloseAgedTopicsWorkerTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute.ExceptionExtensions; 2 | 3 | namespace PopForums.Test.Services; 4 | 5 | public class CloseAgedTopicsWorkerTests 6 | { 7 | private ITopicService _topicService; 8 | private IErrorLog _errorLog; 9 | 10 | private CloseAgedTopicsWorker GetWorker() 11 | { 12 | _topicService = Substitute.For(); 13 | _errorLog = Substitute.For(); 14 | return new CloseAgedTopicsWorker(_topicService, _errorLog); 15 | } 16 | 17 | [Fact] 18 | public void NoErrorNoLog() 19 | { 20 | var worker = GetWorker(); 21 | _topicService.CloseAgedTopics().Returns(Task.CompletedTask); 22 | 23 | worker.Execute(); 24 | 25 | _errorLog.DidNotReceive().Log(Arg.Any(), Arg.Any()); 26 | } 27 | 28 | [Fact] 29 | public void LogWhenThrows() 30 | { 31 | var worker = GetWorker(); 32 | _topicService.CloseAgedTopics().ThrowsAsync(); 33 | 34 | worker.Execute(); 35 | 36 | _errorLog.Received().Log(Arg.Any(), Arg.Any()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/PostImageCleanupWorkerTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute.ExceptionExtensions; 2 | 3 | namespace PopForums.Test.Services; 4 | 5 | public class PostImageCleanupWorkerTests 6 | { 7 | private IPostImageService _postImageService; 8 | private IErrorLog _errorLog; 9 | 10 | private PostImageCleanupWorker GetWorker() 11 | { 12 | _postImageService = Substitute.For(); 13 | _errorLog = Substitute.For(); 14 | return new PostImageCleanupWorker(_postImageService, _errorLog); 15 | } 16 | 17 | [Fact] 18 | public void NoErrorNoLog() 19 | { 20 | var worker = GetWorker(); 21 | _postImageService.DeleteOldPostImages().Returns(Task.CompletedTask); 22 | 23 | worker.Execute(); 24 | 25 | _errorLog.DidNotReceive().Log(Arg.Any(), Arg.Any()); 26 | } 27 | 28 | [Fact] 29 | public void LogWhenThrows() 30 | { 31 | var worker = GetWorker(); 32 | _postImageService.DeleteOldPostImages().ThrowsAsync(); 33 | 34 | worker.Execute(); 35 | 36 | _errorLog.Received().Log(Arg.Any(),ErrorSeverity.Error); 37 | } 38 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/TopicViewLogServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Services; 2 | 3 | public class TopicViewLogServiceTests 4 | { 5 | private TopicViewLogService GetService() 6 | { 7 | _config = Substitute.For(); 8 | _topicViewLogRepo = Substitute.For(); 9 | return new TopicViewLogService(_config, _topicViewLogRepo); 10 | } 11 | 12 | private IConfig _config; 13 | private ITopicViewLogRepository _topicViewLogRepo; 14 | 15 | [Fact] 16 | public async Task LogViewDoesNotCallRepoWhenConfigIsFalse() 17 | { 18 | var service = GetService(); 19 | _config.LogTopicViews.Returns(false); 20 | 21 | await service.LogView(123, 456); 22 | 23 | await _topicViewLogRepo.DidNotReceive().Log(Arg.Any(), Arg.Any(), Arg.Any()); 24 | } 25 | 26 | [Fact] 27 | public async Task LogViewDoesCallsRepoWhenConfigIsTrue() 28 | { 29 | var service = GetService(); 30 | _config.LogTopicViews.Returns(true); 31 | 32 | await service.LogView(123, 456); 33 | 34 | await _topicViewLogRepo.Received().Log(123, 456, Arg.Any()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/UserEmailReconcilerTests.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Test.Services; 2 | 3 | public class UserEmailReconcilerTests 4 | { 5 | private IUserRepository _userRepo; 6 | 7 | private UserEmailReconciler GetService() 8 | { 9 | _userRepo = Substitute.For(); 10 | return new UserEmailReconciler(_userRepo); 11 | } 12 | 13 | public class GetUniqueEmail : UserEmailReconcilerTests 14 | { 15 | [Fact] 16 | public async Task UnmatchedReturnsSameEmail() 17 | { 18 | var service = GetService(); 19 | var email = "a@b.com"; 20 | _userRepo.GetUserByEmail(email).Returns((User)null); 21 | 22 | var result = await service.GetUniqueEmail(email, "12345"); 23 | 24 | Assert.Equal(email, result); 25 | } 26 | 27 | [Fact] 28 | public async Task MatchedReturnsUniqueEmail() 29 | { 30 | var service = GetService(); 31 | var email = "a@b.com"; 32 | var user = new User { Email = email }; 33 | _userRepo.GetUserByEmail(email).Returns(Task.FromResult(user)); 34 | 35 | var result = await service.GetUniqueEmail(email, "12345"); 36 | 37 | Assert.Equal("a-at-b.com@12345.example.com", result); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/PopForums.Test/Services/UserSessionWorkerTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute.ExceptionExtensions; 2 | 3 | namespace PopForums.Test.Services; 4 | 5 | public class UserSessionWorkerTests 6 | { 7 | private IUserSessionService _userSessionService; 8 | private IErrorLog _errorLog; 9 | 10 | private UserSessionWorker GetWorker() 11 | { 12 | _userSessionService = Substitute.For(); 13 | _errorLog = Substitute.For(); 14 | return new UserSessionWorker(_userSessionService, _errorLog); 15 | } 16 | 17 | [Fact] 18 | public void NoErrorNoLog() 19 | { 20 | var worker = GetWorker(); 21 | _userSessionService.CleanUpExpiredSessions().Returns(Task.CompletedTask); 22 | 23 | worker.Execute(); 24 | 25 | _errorLog.DidNotReceive().Log(Arg.Any(), Arg.Any()); 26 | } 27 | 28 | [Fact] 29 | public void LogWhenThrows() 30 | { 31 | var worker = GetWorker(); 32 | _userSessionService.CleanUpExpiredSessions().ThrowsAsync(); 33 | 34 | worker.Execute(); 35 | 36 | _errorLog.Received().Log(Arg.Any(), ErrorSeverity.Error); 37 | } 38 | } -------------------------------------------------------------------------------- /src/PopForums.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace PopForums.Mvc.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | public IActionResult Index() 8 | { 9 | return View(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/PopForums.Web/PopForums.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 21.0.0 6 | PopForums.Web 7 | PopForums.Web 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/PopForums.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PopForumsKestrel": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:5091;http://localhost:5090", 10 | "dotnetRunMessages": true 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/PopForums.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |

forums

6 | 7 |

8 | PopForums - v@(System.Reflection.Assembly.Load("PopForums").GetName().Version)
9 | PopForums.Sql - v@(System.Reflection.Assembly.Load("PopForums.Sql").GetName().Version)
10 | PopForums.Mvc - v@(System.Reflection.Assembly.Load("PopForums.Mvc").GetName().Version)
11 | PopForums.Web - v@(System.Reflection.Assembly.Load("PopForums.Web").GetName().Version)
12 |

-------------------------------------------------------------------------------- /src/PopForums.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | @await RenderSectionAsync("HeaderContent", false) 9 | 10 | 11 |
12 | @RenderBody() 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src/PopForums.Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using PopForums.Mvc 2 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 3 | -------------------------------------------------------------------------------- /src/PopForums.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/PopForums.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/POPWorldMedia/POPForums/2a6d6e5cb34b37b5ae26b5006eabf24efbcda319/src/PopForums.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/PopForums/Composers/ForumStateComposer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Composers; 2 | 3 | public interface IForumStateComposer 4 | { 5 | ForumState GetState(Forum forum, PagerContext pagerContext); 6 | } 7 | 8 | public class ForumStateComposer : IForumStateComposer 9 | { 10 | public ForumState GetState(Forum forum, PagerContext pagerContext) 11 | { 12 | var forumState = new ForumState {ForumID = forum?.ForumID, PageSize = pagerContext.PageSize, PageIndex = pagerContext.PageIndex}; 13 | return forumState; 14 | } 15 | } -------------------------------------------------------------------------------- /src/PopForums/Composers/ResourceComposer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Composers; 2 | 3 | public interface IResourceComposer 4 | { 5 | dynamic GetForCurrentThread(); 6 | } 7 | 8 | public class ResourceComposer : IResourceComposer 9 | { 10 | public dynamic GetForCurrentThread() 11 | { 12 | var resources = new 13 | { 14 | LessThanMinute = Resources.LessThanMinute, 15 | OneMinuteAgo = Resources.OneMinuteAgo, 16 | MinutesAgo = Resources.MinutesAgo, 17 | TodayTime = Resources.TodayTime, 18 | YesterdayTime = Resources.YesterdayTime, 19 | 20 | Notifications = Resources.Notifications, 21 | NewReplyNotification = Resources.NewReplyNotification, 22 | Award = Resources.Award, 23 | VoteUpNotification = Resources.VoteUpNotification, 24 | QuestionAnsweredNotification = Resources.QuestionAnsweredNotification, 25 | Send = Resources.Send, 26 | 27 | UploadImage = Resources.UploadImage 28 | }; 29 | return resources; 30 | } 31 | } -------------------------------------------------------------------------------- /src/PopForums/Configuration/ErrorLogException.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Configuration; 2 | 3 | public class ErrorLogException : Exception 4 | { 5 | public ErrorLogException(string message) : base(message) 6 | { 7 | 8 | } 9 | 10 | public override string Message => "Can't log exception: " + base.Message; 11 | } -------------------------------------------------------------------------------- /src/PopForums/Configuration/ErrorSeverity.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Configuration; 2 | 3 | public enum ErrorSeverity 4 | { 5 | Critical = 1, 6 | Warning = 2, 7 | Information = 3, 8 | Debug = 4, 9 | Error = 5, 10 | Email = 6 11 | } -------------------------------------------------------------------------------- /src/PopForums/Email/EmailQueuePayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Email; 2 | 3 | public class EmailQueuePayload 4 | { 5 | public int MessageID { get; set; } 6 | public EmailQueuePayloadType EmailQueuePayloadType { get; set; } 7 | public string ToEmail { get; set; } 8 | public string ToName { get; set; } 9 | public string TenantID { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Email/EmailQueuePayloadType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Email; 2 | 3 | public enum EmailQueuePayloadType 4 | { 5 | FullMessage = 1, 6 | MassMessage = 2, 7 | DeleteMassMessage = 3 8 | } -------------------------------------------------------------------------------- /src/PopForums/Email/SmtpStatusCode.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Email; 2 | 3 | public enum SmtpStatusCode 4 | { 5 | SystemStatus = 211, 6 | HelpMessage = 214, 7 | ServiceReady = 220, 8 | ServiceClosingTransmissionChannel = 221, 9 | AuthenticationSuccessful = 235, 10 | Ok = 250, 11 | UserNotLocalWillForward = 251, 12 | CannotVerifyUserWillAttemptDelivery = 252, 13 | AuthenticationChallenge = 334, 14 | StartMailInput = 354, 15 | ServiceNotAvailable = 421, 16 | PasswordTransitionNeeded = 432, 17 | MailboxBusy = 450, 18 | ErrorInProcessing = 451, 19 | InsufficientStorage = 452, 20 | TemporaryAuthenticationFailure = 454, 21 | CommandUnrecognized = 500, 22 | SyntaxError = 501, 23 | CommandNotImplemented = 502, 24 | BadCommandSequence = 503, 25 | CommandParameterNotImplemented = 504, 26 | AuthenticationRequired = 530, 27 | AuthenticationMechanismTooWeak = 534, 28 | AuthenticationInvalidCredentials = 535, 29 | EncryptionRequiredForAuthenticationMechanism = 538, 30 | MailboxUnavailable = 550, 31 | UserNotLocalTryAlternatePath = 551, 32 | ExceededStorageAllocation = 552, 33 | MailboxNameNotAllowed = 553, 34 | TransactionFailed = 554, 35 | MailFromOrRcptToParametersNotRecognizedOrNotImplemented = 555 36 | } -------------------------------------------------------------------------------- /src/PopForums/Extensions/Streams.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Extensions; 2 | 3 | public static class Streams 4 | { 5 | public static byte[] ToBytes(this Stream stream) 6 | { 7 | var length = (int)stream.Length; 8 | var bytes = new byte[length]; 9 | stream.Read(bytes, 0, length); 10 | return bytes; 11 | } 12 | } -------------------------------------------------------------------------------- /src/PopForums/Extensions/Users.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Extensions; 2 | 3 | public static class Users 4 | { 5 | public static bool IsPostEditable(this User user, Post post) 6 | { 7 | if (user == null) 8 | return false; 9 | return user.IsInRole(PermanentRoles.Moderator) || user.UserID == post.UserID; 10 | } 11 | } -------------------------------------------------------------------------------- /src/PopForums/ExternalLogin/ExternalAuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ExternalLogin; 2 | 3 | public class ExternalAuthenticationResult 4 | { 5 | public string Issuer { get; set; } 6 | public string ProviderKey { get; set; } 7 | public string Name { get; set; } 8 | public string Email { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/ExternalLogin/ExternalLoginInfo.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ExternalLogin; 2 | 3 | public class ExternalLoginInfo 4 | { 5 | public ExternalLoginInfo(string loginProvider, string providerKey, 6 | string displayName) 7 | { 8 | LoginProvider = loginProvider; 9 | ProviderKey = providerKey; 10 | ProviderDisplayName = displayName; 11 | } 12 | 13 | public string LoginProvider { get; set; } 14 | public string ProviderKey { get; set; } 15 | public string ProviderDisplayName { get; set; } 16 | } -------------------------------------------------------------------------------- /src/PopForums/ExternalLogin/ExternalUserAssociation.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ExternalLogin; 2 | 3 | public class ExternalUserAssociation 4 | { 5 | public int ExternalUserAssociationID { get; set; } 6 | public int UserID { get; set; } 7 | public string Issuer { get; set; } 8 | public string ProviderKey { get; set; } 9 | public string Name { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/ExternalLogin/ExternalUserAssociationMatchResult.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ExternalLogin; 2 | 3 | public class ExternalUserAssociationMatchResult 4 | { 5 | public bool Successful { get; set; } 6 | public ExternalUserAssociation ExternalUserAssociation { get; set; } 7 | public User User { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Feeds/FeedService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Feeds; 2 | 3 | public interface IFeedService 4 | { 5 | Task PublishToFeed(User user, string message, int points, DateTime timeStamp); 6 | Task> GetFeed(User user); 7 | } 8 | 9 | public class FeedService : IFeedService 10 | { 11 | 12 | public FeedService(IFeedRepository feedRepository, IBroker broker) 13 | { 14 | _feedRepository = feedRepository; 15 | _broker = broker; 16 | } 17 | 18 | private readonly IFeedRepository _feedRepository; 19 | private readonly IBroker _broker; 20 | 21 | public const int MaxFeedCount = 50; 22 | 23 | public async Task PublishToFeed(User user, string message, int points, DateTime timeStamp) 24 | { 25 | if (user == null) 26 | return; 27 | await _feedRepository.PublishEvent(user.UserID, message, points, timeStamp); 28 | var cutOff = await _feedRepository.GetOldestTime(user.UserID, MaxFeedCount); 29 | await _feedRepository.DeleteOlderThan(user.UserID, cutOff); 30 | } 31 | 32 | public async Task> GetFeed(User user) 33 | { 34 | return await _feedRepository.GetFeed(user.UserID, MaxFeedCount); 35 | } 36 | } -------------------------------------------------------------------------------- /src/PopForums/Global.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections; 3 | global using System.Collections.Generic; 4 | global using System.IO; 5 | global using System.Linq; 6 | global using System.Net.Http; 7 | global using System.Security; 8 | global using System.Security.Cryptography; 9 | global using System.Text; 10 | global using System.Text.Json; 11 | global using System.Text.Json.Serialization; 12 | global using System.Text.RegularExpressions; 13 | global using System.Threading; 14 | global using System.Threading.Tasks; 15 | 16 | global using Microsoft.Extensions.Configuration; 17 | global using Microsoft.Extensions.DependencyInjection; 18 | 19 | global using PopForums.Composers; 20 | global using PopForums.Configuration; 21 | global using PopForums.Email; 22 | global using PopForums.Extensions; 23 | global using PopForums.ExternalLogin; 24 | global using PopForums.Feeds; 25 | global using PopForums.Messaging; 26 | global using PopForums.Models; 27 | global using PopForums.Repositories; 28 | global using PopForums.ScoringGame; 29 | global using PopForums.Services; -------------------------------------------------------------------------------- /src/PopForums/Messaging/IBroker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging; 2 | 3 | public interface IBroker 4 | { 5 | void NotifyNewPosts(Topic topic, int lasPostID); 6 | void NotifyForumUpdate(Forum forum); 7 | void NotifyTopicUpdate(Topic topic, Forum forum, string topicLink); 8 | void NotifyNewPost(Topic topic, int postID); 9 | void NotifyPMCount(int userID, int pmCount); 10 | void NotifyUser(Notification notification); 11 | void NotifyUser(Notification notification, string tenantID); 12 | void SendPMMessage(PrivateMessagePost post); 13 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/AwardData.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class AwardData 4 | { 5 | public string Title { get; set; } 6 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/AwardPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class AwardPayload 4 | { 5 | public string Title { get; set; } 6 | public int UserID { get; set; } 7 | public string TenantID { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/QuestionData.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class QuestionData 4 | { 5 | public string AskerName { get; set; } 6 | public string Title { get; set; } 7 | public int PostID { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/ReplyData.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class ReplyData 4 | { 5 | public string PostName { get; set; } 6 | public int TopicID { get; set; } 7 | public string Title { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/ReplyPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class ReplyPayload 4 | { 5 | public string PostName { get; set; } 6 | public string Title { get; set; } 7 | public int TopicID { get; set; } 8 | public int UserID { get; set; } 9 | public string TenantID { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Models/VoteData.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging.Models; 2 | 3 | public class VoteData 4 | { 5 | public string VoterName { get; set; } 6 | public string Title { get; set; } 7 | public int PostID { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/Notification.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging; 2 | 3 | public class Notification 4 | { 5 | public int UserID { get; set; } 6 | public DateTime TimeStamp { get; set; } 7 | public bool IsRead { get; set; } 8 | public NotificationType NotificationType { get; set; } 9 | public long ContextID { get; set; } 10 | public JsonElement Data { get; set; } 11 | public int UnreadCount { get; set; } 12 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/NotificationTunnel.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging; 2 | 3 | public interface INotificationTunnel 4 | { 5 | void SendNotificationForUserAward(string title, int userID, string tenantID); 6 | void SendNotificationForReply(string postName, string title, int topicID, int userID, string tenantID); 7 | } 8 | 9 | public class NotificationTunnel : INotificationTunnel 10 | { 11 | public static string HeaderName = "PfApi"; 12 | 13 | private readonly INotificationAdapter _notificationAdapter; 14 | 15 | public NotificationTunnel(INotificationAdapter notificationAdapter) 16 | { 17 | _notificationAdapter = notificationAdapter; 18 | } 19 | 20 | public async void SendNotificationForUserAward(string title, int userID, string tenantID) 21 | { 22 | await _notificationAdapter.Award(title, userID, tenantID); 23 | } 24 | 25 | public async void SendNotificationForReply(string postName, string title, int topicID, int userID, string tenantID) 26 | { 27 | await _notificationAdapter.Reply(postName, title, topicID, userID, tenantID); 28 | } 29 | } -------------------------------------------------------------------------------- /src/PopForums/Messaging/NotificationType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Messaging; 2 | 3 | public enum NotificationType 4 | { 5 | NewReply = 0, 6 | VoteUp = 1, 7 | QuestionAnswered = 2, 8 | Award = 3 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/AwardCalculationPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class AwardCalculationPayload 4 | { 5 | public string EventDefinitionID { get; set; } 6 | public int UserID { get; set; } 7 | public string TenantID { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/BasicJsonMessage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class BasicJsonMessage 4 | { 5 | public bool Result { get; set; } 6 | public string Message { get; set; } 7 | public object Data { get; set; } 8 | public string Redirect { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/BasicServiceResponse.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class BasicServiceResponse where T : class 4 | { 5 | public bool IsSuccessful { get; set; } 6 | public string Message { get; set; } 7 | public T Data { get; set; } 8 | public string Redirect { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/CategorizedForumContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class CategorizedForumContainer 4 | { 5 | public CategorizedForumContainer(IEnumerable categories, IEnumerable forums) 6 | { 7 | ReadStatusLookup = new Dictionary(); 8 | AllCategories = categories; 9 | AllForums = forums; 10 | UncategorizedForums = forums.Where(f => !f.CategoryID.HasValue || f.CategoryID == 0).OrderBy(f => f.SortOrder).ToList(); 11 | CategoryDictionary = new Dictionary>(); 12 | foreach (var category in AllCategories.OrderBy(c => c.SortOrder)) 13 | { 14 | var forumSet = AllForums.Where(f => f.CategoryID == category.CategoryID).OrderBy(f => f.SortOrder).ToList(); 15 | if (forumSet.Count > 0) 16 | CategoryDictionary.Add(category, forumSet); 17 | } 18 | } 19 | 20 | public IEnumerable AllCategories { get; private set; } 21 | public IEnumerable AllForums { get; private set; } 22 | public List UncategorizedForums { get; private set; } 23 | public Dictionary> CategoryDictionary { get; private set; } 24 | public string ForumTitle { get; set; } 25 | public Dictionary ReadStatusLookup { get; private set; } 26 | } -------------------------------------------------------------------------------- /src/PopForums/Models/Category.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class Category 4 | { 5 | public int CategoryID { get; set; } 6 | public string Title { get; set; } 7 | public int SortOrder { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/CategoryContainerWithForums.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class CategoryContainerWithForums 4 | { 5 | public Category Category { get; set; } 6 | public IEnumerable Forums { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ClientPrivateMessagePost.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ClientPrivateMessagePost 4 | { 5 | public int PMPostID { get; set; } 6 | public int UserID { get; set; } 7 | public string Name { get; set; } 8 | public string PostTime { get; set; } 9 | public string FullText { get; set; } 10 | 11 | public static ClientPrivateMessagePost[] MapForClient(List posts) 12 | { 13 | var messages = posts.Select(x => new ClientPrivateMessagePost { PMPostID = x.PMPostID, UserID = x.UserID, Name = x.Name, PostTime = x.PostTime.ToString("o"), FullText = x.FullText }).ToArray(); 14 | return messages; 15 | } 16 | 17 | public static ClientPrivateMessagePost MapForClient(PrivateMessagePost post) 18 | { 19 | var message = new ClientPrivateMessagePost { PMPostID = post.PMPostID, UserID = post.UserID, Name = post.Name, PostTime = post.PostTime.ToString("o"), FullText = post.FullText }; 20 | return message; 21 | } 22 | } -------------------------------------------------------------------------------- /src/PopForums/Models/EmailMessage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class EmailMessage 4 | { 5 | public string FromName { get; set; } 6 | public string ToEmail { get; set; } 7 | public string ToName { get; set; } 8 | public string Subject { get; set; } 9 | public string Body { get; set; } 10 | public string HtmlBody { get; set; } 11 | public string ReplyTo { get; set; } 12 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ErrorLogEntry.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ErrorLogEntry 4 | { 5 | public int ErrorID { get; set; } 6 | public DateTime TimeStamp { get; set; } 7 | public string Message { get; set; } 8 | public string StackTrace { get; set; } 9 | public string Data { get; set; } 10 | public ErrorSeverity Severity { get; set; } 11 | public string SeverityString => Severity.ToString(); 12 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ExpiredUserSession.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ExpiredUserSession 4 | { 5 | public int SessionID { get; set; } 6 | public int? UserID { get; set; } 7 | public DateTime LastTime { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/FeedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class FeedEvent 4 | { 5 | public int UserID { get; set; } 6 | public string Message { get; set; } 7 | public int Points { get; set; } 8 | public DateTime TimeStamp { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/Forum.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class Forum 4 | { 5 | public int ForumID { get; set; } 6 | public int? CategoryID { get; set; } 7 | public string Title { get; set; } 8 | public string Description { get; set; } 9 | public bool IsVisible { get; set; } 10 | public bool IsArchived { get; set; } 11 | public int SortOrder { get; set; } 12 | public int TopicCount { get; set; } 13 | public int PostCount { get; set; } 14 | public DateTime LastPostTime { get; set; } 15 | public string LastPostName { get; set; } 16 | public string UrlName { get; set; } 17 | public string ForumAdapterName { get; set; } 18 | public bool IsQAForum { get; set; } 19 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ForumPermissionContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ForumPermissionContainer 4 | { 5 | public int ForumID { get; set; } 6 | public List AllRoles { get; set; } 7 | public List PostRoles { get; set; } 8 | public List ViewRoles { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ForumPermissionContext.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ForumPermissionContext 4 | { 5 | public bool UserCanView { get; set; } 6 | public bool UserCanPost { get; set; } 7 | public bool UserCanModerate { get; set; } 8 | public string DenialReason { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ForumState.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ForumState 4 | { 5 | public int? ForumID { get; set; } 6 | public int PageSize { get; set; } 7 | public int PageIndex { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ForumTopicContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ForumTopicContainer : PagedTopicContainer 4 | { 5 | public Forum Forum { get; set; } 6 | public ForumPermissionContext PermissionContext { get; set; } 7 | public ForumState ForumState { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/IPHistoryEvent.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class IPHistoryEvent 4 | { 5 | public DateTime EventTime { get; set; } 6 | public string Type { get; set; } 7 | public string Description { get; set; } 8 | public int? UserID { get; set; } 9 | public string Name { get; set; } 10 | public object ID { get; set; } 11 | } -------------------------------------------------------------------------------- /src/PopForums/Models/IStreamResponse.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | /// 4 | /// This interface is used to pass a Stream to send to the client. It implements IDisposable to allow for cleanup 5 | /// of the Stream and any other unmanaged resources (like the database connections, for example). Because using 6 | /// statements would fall out of scope when a controller action returns, the Stream would be disposed before it 7 | /// could be used. This interface allows for the Stream to be used and then disposed of when the client is done by 8 | /// registering it with the HttpResponse RegisterForDispose method. 9 | /// 10 | public interface IStreamResponse : IDisposable 11 | { 12 | Stream Stream { get; } 13 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ModerationLogEntry.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ModerationLogEntry 4 | { 5 | public int ModerationID { get; set; } 6 | public DateTime TimeStamp { get; set; } 7 | public int UserID { get; set; } 8 | public string UserName { get; set; } 9 | public ModerationType ModerationType { get; set; } 10 | public string ModerationTypeString => ModerationType.ToString(); 11 | public int? ForumID { get; set; } 12 | public int TopicID { get; set; } 13 | public int? PostID { get; set; } 14 | public string Comment { get; set; } 15 | public string OldText { get; set; } 16 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ModerationType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public enum ModerationType 4 | { 5 | NotSet = 0, 6 | PostEdit = 1, 7 | PostDelete = 2, 8 | PostDeletePermanently = 3, 9 | TopicEdit = 4, 10 | TopicDelete = 5, 11 | TopicDeletePermanently = 6, 12 | TopicClose = 7, 13 | TopicOpen = 8, 14 | TopicPin = 9, 15 | TopicUnpin = 10, 16 | TopicMoved = 11, 17 | TopicUndelete = 12, 18 | PostUndelete = 13, 19 | TopicRenamed = 14, 20 | TopicCloseAuto = 15 21 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ModifyForumRolesContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ModifyForumRolesContainer 4 | { 5 | public int ForumID { get; set; } 6 | public ModifyForumRolesType ModifyType { get; set; } 7 | public string Role { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ModifyForumRolesType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public enum ModifyForumRolesType 4 | { 5 | AddView, 6 | RemoveView, 7 | AddPost, 8 | RemovePost, 9 | RemoveAllView, 10 | RemoveAllPost 11 | } -------------------------------------------------------------------------------- /src/PopForums/Models/NewPost.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class NewPost 4 | { 5 | public string Title { get; set; } 6 | public string FullText { get; set; } 7 | public bool IncludeSignature { get; set; } 8 | /// 9 | /// The ForumID or TopicID the post will be associated with. 10 | /// 11 | public int ItemID { get; set; } 12 | public bool CloseOnReply { get; set; } 13 | public bool IsPlainText { get; set; } 14 | public bool IsImageEnabled { get; set; } 15 | public int ParentPostID { get; set; } 16 | public string[] PostImageIDs { get; set; } 17 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PagedListOfT.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PagedList : PagerContext where T : class 4 | { 5 | public IEnumerable List { get; set; } 6 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PagedTopicContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PagedTopicContainer 4 | { 5 | public PagedTopicContainer() 6 | { 7 | ReadStatusLookup = new Dictionary(); 8 | } 9 | 10 | public List Topics { get; set; } 11 | public PagerContext PagerContext { get; set; } 12 | public Dictionary ReadStatusLookup { get; private set; } 13 | public Dictionary ForumTitles { get; set; } 14 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PagerContext.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PagerContext 4 | { 5 | public int PageCount { get; set; } 6 | public int PageIndex { get; set; } 7 | public int PageSize { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PasswordResetContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PasswordResetContainer 4 | { 5 | public bool IsValidUser { get; set; } 6 | public string Password { get; set; } 7 | public string PasswordRetype { get; set; } 8 | public string Result { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PermanentRoles.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public static class PermanentRoles 4 | { 5 | public const string Admin = "Admin"; 6 | public const string Moderator = "Moderator"; 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/Post.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class Post 4 | { 5 | public int PostID { get; set; } 6 | public int TopicID { get; set; } 7 | public int ParentPostID { get; set; } 8 | public string IP { get; set; } 9 | public bool IsFirstInTopic { get; set; } 10 | public bool ShowSig { get; set; } 11 | public int UserID { get; set; } 12 | public string Name { get; set; } 13 | public string Title { get; set; } 14 | public string FullText { get; set; } 15 | public DateTime PostTime { get; set; } 16 | public bool IsEdited { get; set; } 17 | public string LastEditName { get; set; } 18 | public DateTime? LastEditTime { get; set; } 19 | public bool IsDeleted { get; set; } 20 | public int Votes { get; set; } 21 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PostEdit.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PostEdit 4 | { 5 | public PostEdit() {} 6 | 7 | public PostEdit(Post post) 8 | { 9 | Title = post.Title; 10 | FullText = post.FullText; 11 | ShowSig = post.ShowSig; 12 | } 13 | 14 | public string Title { get; set; } 15 | public string FullText { get; set; } 16 | public bool ShowSig { get; set; } 17 | public string Comment { get; set; } 18 | public bool IsPlainText { get; set; } 19 | public bool IsFirstInTopic { get; set; } 20 | public string[] PostImageIDs { get; set; } 21 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PostImage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PostImage 4 | { 5 | public string ID { get; init; } 6 | public DateTime TimeStamp { get; init; } 7 | public string TenantID { get; init; } 8 | public string ContentType { get; init; } 9 | public byte[] ImageData { get; init; } 10 | 11 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PostImagePersistPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PostImagePersistPayload 4 | { 5 | public string Url { get; set; } 6 | public string ID { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PostItemContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PostItemContainer 4 | { 5 | public Post Post { get; set; } 6 | public List VotedPostIDs { get; set; } 7 | public Dictionary Signatures { get; set; } 8 | public Dictionary Avatars { get; set; } 9 | public User User { get; set; } 10 | public Profile Profile { get; set; } 11 | public Topic Topic { get; set; } 12 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PostWithChildren.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PostWithChildren 4 | { 5 | public Post Post { get; set; } 6 | public List Children { get; set; } 7 | public DateTime? LastReadTime { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PrivateMessage 4 | { 5 | public int PMID { get; set; } 6 | public DateTime LastPostTime { get; set; } 7 | public JsonElement Users { get; set; } 8 | public DateTime LastViewDate { get; set; } 9 | 10 | public static string GetUserNames(PrivateMessage pm, int excludeUserID) 11 | { 12 | var users = pm.Users.Deserialize(new JsonSerializerOptions{PropertyNamingPolicy = JsonNamingPolicy.CamelCase}); 13 | if (users == null) 14 | return string.Empty; 15 | var names = new List(); 16 | foreach (var item in users) 17 | { 18 | if (item.UserID != excludeUserID) 19 | names.Add(item.Name as string); 20 | } 21 | var result = string.Join(", ", names); 22 | return result; 23 | } 24 | 25 | private class UserNamePair 26 | { 27 | public int UserID { get; set; } 28 | public string Name { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessageBoxType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public enum PrivateMessageBoxType 4 | { 5 | Inbox = 1, 6 | Archive = 2 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessagePost.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PrivateMessagePost 4 | { 5 | public int PMPostID { get; set; } 6 | public int PMID { get; set; } 7 | public int UserID { get; set; } 8 | public string Name { get; set; } 9 | public DateTime PostTime { get; set; } 10 | public string FullText { get; set; } 11 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessageState.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PrivateMessageState 4 | { 5 | public int PmID { get; set; } 6 | public JsonElement Users { get; set; } 7 | public ClientPrivateMessagePost[] Messages { get; set; } 8 | public int? NewestPostID { get; set; } 9 | public bool IsUserNotFound { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessageUser.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PrivateMessageUser 4 | { 5 | public int PMID { get; set; } 6 | public int UserID { get; set; } 7 | public DateTime LastViewDate { get; set; } 8 | public bool IsArchived { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/PrivateMessageView.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class PrivateMessageView 4 | { 5 | public PrivateMessage PrivateMessage { get; set; } 6 | public PrivateMessageState State { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/Profile.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class Profile 4 | { 5 | public int UserID { get; set; } 6 | public bool IsSubscribed { get; set; } 7 | public string Signature { get; set; } 8 | public bool ShowDetails { get; set; } 9 | public string Location { get; set; } 10 | public bool IsPlainText { get; set; } 11 | public DateTime? Dob { get; set; } 12 | public string Web { get; set; } 13 | public string Facebook { get; set; } 14 | public string Instagram { get; set; } 15 | public bool IsTos { get; set; } 16 | public int? AvatarID { get; set; } 17 | public int? ImageID { get; set; } 18 | public bool HideVanity { get; set; } 19 | public int? LastPostID { get; set; } 20 | public int Points { get; set; } 21 | public bool IsAutoFollowOnReply { get; set; } 22 | } -------------------------------------------------------------------------------- /src/PopForums/Models/QAPostItemContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class QAPostItemContainer : PostItemContainer 4 | { 5 | public PostWithChildren PostWithChildren { get; set; } 6 | } -------------------------------------------------------------------------------- /src/PopForums/Models/QueuedEmailMessage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class QueuedEmailMessage : EmailMessage 4 | { 5 | public int MessageID { get; set; } 6 | public DateTime QueueTime { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ReadStatus.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | [Flags] 4 | public enum ReadStatus 5 | { 6 | NoNewPosts = 1, 7 | NewPosts = 2, 8 | Closed = 4, 9 | Open = 8, 10 | Pinned = 16, 11 | NotPinned = 32 12 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ResponseOfT.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | /// 4 | /// A generic container for wrapping the response of external calls for consumption by internal service. 5 | /// 6 | /// 7 | public class Response where T : class 8 | { 9 | /// 10 | /// Creates a generic Response with IsValid set to true and no debug information or exception. 11 | /// 12 | /// The strongly typed result to be consumed by the caller. 13 | public Response(T data) 14 | { 15 | Data = data; 16 | IsValid = true; 17 | } 18 | 19 | /// 20 | /// Creates a generic response with all fields set. 21 | /// 22 | /// 23 | /// Default is false. 24 | /// Default is null. 25 | /// Default is null. 26 | public Response(T data, bool isValid = false, Exception exception = null, string debugInfo = null) 27 | { 28 | Data = data; 29 | IsValid = isValid; 30 | Exception = exception; 31 | DebugInfo = debugInfo; 32 | } 33 | 34 | public T Data { get; } 35 | public bool IsValid { get; } 36 | public Exception Exception { get; } 37 | public string DebugInfo { get; } 38 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SearchIndexPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SearchIndexPayload 4 | { 5 | public int TopicID { get; set; } 6 | public string TenantID { get; set; } 7 | public bool IsForRemoval { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SearchType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public enum SearchType 4 | { 5 | Rank, 6 | Date, 7 | Title, 8 | Name, 9 | Replies 10 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SearchWord.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SearchWord 4 | { 5 | public string Word { get; set; } 6 | public int TopicID { get; set; } 7 | public int Rank { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SecurityLogEntry.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SecurityLogEntry 4 | { 5 | public int SecurityLogID { get; set; } 6 | public SecurityLogType SecurityLogType { get; set; } 7 | public string SecurityLogTypeString => SecurityLogType.ToString(); 8 | public int? UserID { get; set; } 9 | public int? TargetUserID { get; set; } 10 | public string IP { get; set; } 11 | public string Message { get; set; } 12 | public DateTime ActivityDate { get; set; } 13 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SecurityLogType.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public enum SecurityLogType 4 | { 5 | Undefined = 0, 6 | Login = 1, 7 | Logout = 2, 8 | PasswordChange = 3, 9 | EmailChange = 4, 10 | FailedLogin = 5, 11 | UserCreated = 6, 12 | UserDeleted = 7, 13 | RoleCreated = 8, 14 | RoleDeleted = 9, 15 | UserAddedToRole = 10, 16 | UserRemovedFromRole = 11, 17 | UserSessionStart = 12, 18 | UserSessionEnd = 13, 19 | NameChange = 14, 20 | IsApproved = 15, 21 | IsNotApproved = 16, 22 | ExternalAssociationSet = 17, 23 | ExternalAssociationRemoved = 18, 24 | ExternalAssociationCheckSuccessful = 19, 25 | ExternalAssociationCheckFailed = 20, 26 | ReCaptchaFailed = 21, 27 | ExternalLoginChallengeFailed = 22 28 | } -------------------------------------------------------------------------------- /src/PopForums/Models/ServiceHeartbeat.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class ServiceHeartbeat 4 | { 5 | public string ServiceName { get; set; } 6 | public string MachineName { get; set; } 7 | public DateTime LastRun { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SetupVariables.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SetupVariables 4 | { 5 | public string Name { get; set; } 6 | public string Email { get; set; } 7 | public string Password { get; set; } 8 | public string ForumTitle { get; set; } 9 | public string SmtpServer { get; set; } 10 | public int SmtpPort { get; set; } 11 | public string MailerAddress { get; set; } 12 | public bool UseEsmtp { get; set; } 13 | public string SmtpUser { get; set; } 14 | public string SmtpPassword { get; set; } 15 | public bool UseSslSmtp { get; set; } 16 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SignupData.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SignupData 4 | { 5 | public string Name { get; set; } 6 | public string Email { get; set; } 7 | public string Password { get; set; } 8 | public string PasswordRetype { get; set; } 9 | public bool IsSubscribed { get; set; } 10 | public bool IsCoppa { get; set; } 11 | public bool IsTos { get; set; } 12 | public string Token { get; set; } 13 | public bool IsAutoFollowOnReply { get; set; } 14 | 15 | public static string GetCoppaDate() 16 | { 17 | return DateTime.Now.AddYears(-13).ToString("D"); 18 | } 19 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SingleString.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SingleString 4 | { 5 | public string String { get; set; } 6 | } -------------------------------------------------------------------------------- /src/PopForums/Models/SubscribeNotificationPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class SubscribeNotificationPayload 4 | { 5 | public int TopicID { get; set; } 6 | public string TopicTitle { get; set; } 7 | public int PostingUserID { get; set; } 8 | public string PostingUserName { get; set; } 9 | public string TenantID { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Models/TimeFormats.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class TimeFormats 4 | { 5 | public string TodayTime { get; set; } 6 | public string YesterdayTime { get; set; } 7 | public string MinutesAgo { get; set; } 8 | public string OneMinuteAgo { get; set; } 9 | public string LessThanMinute { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Models/Topic.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class Topic 4 | { 5 | public int TopicID { get; set; } 6 | public int ForumID { get; set; } 7 | public string Title { get; set; } 8 | public int ReplyCount { get; set; } 9 | public int ViewCount { get; set; } 10 | public int StartedByUserID { get; set; } 11 | public string StartedByName { get; set; } 12 | public int LastPostUserID { get; set; } 13 | public string LastPostName { get; set; } 14 | public DateTime LastPostTime { get; set; } 15 | public bool IsClosed { get; set; } 16 | public bool IsPinned { get; set; } 17 | public bool IsDeleted { get; set; } 18 | public string UrlName { get; set; } 19 | public int? AnswerPostID { get; set; } 20 | } -------------------------------------------------------------------------------- /src/PopForums/Models/TopicContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class TopicContainer 4 | { 5 | public Forum Forum { get; set; } 6 | public Topic Topic { get; set; } 7 | public List Posts { get; set; } 8 | public PagerContext PagerContext { get; set; } 9 | public ForumPermissionContext PermissionContext { get; set; } 10 | public Dictionary Signatures { get; set; } 11 | public Dictionary Avatars { get; set; } 12 | public List VotedPostIDs { get; set; } 13 | public DateTime? LastReadTime { get; set; } 14 | public TopicState TopicState { get; set; } 15 | } -------------------------------------------------------------------------------- /src/PopForums/Models/TopicContainerForQA.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class TopicContainerForQA : TopicContainer 4 | { 5 | public PostWithChildren QuestionPostWithComments { get; set; } 6 | public List AnswersWithComments { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/TopicState.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class TopicState 4 | { 5 | public int TopicID { get; set; } 6 | public bool IsImageEnabled { get; set; } 7 | public bool IsSubscribed { get; set; } 8 | public bool IsFavorite { get; set; } 9 | public int? PageIndex { get; set; } 10 | public int? PageCount { get; set; } 11 | public int LastVisiblePostID { get; set; } 12 | public int? AnswerPostID { get; set; } 13 | } -------------------------------------------------------------------------------- /src/PopForums/Models/TopicUnsubscribeContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class TopicUnsubscribeContainer 4 | { 5 | public User User { get; set; } 6 | public Topic Topic { get; set; } 7 | } -------------------------------------------------------------------------------- /src/PopForums/Models/User.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class User 4 | { 5 | public int UserID { get; set; } 6 | public DateTime CreationDate { get; set; } 7 | public string Name { get; set; } 8 | public string Email { get; set; } 9 | public Guid AuthorizationKey { get; set; } 10 | public bool IsApproved { get; set; } 11 | public DateTime? TokenExpiration { get; set; } 12 | public List Roles { get; set; } 13 | 14 | public bool IsInRole(string role) 15 | { 16 | if (Roles == null) 17 | throw new Exception("Roles not set for user."); 18 | return Roles.Contains(role); 19 | } 20 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserEditProfile.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserEditProfile 4 | { 5 | public UserEditProfile() {} 6 | 7 | public UserEditProfile(Profile profile) 8 | { 9 | IsSubscribed = profile.IsSubscribed; 10 | Signature = profile.Signature; 11 | ShowDetails = profile.ShowDetails; 12 | Location = profile.Location; 13 | IsPlainText = profile.IsPlainText; 14 | Dob = profile.Dob; 15 | Web = profile.Web; 16 | Instagram = profile.Instagram; 17 | Facebook = profile.Facebook; 18 | HideVanity = profile.HideVanity; 19 | IsAutoFollowOnReply = profile.IsAutoFollowOnReply; 20 | } 21 | 22 | public bool IsSubscribed { get; set; } 23 | public string Signature { get; set; } 24 | public bool ShowDetails { get; set; } 25 | public string Location { get; set; } 26 | public bool IsPlainText { get; set; } 27 | public DateTime? Dob { get; set; } 28 | public string Web { get; set; } 29 | public string Instagram { get; set; } 30 | public string Facebook { get; set; } 31 | public bool HideVanity { get; set; } 32 | public bool IsAutoFollowOnReply { get; set; } 33 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserEditSecurity.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserEditSecurity 4 | { 5 | public UserEditSecurity() {} 6 | 7 | public UserEditSecurity(User user, bool isNewUserApproved) 8 | { 9 | OldEmail = user.Email; 10 | IsNewUserApproved = isNewUserApproved; 11 | } 12 | 13 | public string OldPassword { get; set; } 14 | public string NewPassword { get; set; } 15 | public string NewPasswordRetype { get; set; } 16 | public string OldEmail { get; private set; } 17 | public string NewEmail { get; set; } 18 | public string NewEmailRetype { get; set; } 19 | public bool IsNewUserApproved { get; set; } 20 | 21 | public bool NewPasswordsMatch() 22 | { 23 | if (String.IsNullOrWhiteSpace(NewPassword) || String.IsNullOrWhiteSpace(NewPasswordRetype)) 24 | return false; 25 | return NewPassword == NewPasswordRetype; 26 | } 27 | 28 | public bool NewEmailsMatch() 29 | { 30 | if (String.IsNullOrWhiteSpace(NewEmail) || String.IsNullOrWhiteSpace(NewEmailRetype)) 31 | return false; 32 | return NewEmail == NewEmailRetype; 33 | } 34 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserImage.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserImage 4 | { 5 | public int UserImageID { get; set; } 6 | public int UserID { get; set; } 7 | public int SortOrder { get; set; } 8 | public bool IsApproved { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserImageApprovalContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserImageApprovalContainer 4 | { 5 | public bool IsNewUserImageApproved { get; set; } 6 | public List Unapproved { get; set; } 7 | } 8 | 9 | public class UserImagePair 10 | { 11 | public User User { get; set; } 12 | public UserImage UserImage { get; set; } 13 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserResult.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserResult 4 | { 5 | public int UserID { get; set; } 6 | public string Name { get; set; } 7 | public string Email { get; set; } 8 | public DateTime CreationDate { get; set; } 9 | public string IP { get; set; } 10 | } -------------------------------------------------------------------------------- /src/PopForums/Models/UserSearch.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class UserSearch 4 | { 5 | public string SearchText { get; set; } 6 | public UserSearchType SearchType { get; set; } 7 | 8 | public enum UserSearchType 9 | { 10 | Name, Email, Role 11 | } 12 | } -------------------------------------------------------------------------------- /src/PopForums/Models/VotePostContainer.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Models; 2 | 3 | public class VotePostContainer 4 | { 5 | public int PostID { get; set; } 6 | public int Votes { get; set; } 7 | public Dictionary Voters { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IAwardCalculationQueueRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IAwardCalculationQueueRepository 4 | { 5 | Task Enqueue(AwardCalculationPayload payload); 6 | Task> Dequeue(); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IAwardConditionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IAwardConditionRepository 4 | { 5 | Task> GetConditions(string awardDefinitionID); 6 | Task DeleteConditions(string awardDefinitionID); 7 | Task SaveConditions(List conditions); 8 | Task DeleteConditionsByEventDefinitionID(string eventDefinitionID); 9 | Task DeleteCondition(string awardDefinitionID, string eventDefinitionID); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IAwardDefinitionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IAwardDefinitionRepository 4 | { 5 | Task Get(string awardDefinitionID); 6 | Task> GetAll(); 7 | Task> GetByEventDefinitionID(string eventDefinitionID); 8 | Task Create(string awardDefinitionID, string title, string description, bool isSingleTimeAward); 9 | Task Delete(string awardDefinitionID); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IBanRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IBanRepository 4 | { 5 | Task BanIP(string ip); 6 | Task RemoveIPBan(string ip); 7 | Task> GetIPBans(); 8 | Task IPIsBanned(string ip); 9 | Task BanEmail(string email); 10 | Task RemoveEmailBan(string email); 11 | Task> GetEmailBans(); 12 | Task EmailIsBanned(string email); 13 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ICategoryRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ICategoryRepository 4 | { 5 | Task Get(int categoryID); 6 | Task> GetAll(); 7 | Task Create(string newTitle, int sortOrder); 8 | Task Delete(int categoryID); 9 | Task Update(Category category); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IEmailQueueRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IEmailQueueRepository 4 | { 5 | Task Enqueue(EmailQueuePayload payload); 6 | Task Dequeue(); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IErrorLogRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IErrorLogRepository 4 | { 5 | Task Create(DateTime timeStamp, string message, string stackTrace, string data, ErrorSeverity severity); 6 | Task GetErrorCount(); 7 | Task> GetErrors(int startRow, int pageSize); 8 | Task DeleteError(int errorID); 9 | Task DeleteAllErrors(); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IEventDefinitionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IEventDefinitionRepository 4 | { 5 | Task Get(string eventDefinitionID); 6 | Task> GetAll(); 7 | Task Create(EventDefinition eventDefinition); 8 | void Delete(string eventDefinitionID); 9 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IExternalUserAssociationRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IExternalUserAssociationRepository 4 | { 5 | Task Get(string issuer, string providerKey); 6 | Task Get(int externalUserAssociationID); 7 | Task> GetByUser(int userID); 8 | Task Save(int userID, string issuer, string providerKey, string name); 9 | Task Delete(int externalUserAssociationID); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IFavoriteTopicsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IFavoriteTopicsRepository 4 | { 5 | Task> GetFavoriteTopics(int userID, int startRow, int pageSize); 6 | Task GetFavoriteTopicCount(int userID); 7 | Task IsTopicFavorite(int userID, int topicID); 8 | Task AddFavoriteTopic(int userID, int topicID); 9 | Task RemoveFavoriteTopic(int userID, int topicID); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IFeedRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IFeedRepository 4 | { 5 | Task> GetFeed(int userID, int itemCount); 6 | Task PublishEvent(int userID, string message, int points, DateTime timeStamp); 7 | Task GetOldestTime(int userID, int takeCount); 8 | Task DeleteOlderThan(int userID, DateTime timeCutOff); 9 | Task> GetFeed(int itemCount); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ILastReadRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ILastReadRepository 4 | { 5 | Task SetForumRead(int userID, int forumID, DateTime readTime); 6 | Task DeleteTopicReadsInForum(int userID, int forumID); 7 | Task SetAllForumsRead(int userID, DateTime readTime); 8 | Task DeleteAllTopicReads(int userID); 9 | Task SetTopicRead(int userID, int topicID, DateTime readTime); 10 | Task> GetLastReadTimesForForums(int userID); 11 | Task GetLastReadTimesForForum(int userID, int forumID); 12 | Task> GetLastReadTimesForTopics(int userID, IEnumerable topicIDs); 13 | Task GetLastReadTimeForTopic(int userID, int topicID); 14 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IModerationLogRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IModerationLogRepository 4 | { 5 | Task Log(DateTime timeStamp, int userID, string userName, int moderationType, int? forumID, int topicID, int? postID, string comment, string oldText); 6 | Task> GetLog(DateTime start, DateTime end); 7 | Task> GetLog(int topicID, bool excludePostEntries); 8 | Task> GetLog(int postID); 9 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/INotificationRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface INotificationRepository 4 | { 5 | Task UpdateNotification(Notification notification); 6 | Task CreateNotification(Notification notification); 7 | Task MarkNotificationRead(int userID, NotificationType notificationType, long contextID); 8 | Task> GetNotifications(int userID, DateTime afterDateTime, int pageSize); 9 | Task GetPageCount(int userID, int pageSize); 10 | Task GetUnreadNotificationCount(int userID); 11 | Task MarkAllRead(int userID); 12 | Task DeleteOlderThan(int userID, DateTime timeCutOff); 13 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IPointLedgerRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IPointLedgerRepository 4 | { 5 | Task RecordEntry(PointLedgerEntry entry); 6 | Task GetPointTotal(int userID); 7 | Task GetEntryCount(int userID, string eventDefinitionID); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IPostImageRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IPostImageRepository 4 | { 5 | Task Persist(byte[] bytes, string contentType); 6 | Task GetWithoutData(string id); 7 | [Obsolete("Use the combination of GetWithoutData(int) and GetImageStream(int) instead.")] 8 | Task Get(string id); 9 | Task DeletePostImageData(string id, string tenantID); 10 | Task GetImageStream(string id); 11 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IPostImageTempRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IPostImageTempRepository 4 | { 5 | Task Save(Guid postImageTempID, DateTime timeStamp, string tenantID); 6 | Task Delete(Guid id); 7 | Task> GetOld(DateTime olderThan); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IPrivateMessageRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IPrivateMessageRepository 4 | { 5 | Task Get(int pmID, int userID); 6 | Task> GetPosts(int pmID, DateTime afterDateTime); 7 | Task> GetPosts(int pmID, DateTime beforeDateTime, int pageSize); 8 | Task CreatePrivateMessage(PrivateMessage pm); 9 | Task AddUsers(int pmID, List userIDs, DateTime viewDate, bool isArchived); 10 | Task AddPost(PrivateMessagePost post); 11 | Task> GetUsers(int pmID); 12 | Task SetLastViewTime(int pmID, int userID, DateTime viewDate); 13 | Task SetArchive(int pmID, int userID, bool isArchived); 14 | Task> GetPrivateMessages(int userID, PrivateMessageBoxType boxType, int startRow, int pageSize); 15 | Task GetUnreadCount(int userID); 16 | Task GetBoxCount(int userID, PrivateMessageBoxType boxType); 17 | Task UpdateLastPostTime(int pmID, DateTime lastPostTime); 18 | Task GetExistingFromIDs(List ids); 19 | Task GetFirstUnreadPostID(int pmID, DateTime lastReadTime); 20 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IProfileRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IProfileRepository 4 | { 5 | Task GetProfile(int userID); 6 | Task Create(Profile profile); 7 | Task Update(Profile profile); 8 | Task GetLastPostID(int userID); 9 | Task SetLastPostID(int userID, int postID); 10 | Task> GetSignatures(List userIDs); 11 | Task> GetAvatars(List userIDs); 12 | Task SetCurrentImageIDToNull(int userID); 13 | Task UpdatePoints(int userID, int points); 14 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IQueuedEmailMessageRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IQueuedEmailMessageRepository 4 | { 5 | Task CreateMessage(QueuedEmailMessage message); 6 | Task DeleteMessage(int messageID); 7 | Task GetMessage(int messageID); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IRoleRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IRoleRepository 4 | { 5 | Task CreateRole(string role); 6 | Task DeleteRole(string role); 7 | Task> GetAllRoles(); 8 | Task> GetUserRoles(int userID); 9 | Task ReplaceUserRoles(int userID, string[] roles); 10 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISearchIndexQueueRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISearchIndexQueueRepository 4 | { 5 | Task Enqueue(SearchIndexPayload payload); 6 | Task Dequeue(); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISearchRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISearchRepository 4 | { 5 | Task> GetJunkWords(); 6 | Task CreateJunkWord(string word); 7 | Task DeleteJunkWord(string word); 8 | Task DeleteAllIndexedWordsForTopic(int topicID); 9 | Task SaveSearchWord(int topicID, string word, int rank); 10 | Task>, int>> SearchTopics(string searchTerm, List hiddenForums, SearchType searchType, int startRow, int pageSize); 11 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISecurityLogRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISecurityLogRepository 4 | { 5 | Task Create(SecurityLogEntry logEntry); 6 | Task> GetByUserID(int userID, DateTime startDate, DateTime endDate); 7 | Task> GetIPHistory(string ip, DateTime start, DateTime end); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IServiceHeartbeatRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IServiceHeartbeatRepository 4 | { 5 | Task RecordHeartbeat(string serviceName, string machineName, DateTime lastRun); 6 | Task> GetAll(); 7 | Task ClearAll(); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISettingsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISettingsRepository 4 | { 5 | Dictionary Get(); 6 | void Save(Dictionary dictionary); 7 | event Action OnSettingsInvalidated; 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISetupRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISetupRepository 4 | { 5 | bool IsConnectionPossible(); 6 | bool IsDatabaseSetup(); 7 | void SetupDatabase(); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISubscribeNotificationRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISubscribeNotificationRepository 4 | { 5 | Task Enqueue(SubscribeNotificationPayload payload); 6 | Task Dequeue(); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ISubscribedTopicsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ISubscribedTopicsRepository 4 | { 5 | Task> GetSubscribedTopics(int userID, int startRow, int pageSize); 6 | Task GetSubscribedTopicCount(int userID); 7 | Task IsTopicSubscribed(int userID, int topicID); 8 | Task AddSubscribedTopic(int userID, int topicID); 9 | Task RemoveSubscribedTopic(int userID, int topicID); 10 | Task> GetSubscribedUserIDs(int topicID); 11 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/ITopicViewLogRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface ITopicViewLogRepository 4 | { 5 | Task Log(int? userID, int topicID, DateTime timeStamp); 6 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IUserAvatarRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IUserAvatarRepository 4 | { 5 | [Obsolete("Use GetImageData(int) instead.")] 6 | Task GetImageData(int userAvatarID); 7 | Task GetImageStream(int userAvatarID); 8 | Task> GetUserAvatarIDs(int userID); 9 | Task SaveNewAvatar(int userID, byte[] imageData, DateTime timeStamp); 10 | Task DeleteAvatarsByUserID(int userID); 11 | Task GetLastModificationDate(int userAvatarID); 12 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IUserAwardRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IUserAwardRepository 4 | { 5 | Task IssueAward(int userID, string awardDefinitionID, string title, string description, DateTime timeStamp); 6 | Task IsAwarded(int userID, string awardDefinitionID); 7 | Task> GetAwards(int userID); 8 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IUserImageRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IUserImageRepository 4 | { 5 | [Obsolete("Use GetImageStream instead.")] 6 | Task GetImageData(int userImageID); 7 | Task GetImageStream(int userImageID); 8 | Task> GetUserImages(int userID); 9 | Task SaveNewImage(int userID, int sortOrder, bool isApproved, byte[] imageData, DateTime timeStamp); 10 | Task DeleteImagesByUserID(int userID); 11 | Task GetLastModificationDate(int userImageID); 12 | Task> GetUnapprovedUserImages(); 13 | Task IsUserImageApproved(int userImageID); 14 | Task ApproveUserImage(int userImageID); 15 | Task DeleteUserImage(int userImageID); 16 | Task Get(int userImageID); 17 | } -------------------------------------------------------------------------------- /src/PopForums/Repositories/IUserSessionRepository.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Repositories; 2 | 3 | public interface IUserSessionRepository 4 | { 5 | Task CreateSession(int sessionID, int? userID, DateTime lastTime); 6 | Task UpdateSession(int sessionID, DateTime lastTime); 7 | Task IsSessionAnonymous(int sessionID); 8 | Task> GetAndDeleteExpiredSessions(DateTime cutOffDate); 9 | Task GetSessionIDByUserID(int userID); 10 | Task DeleteSessions(int? userID, int sessionID); 11 | Task GetTotalSessionCount(); 12 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/AwardCalculatorWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public interface IAwardCalculatorWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class AwardCalculatorWorker(IAwardCalculator calculator, IAwardCalculationQueueRepository awardCalculationQueueRepository, IErrorLog errorLog) : IAwardCalculatorWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | var nextItem = await awardCalculationQueueRepository.Dequeue(); 15 | if (string.IsNullOrEmpty(nextItem.Key)) 16 | return; 17 | await calculator.ProcessCalculation(nextItem.Key, nextItem.Value); 18 | } 19 | catch (Exception exc) 20 | { 21 | errorLog.Log(exc, ErrorSeverity.Error); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/AwardCondition.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public class AwardCondition 4 | { 5 | public string AwardDefinitionID { get; set; } 6 | public string EventDefinitionID { get; set; } 7 | public int EventCount { get; set; } 8 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/AwardDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public class AwardDefinition 4 | { 5 | public string AwardDefinitionID { get; set; } 6 | public string Title { get; set; } 7 | public string Description { get; set; } 8 | public bool IsSingleTimeAward { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/EventDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public class EventDefinition 4 | { 5 | public string EventDefinitionID { get; set; } 6 | public string Description { get; set; } 7 | public int PointValue { get; set; } 8 | public bool IsPublishedToFeed { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/PointLedgerEntry.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public class PointLedgerEntry 4 | { 5 | public int UserID { get; set; } 6 | public string EventDefinitionID { get; set; } 7 | public int Points { get; set; } 8 | public DateTime TimeStamp { get; set; } 9 | } -------------------------------------------------------------------------------- /src/PopForums/ScoringGame/UserAward.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.ScoringGame; 2 | 3 | public class UserAward 4 | { 5 | public int UserAwardID { get; set; } 6 | public int UserID { get; set; } 7 | public string AwardDefinitionID { get; set; } 8 | public string Title { get; set; } 9 | public string Description { get; set; } 10 | public DateTime TimeStamp { get; set; } 11 | } -------------------------------------------------------------------------------- /src/PopForums/Services/BanService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IBanService 4 | { 5 | Task BanIP(string ip); 6 | Task RemoveIPBan(string ip); 7 | Task> GetIPBans(); 8 | Task BanEmail(string email); 9 | Task RemoveEmailBan(string email); 10 | Task> GetEmailBans(); 11 | } 12 | 13 | public class BanService : IBanService 14 | { 15 | public BanService(IBanRepository banRepsoitory) 16 | { 17 | _banRepository = banRepsoitory; 18 | } 19 | 20 | private readonly IBanRepository _banRepository; 21 | 22 | public async Task BanIP(string ip) 23 | { 24 | await _banRepository.BanIP(ip.Trim()); 25 | } 26 | 27 | public async Task RemoveIPBan(string ip) 28 | { 29 | await _banRepository.RemoveIPBan(ip); 30 | } 31 | 32 | public async Task> GetIPBans() 33 | { 34 | return await _banRepository.GetIPBans(); 35 | } 36 | 37 | public async Task BanEmail(string email) 38 | { 39 | await _banRepository.BanEmail(email.Trim()); 40 | } 41 | 42 | public async Task RemoveEmailBan(string email) 43 | { 44 | await _banRepository.RemoveEmailBan(email); 45 | } 46 | 47 | public async Task> GetEmailBans() 48 | { 49 | return await _banRepository.GetEmailBans(); 50 | } 51 | } -------------------------------------------------------------------------------- /src/PopForums/Services/CloseAgedTopicsWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ICloseAgedTopicsWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class CloseAgedTopicsWorker(ITopicService topicService, IErrorLog errorLog) : ICloseAgedTopicsWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | await topicService.CloseAgedTopics(); 15 | } 16 | catch (Exception exc) 17 | { 18 | errorLog.Log(exc, ErrorSeverity.Error); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/PopForums/Services/IPHistoryService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IIPHistoryService 4 | { 5 | Task> GetHistory(string ip, DateTime start, DateTime end); 6 | } 7 | 8 | public class IPHistoryService : IIPHistoryService 9 | { 10 | public IPHistoryService(IPostService postService, ISecurityLogService securityLogService) 11 | { 12 | _postService = postService; 13 | _securityLogService = securityLogService; 14 | } 15 | 16 | private readonly IPostService _postService; 17 | private readonly ISecurityLogService _securityLogService; 18 | 19 | public async Task> GetHistory(string ip, DateTime start, DateTime end) 20 | { 21 | var list = new List(); 22 | list.AddRange(await _postService.GetIPHistory(ip, start, end)); 23 | list.AddRange(await _securityLogService.GetIPHistory(ip, start, end)); 24 | return list.OrderBy(i => i.EventTime).ToList(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/PopForums/Services/ITopicViewCountService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ITopicViewCountService 4 | { 5 | Task ProcessView(Topic topic); 6 | void SetViewedTopic(Topic topic); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Services/IUserRetrievalShim.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IUserRetrievalShim 4 | { 5 | User GetUser(); 6 | Profile GetProfile(); 7 | } -------------------------------------------------------------------------------- /src/PopForums/Services/PostImageCleanupWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IPostImageCleanupWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class PostImageCleanupWorker(IPostImageService postImageService, IErrorLog errorLog) : IPostImageCleanupWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | await postImageService.DeleteOldPostImages(); 15 | } 16 | catch (Exception exc) 17 | { 18 | errorLog.Log(exc, ErrorSeverity.Error); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/PopForums/Services/QueuedEmailService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IQueuedEmailService 4 | { 5 | Task CreateAndQueueEmail(QueuedEmailMessage queuedEmailMessage); 6 | } 7 | 8 | public class QueuedEmailService : IQueuedEmailService 9 | { 10 | private readonly IQueuedEmailMessageRepository _queuedEmailMessageRepository; 11 | private readonly IEmailQueueRepository _emailQueueRepository; 12 | private readonly ITenantService _tenantService; 13 | 14 | public QueuedEmailService(IQueuedEmailMessageRepository queuedEmailMessageRepository, IEmailQueueRepository emailQueueRepository, ITenantService tenantService) 15 | { 16 | _queuedEmailMessageRepository = queuedEmailMessageRepository; 17 | _emailQueueRepository = emailQueueRepository; 18 | _tenantService = tenantService; 19 | } 20 | 21 | public async Task CreateAndQueueEmail(QueuedEmailMessage queuedEmailMessage) 22 | { 23 | var id = await _queuedEmailMessageRepository.CreateMessage(queuedEmailMessage); 24 | var tenantID = _tenantService.GetTenant(); 25 | var payload = new EmailQueuePayload { MessageID = id, EmailQueuePayloadType = EmailQueuePayloadType.FullMessage, TenantID = tenantID }; 26 | await _emailQueueRepository.Enqueue(payload); 27 | } 28 | } -------------------------------------------------------------------------------- /src/PopForums/Services/SearchIndexWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ISearchIndexWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class SearchIndexWorker(IErrorLog errorLog, ISearchIndexSubsystem searchIndexSubsystem, ISearchService searchService) : ISearchIndexWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | var payload = await searchService.GetNextTopicForIndexing(); 15 | if (payload == null) 16 | return; 17 | searchIndexSubsystem.DoIndex(payload.TopicID, payload.TenantID, payload.IsForRemoval); 18 | } 19 | catch (Exception exc) 20 | { 21 | errorLog.Log(exc, ErrorSeverity.Error); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/PopForums/Services/ServiceHeartbeatService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IServiceHeartbeatService 4 | { 5 | Task RecordHeartbeat(string serviceName, string machineName); 6 | Task> GetAll(); 7 | Task ClearAll(); 8 | } 9 | 10 | public class ServiceHeartbeatService : IServiceHeartbeatService 11 | { 12 | private readonly IServiceHeartbeatRepository _serviceHeartbeatRepository; 13 | 14 | public ServiceHeartbeatService(IServiceHeartbeatRepository serviceHeartbeatRepository) 15 | { 16 | _serviceHeartbeatRepository = serviceHeartbeatRepository; 17 | } 18 | 19 | public async Task RecordHeartbeat(string serviceName, string machineName) 20 | { 21 | await _serviceHeartbeatRepository.RecordHeartbeat(serviceName, machineName, DateTime.UtcNow); 22 | } 23 | 24 | public async Task> GetAll() 25 | { 26 | return await _serviceHeartbeatRepository.GetAll(); 27 | } 28 | 29 | public async Task ClearAll() 30 | { 31 | await _serviceHeartbeatRepository.ClearAll(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/PopForums/Services/SubscribeNotificationWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ISubscribeNotificationWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class SubscribeNotificationWorker(ISubscribeNotificationRepository subscribeNotificationRepository, ISubscribedTopicsService subscribedTopicsService, INotificationAdapter notificationAdapter, IErrorLog errorLog) : ISubscribeNotificationWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | var payload = await subscribeNotificationRepository.Dequeue(); 15 | if (payload == null) 16 | return; 17 | var userIDs = await subscribedTopicsService.GetSubscribedUserIDs(payload.TopicID); 18 | var filteredUserIDs = userIDs.Where(x => x != payload.PostingUserID); 19 | foreach (var userID in filteredUserIDs) 20 | await notificationAdapter.Reply(payload.PostingUserName, payload.TopicTitle, payload.TopicID, userID, payload.TenantID); 21 | } 22 | catch (Exception exc) 23 | { 24 | errorLog.Log(exc, ErrorSeverity.Error); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/PopForums/Services/TenantService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ITenantService 4 | { 5 | void SetTenant(string tenantID); 6 | string GetTenant(); 7 | } 8 | 9 | public class TenantService : ITenantService 10 | { 11 | public void SetTenant(string tenantID) 12 | { 13 | throw new NotImplementedException(); 14 | } 15 | 16 | public string GetTenant() 17 | { 18 | return string.Empty; 19 | } 20 | } -------------------------------------------------------------------------------- /src/PopForums/Services/TimeFormatStringService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ITimeFormatStringService 4 | { 5 | TimeFormats GeTimeFormats(); 6 | string GetTimeFormatsAsJson(); 7 | } 8 | 9 | public class TimeFormatStringService : ITimeFormatStringService 10 | { 11 | public TimeFormats GeTimeFormats() 12 | { 13 | var formats = new TimeFormats 14 | { 15 | TodayTime = Resources.TodayTime, 16 | YesterdayTime = Resources.YesterdayTime, 17 | MinutesAgo = Resources.MinutesAgo, 18 | OneMinuteAgo = Resources.OneMinuteAgo, 19 | LessThanMinute = Resources.LessThanMinute 20 | }; 21 | return formats; 22 | } 23 | 24 | public string GetTimeFormatsAsJson() 25 | { 26 | var formats = GeTimeFormats(); 27 | var serialized = JsonSerializer.Serialize(formats, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); 28 | return serialized; 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums/Services/TopicViewLogService.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface ITopicViewLogService 4 | { 5 | Task LogView(int? userID, int topicID); 6 | } 7 | 8 | public class TopicViewLogService : ITopicViewLogService 9 | { 10 | private readonly IConfig _config; 11 | private readonly ITopicViewLogRepository _topicViewLogRepository; 12 | 13 | public TopicViewLogService(IConfig config, ITopicViewLogRepository topicViewLogRepository) 14 | { 15 | _config = config; 16 | _topicViewLogRepository = topicViewLogRepository; 17 | } 18 | 19 | public async Task LogView(int? userID, int topicID) 20 | { 21 | if (!_config.LogTopicViews) 22 | return; 23 | var timeStamp = DateTime.UtcNow; 24 | await _topicViewLogRepository.Log(userID, topicID, timeStamp); 25 | } 26 | } -------------------------------------------------------------------------------- /src/PopForums/Services/UserEmailReconciler.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IUserEmailReconciler 4 | { 5 | Task GetUniqueEmail(string email, string externalID); 6 | } 7 | 8 | /// 9 | /// Checks for existing email addresses from an external identity provider. If a match is found, it mangles the address 10 | /// to use an "example.com" address, which is not real per IETF RFC 2606. 11 | /// 12 | public class UserEmailReconciler : IUserEmailReconciler 13 | { 14 | private readonly IUserRepository _userRepository; 15 | 16 | public UserEmailReconciler(IUserRepository userRepository) 17 | { 18 | _userRepository = userRepository; 19 | } 20 | 21 | public async Task GetUniqueEmail(string email, string externalID) 22 | { 23 | var match = await _userRepository.GetUserByEmail(email); 24 | if (match is null) 25 | return email; 26 | 27 | var uniqueEmail = $"{email.Replace("@","-at-")}@{externalID}.example.com"; 28 | return uniqueEmail; 29 | } 30 | } -------------------------------------------------------------------------------- /src/PopForums/Services/UserNameReconciler.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IUserNameReconciler 4 | { 5 | Task GetUniqueNameForUser(string name); 6 | } 7 | 8 | /// 9 | /// Used to make sure that incoming names from an external identity provider are unique. In other words, if there's 10 | /// a "John Smith," the next one becomes "John Smith-2." 11 | /// 12 | public class UserNameReconciler : IUserNameReconciler 13 | { 14 | private readonly IUserRepository _userRepository; 15 | 16 | public UserNameReconciler(IUserRepository userRepository) 17 | { 18 | _userRepository = userRepository; 19 | } 20 | 21 | public async Task GetUniqueNameForUser(string name) 22 | { 23 | var existingMatches = await _userRepository.GetUserNamesThatStartWith(name); 24 | var uniqueName = name.ToUniqueName(existingMatches.ToList()); 25 | return uniqueName; 26 | } 27 | } -------------------------------------------------------------------------------- /src/PopForums/Services/UserSessionWorker.cs: -------------------------------------------------------------------------------- 1 | namespace PopForums.Services; 2 | 3 | public interface IUserSessionWorker 4 | { 5 | void Execute(); 6 | } 7 | 8 | public class UserSessionWorker(IUserSessionService sessionService, IErrorLog errorLog) : IUserSessionWorker 9 | { 10 | public async void Execute() 11 | { 12 | try 13 | { 14 | await sessionService.CleanUpExpiredSessions(); 15 | } 16 | catch (Exception exc) 17 | { 18 | errorLog.Log(exc, ErrorSeverity.Error); 19 | } 20 | } 21 | } --------------------------------------------------------------------------------