From ac769e656b122ff569c3f1534701b71e00fed586 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 27 Oct 2020 19:21:35 +0800 Subject: Split front and back end. --- Timeline/Auth/Attribute.cs | 21 - Timeline/Auth/MyAuthenticationHandler.cs | 100 -- Timeline/Auth/PrincipalExtensions.cs | 13 - Timeline/ClientApp/.babelrc | 27 - Timeline/ClientApp/.editorconfig | 14 - Timeline/ClientApp/.eslintignore | 6 - Timeline/ClientApp/.eslintrc.js | 47 - Timeline/ClientApp/.gitattributes | 1 - Timeline/ClientApp/.gitignore | 32 - Timeline/ClientApp/.vscode/extensions.json | 9 - Timeline/ClientApp/.vscode/preview.yml | 10 - Timeline/ClientApp/.vscode/settings.json | 6 - Timeline/ClientApp/.yarnrc.yml | 5 - Timeline/ClientApp/LICENSE | 21 - Timeline/ClientApp/package.json | 111 -- Timeline/ClientApp/postcss.config.js | 10 - .../ClientApp/public/android-chrome-192x192.png | Bin 8519 -> 0 bytes .../ClientApp/public/android-chrome-512x512.png | Bin 23619 -> 0 bytes Timeline/ClientApp/public/apple-touch-icon.png | Bin 2088 -> 0 bytes Timeline/ClientApp/public/browserconfig.xml | 9 - Timeline/ClientApp/public/favicon-16x16.png | Bin 682 -> 0 bytes Timeline/ClientApp/public/favicon-32x32.png | Bin 821 -> 0 bytes Timeline/ClientApp/public/favicon.ico | Bin 15086 -> 0 bytes Timeline/ClientApp/public/mstile-144x144.png | Bin 2259 -> 0 bytes Timeline/ClientApp/public/mstile-150x150.png | Bin 2240 -> 0 bytes Timeline/ClientApp/public/mstile-310x150.png | Bin 2482 -> 0 bytes Timeline/ClientApp/public/mstile-310x310.png | Bin 4937 -> 0 bytes Timeline/ClientApp/public/mstile-70x70.png | Bin 1561 -> 0 bytes Timeline/ClientApp/public/safari-pinned-tab.svg | 25 - Timeline/ClientApp/public/site.webmanifest | 22 - Timeline/ClientApp/sandbox.config.json | 11 - Timeline/ClientApp/src/app/App.tsx | 84 -- Timeline/ClientApp/src/app/common.ts | 44 - Timeline/ClientApp/src/app/http/common.ts | 161 -- Timeline/ClientApp/src/app/http/timeline.ts | 544 ------- Timeline/ClientApp/src/app/http/token.ts | 72 - Timeline/ClientApp/src/app/http/user.ts | 134 -- Timeline/ClientApp/src/app/i18n.ts | 79 - Timeline/ClientApp/src/app/index.ejs | 29 - Timeline/ClientApp/src/app/index.sass | 66 - Timeline/ClientApp/src/app/index.tsx | 15 - .../ClientApp/src/app/locales/en/translation.ts | 202 --- Timeline/ClientApp/src/app/locales/scheme.ts | 182 --- .../ClientApp/src/app/locales/zh/translation.ts | 195 --- Timeline/ClientApp/src/app/service-worker.tsx | 113 -- Timeline/ClientApp/src/app/services/DataHub.ts | 225 --- Timeline/ClientApp/src/app/services/alert.ts | 61 - Timeline/ClientApp/src/app/services/common.ts | 23 - Timeline/ClientApp/src/app/services/timeline.ts | 702 --------- Timeline/ClientApp/src/app/services/user.ts | 393 ----- Timeline/ClientApp/src/app/tsconfig.json | 13 - Timeline/ClientApp/src/app/typings.d.ts | 24 - Timeline/ClientApp/src/app/utilities/rxjs.ts | 14 - Timeline/ClientApp/src/app/utilities/url.ts | 52 - Timeline/ClientApp/src/app/views/about/about.sass | 4 - .../src/app/views/about/author-avatar.png | Bin 12038 -> 0 bytes Timeline/ClientApp/src/app/views/about/github.png | Bin 4268 -> 0 bytes Timeline/ClientApp/src/app/views/about/index.tsx | 164 -- Timeline/ClientApp/src/app/views/admin/Admin.tsx | 75 - .../ClientApp/src/app/views/admin/UserAdmin.tsx | 460 ------ Timeline/ClientApp/src/app/views/common/AppBar.tsx | 64 - .../ClientApp/src/app/views/common/BlobImage.tsx | 27 - .../src/app/views/common/ImageCropper.tsx | 306 ---- .../src/app/views/common/LoadingButton.tsx | 29 - .../ClientApp/src/app/views/common/LoadingPage.tsx | 12 - .../src/app/views/common/OperationDialog.tsx | 364 ----- .../ClientApp/src/app/views/common/SearchInput.tsx | 63 - .../src/app/views/common/TimelineLogo.tsx | 26 - .../src/app/views/common/UserTimelineLogo.tsx | 26 - .../src/app/views/common/alert/AlertHost.tsx | 101 -- .../src/app/views/common/alert/alert.sass | 15 - .../ClientApp/src/app/views/common/common.sass | 33 - .../ClientApp/src/app/views/home/BoardWithUser.tsx | 101 -- .../src/app/views/home/BoardWithoutUser.tsx | 60 - .../ClientApp/src/app/views/home/OfflineBoard.tsx | 61 - .../ClientApp/src/app/views/home/TimelineBoard.tsx | 73 - .../src/app/views/home/TimelineCreateDialog.tsx | 53 - Timeline/ClientApp/src/app/views/home/home.sass | 13 - Timeline/ClientApp/src/app/views/home/index.tsx | 99 -- Timeline/ClientApp/src/app/views/login/index.tsx | 151 -- Timeline/ClientApp/src/app/views/login/login.sass | 2 - .../ClientApp/src/app/views/settings/index.tsx | 209 --- .../app/views/timeline-common/CollapseButton.tsx | 23 - .../app/views/timeline-common/InfoCardTemplate.tsx | 26 - .../app/views/timeline-common/SyncStatusBadge.tsx | 58 - .../src/app/views/timeline-common/Timeline.tsx | 84 -- .../src/app/views/timeline-common/TimelineItem.tsx | 172 --- .../app/views/timeline-common/TimelineMember.tsx | 211 --- .../views/timeline-common/TimelinePageTemplate.tsx | 185 --- .../timeline-common/TimelinePageTemplateUI.tsx | 243 --- .../app/views/timeline-common/TimelinePostEdit.tsx | 241 --- .../TimelinePropertyChangeDialog.tsx | 72 - .../src/app/views/timeline-common/TimelineTop.tsx | 21 - .../app/views/timeline-common/timeline-common.sass | 146 -- .../app/views/timeline/TimelineDeleteDialog.tsx | 55 - .../src/app/views/timeline/TimelineInfoCard.tsx | 85 -- .../src/app/views/timeline/TimelinePageUI.tsx | 20 - .../ClientApp/src/app/views/timeline/index.tsx | 37 - .../ClientApp/src/app/views/timeline/timeline.sass | 0 .../src/app/views/user/ChangeAvatarDialog.tsx | 302 ---- .../src/app/views/user/ChangeNicknameDialog.tsx | 28 - .../ClientApp/src/app/views/user/UserInfoCard.tsx | 80 - .../ClientApp/src/app/views/user/UserPageUI.tsx | 18 - Timeline/ClientApp/src/app/views/user/index.tsx | 72 - Timeline/ClientApp/src/app/views/user/user.sass | 7 - Timeline/ClientApp/src/sw/sw.ts | 28 - Timeline/ClientApp/src/sw/tsconfig.json | 12 - Timeline/ClientApp/src/tsconfig.json | 23 - Timeline/ClientApp/webpack.common.js | 86 -- Timeline/ClientApp/webpack.config.dev.js | 52 - Timeline/ClientApp/webpack.config.prod.js | 53 - Timeline/Configs/ApplicationConfiguration.cs | 13 - Timeline/Configs/JwtConfiguration.cs | 14 - Timeline/Controllers/ControllerAuthExtensions.cs | 40 - .../Controllers/Testing/TestingAuthController.cs | 32 - Timeline/Controllers/TimelineController.cs | 491 ------ Timeline/Controllers/TokenController.cs | 142 -- Timeline/Controllers/UserAvatarController.cs | 174 --- Timeline/Controllers/UserController.cs | 195 --- Timeline/Entities/DataEntity.cs | 23 - Timeline/Entities/DatabaseContext.cs | 34 - Timeline/Entities/JwtTokenEntity.cs | 17 - Timeline/Entities/TimelineEntity.cs | 58 - Timeline/Entities/TimelineMemberEntity.cs | 24 - Timeline/Entities/TimelinePostEntity.cs | 43 - Timeline/Entities/UserAvatarEntity.cs | 29 - Timeline/Entities/UserEntity.cs | 56 - Timeline/Entities/UtcDateAnnotation.cs | 44 - Timeline/Filters/Header.cs | 63 - Timeline/Filters/Timeline.cs | 32 - Timeline/Formatters/BytesInputFormatter.cs | 79 - Timeline/Formatters/StringInputFormatter.cs | 26 - Timeline/GlobalSuppressions.cs | 14 - Timeline/Helpers/DataCacheHelper.cs | 125 -- Timeline/Helpers/DateTimeExtensions.cs | 14 - Timeline/Helpers/InvalidModelResponseFactory.cs | 25 - Timeline/Helpers/LanguageHelper.cs | 12 - Timeline/Helpers/Log.cs | 22 - .../20200105150407_Initialize.Designer.cs | 266 ---- Timeline/Migrations/20200105150407_Initialize.cs | 217 --- .../20200131100517_RefactorUser.Designer.cs | 240 --- Timeline/Migrations/20200131100517_RefactorUser.cs | 128 -- .../20200221064341_AddJwtToken.Designer.cs | 257 ---- Timeline/Migrations/20200221064341_AddJwtToken.cs | 45 - .../20200229103848_AddPostLocalId.Designer.cs | 265 ---- .../Migrations/20200229103848_AddPostLocalId.cs | 42 - .../20200306110049_AddDataTable.Designer.cs | 290 ---- Timeline/Migrations/20200306110049_AddDataTable.cs | 87 -- .../20200306111553_DropUserDetails.Designer.cs | 290 ---- .../Migrations/20200306111553_DropUserDetails.cs | 17 - .../20200312112552_AddImagePost.Designer.cs | 299 ---- Timeline/Migrations/20200312112552_AddImagePost.cs | 38 - .../20200614061237_AddTimelineUniqueId.Designer.cs | 306 ---- .../20200614061237_AddTimelineUniqueId.cs | 50 - ...00618064936_TimelineAddModifiedTime.Designer.cs | 314 ---- .../20200618064936_TimelineAddModifiedTime.cs | 57 - .../20200808071611_UserAddUniqueId.Designer.cs | 321 ---- .../Migrations/20200808071611_UserAddUniqueId.cs | 55 - .../20200810155908_AddTimesToUser.Designer.cs | 339 ----- .../Migrations/20200810155908_AddTimesToUser.cs | 67 - ...200810170533_MakePostAuthorOptional.Designer.cs | 337 ----- .../20200810170533_MakePostAuthorOptional.cs | 78 - ...0808_ChangeDateTimeOffsetToDateTime.Designer.cs | 337 ----- ...0200811080808_ChangeDateTimeOffsetToDateTime.cs | 17 - .../20200826164553_TimelineAddTitle.Designer.cs | 341 ----- .../Migrations/20200826164553_TimelineAddTitle.cs | 22 - .../Migrations/DatabaseContextModelSnapshot.cs | 339 ----- Timeline/MockClientApp/index.html | 10 - Timeline/Models/ByteData.cs | 33 - .../Models/Converters/JsonDateTimeConverter.cs | 23 - Timeline/Models/Converters/MyDateTimeConverter.cs | 51 - .../Models/Http/ActionContextAccessorExtensions.cs | 14 - Timeline/Models/Http/Common.cs | 120 -- Timeline/Models/Http/ErrorResponse.cs | 261 ---- Timeline/Models/Http/Timeline.cs | 219 --- Timeline/Models/Http/TimelineController.cs | 93 -- Timeline/Models/Http/TokenController.cs | 62 - Timeline/Models/Http/UserController.cs | 93 -- Timeline/Models/Http/UserInfo.cs | 90 -- Timeline/Models/Timeline.cs | 98 -- Timeline/Models/User.cs | 21 - .../Validation/GeneralTimelineNameValidator.cs | 33 - Timeline/Models/Validation/NameValidator.cs | 42 - Timeline/Models/Validation/NicknameValidator.cs | 25 - .../Models/Validation/TimelineNameValidator.cs | 19 - Timeline/Models/Validation/UsernameValidator.cs | 19 - Timeline/Models/Validation/Validator.cs | 127 -- Timeline/Program.cs | 43 - Timeline/Properties/launchSettings.json | 27 - .../Authentication/AuthHandler.Designer.cs | 99 -- Timeline/Resources/Authentication/AuthHandler.resx | 132 -- .../ControllerAuthExtensions.Designer.cs | 81 - .../Controllers/ControllerAuthExtensions.resx | 126 -- .../Controllers/TimelineController.Designer.cs | 81 - .../Resources/Controllers/TimelineController.resx | 126 -- .../Controllers/TokenController.Designer.cs | 153 -- .../Resources/Controllers/TokenController.resx | 150 -- .../Controllers/UserAvatarController.Designer.cs | 144 -- .../Controllers/UserAvatarController.resx | 147 -- .../Controllers/UserController.Designer.cs | 117 -- Timeline/Resources/Controllers/UserController.resx | 138 -- Timeline/Resources/Entities.Designer.cs | 72 - Timeline/Resources/Entities.resx | 123 -- Timeline/Resources/Filters.Designer.cs | 90 -- Timeline/Resources/Filters.resx | 129 -- .../Resources/Helper/DataCacheHelper.Designer.cs | 90 -- Timeline/Resources/Helper/DataCacheHelper.resx | 129 -- Timeline/Resources/Messages.Designer.cs | 396 ----- Timeline/Resources/Messages.resx | 231 --- Timeline/Resources/Models/Http/Common.Designer.cs | 99 -- Timeline/Resources/Models/Http/Common.resx | 132 -- .../Resources/Models/Http/Exception.Designer.cs | 81 - Timeline/Resources/Models/Http/Exception.resx | 126 -- .../Models/Validation/NameValidator.Designer.cs | 99 -- .../Resources/Models/Validation/NameValidator.resx | 132 -- .../Validation/NicknameValidator.Designer.cs | 72 - .../Models/Validation/NicknameValidator.resx | 123 -- .../Models/Validation/Validator.Designer.cs | 108 -- .../Resources/Models/Validation/Validator.resx | 135 -- .../Resources/Services/DataManager.Designer.cs | 72 - Timeline/Resources/Services/DataManager.resx | 123 -- Timeline/Resources/Services/Exception.Designer.cs | 234 --- Timeline/Resources/Services/Exception.resx | 177 --- Timeline/Resources/Services/Exceptions.Designer.cs | 189 --- Timeline/Resources/Services/Exceptions.resx | 142 -- .../Resources/Services/TimelineService.Designer.cs | 144 -- Timeline/Resources/Services/TimelineService.resx | 147 -- .../Services/UserAvatarService.Designer.cs | 108 -- Timeline/Resources/Services/UserAvatarService.resx | 135 -- .../Resources/Services/UserService.Designer.cs | 162 -- Timeline/Resources/Services/UserService.resx | 153 -- .../Services/UserTokenService.Designer.cs | 72 - Timeline/Resources/Services/UserTokenService.resx | 123 -- Timeline/Routes/ApiRoutePrefixConvention.cs | 46 - Timeline/Routes/UnknownEndpointMiddleware.cs | 39 - Timeline/Services/BadPasswordException.cs | 27 - Timeline/Services/Clock.cs | 29 - Timeline/Services/DataManager.cs | 122 -- Timeline/Services/DatabaseBackupService.cs | 35 - Timeline/Services/DatabaseCorruptedException.cs | 15 - Timeline/Services/ETagGenerator.cs | 45 - Timeline/Services/EntityNames.cs | 14 - .../Services/Exceptions/EntityAlreadyExistError.cs | 63 - .../Services/Exceptions/EntityNotExistError.cs | 55 - .../Services/Exceptions/ExceptionMessageHelper.cs | 13 - Timeline/Services/Exceptions/ImageException.cs | 57 - .../Exceptions/TimelineNotExistException.cs | 21 - .../Exceptions/TimelinePostNoDataException.cs | 15 - .../Exceptions/TimelinePostNotExistException.cs | 33 - .../Services/Exceptions/UserNotExistException.cs | 40 - Timeline/Services/ImageValidator.cs | 54 - .../Services/JwtUserTokenBadFormatException.cs | 48 - Timeline/Services/PasswordBadFormatException.cs | 27 - Timeline/Services/PasswordService.cs | 224 --- Timeline/Services/PathProvider.cs | 42 - Timeline/Services/TimelineService.cs | 1166 --------------- Timeline/Services/UserAvatarService.cs | 265 ---- Timeline/Services/UserDeleteService.cs | 69 - Timeline/Services/UserRoleConvert.cs | 43 - Timeline/Services/UserService.cs | 437 ------ Timeline/Services/UserTokenException.cs | 68 - Timeline/Services/UserTokenManager.cs | 97 -- Timeline/Services/UserTokenService.cs | 149 -- Timeline/Startup.cs | 187 --- Timeline/Swagger/ApiConvention.cs | 15 - .../Swagger/ByteDataRequestOperationProcessor.cs | 27 - .../DefaultDescriptionOperationProcessor.cs | 39 - .../DocumentDescriptionDocumentProcessor.cs | 55 - Timeline/Timeline.csproj | 289 ---- Timeline/appsettings.Development.json | 9 - Timeline/appsettings.json | 11 - Timeline/default-avatar.png | Bin 26442 -> 0 bytes Timeline/packages.lock.json | 1563 -------------------- 273 files changed, 29178 deletions(-) delete mode 100644 Timeline/Auth/Attribute.cs delete mode 100644 Timeline/Auth/MyAuthenticationHandler.cs delete mode 100644 Timeline/Auth/PrincipalExtensions.cs delete mode 100644 Timeline/ClientApp/.babelrc delete mode 100644 Timeline/ClientApp/.editorconfig delete mode 100644 Timeline/ClientApp/.eslintignore delete mode 100644 Timeline/ClientApp/.eslintrc.js delete mode 100644 Timeline/ClientApp/.gitattributes delete mode 100644 Timeline/ClientApp/.gitignore delete mode 100644 Timeline/ClientApp/.vscode/extensions.json delete mode 100644 Timeline/ClientApp/.vscode/preview.yml delete mode 100644 Timeline/ClientApp/.vscode/settings.json delete mode 100644 Timeline/ClientApp/.yarnrc.yml delete mode 100644 Timeline/ClientApp/LICENSE delete mode 100644 Timeline/ClientApp/package.json delete mode 100644 Timeline/ClientApp/postcss.config.js delete mode 100644 Timeline/ClientApp/public/android-chrome-192x192.png delete mode 100644 Timeline/ClientApp/public/android-chrome-512x512.png delete mode 100644 Timeline/ClientApp/public/apple-touch-icon.png delete mode 100644 Timeline/ClientApp/public/browserconfig.xml delete mode 100644 Timeline/ClientApp/public/favicon-16x16.png delete mode 100644 Timeline/ClientApp/public/favicon-32x32.png delete mode 100644 Timeline/ClientApp/public/favicon.ico delete mode 100644 Timeline/ClientApp/public/mstile-144x144.png delete mode 100644 Timeline/ClientApp/public/mstile-150x150.png delete mode 100644 Timeline/ClientApp/public/mstile-310x150.png delete mode 100644 Timeline/ClientApp/public/mstile-310x310.png delete mode 100644 Timeline/ClientApp/public/mstile-70x70.png delete mode 100644 Timeline/ClientApp/public/safari-pinned-tab.svg delete mode 100644 Timeline/ClientApp/public/site.webmanifest delete mode 100644 Timeline/ClientApp/sandbox.config.json delete mode 100644 Timeline/ClientApp/src/app/App.tsx delete mode 100644 Timeline/ClientApp/src/app/common.ts delete mode 100644 Timeline/ClientApp/src/app/http/common.ts delete mode 100644 Timeline/ClientApp/src/app/http/timeline.ts delete mode 100644 Timeline/ClientApp/src/app/http/token.ts delete mode 100644 Timeline/ClientApp/src/app/http/user.ts delete mode 100644 Timeline/ClientApp/src/app/i18n.ts delete mode 100644 Timeline/ClientApp/src/app/index.ejs delete mode 100644 Timeline/ClientApp/src/app/index.sass delete mode 100644 Timeline/ClientApp/src/app/index.tsx delete mode 100644 Timeline/ClientApp/src/app/locales/en/translation.ts delete mode 100644 Timeline/ClientApp/src/app/locales/scheme.ts delete mode 100644 Timeline/ClientApp/src/app/locales/zh/translation.ts delete mode 100644 Timeline/ClientApp/src/app/service-worker.tsx delete mode 100644 Timeline/ClientApp/src/app/services/DataHub.ts delete mode 100644 Timeline/ClientApp/src/app/services/alert.ts delete mode 100644 Timeline/ClientApp/src/app/services/common.ts delete mode 100644 Timeline/ClientApp/src/app/services/timeline.ts delete mode 100644 Timeline/ClientApp/src/app/services/user.ts delete mode 100644 Timeline/ClientApp/src/app/tsconfig.json delete mode 100644 Timeline/ClientApp/src/app/typings.d.ts delete mode 100644 Timeline/ClientApp/src/app/utilities/rxjs.ts delete mode 100644 Timeline/ClientApp/src/app/utilities/url.ts delete mode 100644 Timeline/ClientApp/src/app/views/about/about.sass delete mode 100644 Timeline/ClientApp/src/app/views/about/author-avatar.png delete mode 100644 Timeline/ClientApp/src/app/views/about/github.png delete mode 100644 Timeline/ClientApp/src/app/views/about/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/admin/Admin.tsx delete mode 100644 Timeline/ClientApp/src/app/views/admin/UserAdmin.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/AppBar.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/BlobImage.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/ImageCropper.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/LoadingButton.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/LoadingPage.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/OperationDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/SearchInput.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx delete mode 100644 Timeline/ClientApp/src/app/views/common/alert/alert.sass delete mode 100644 Timeline/ClientApp/src/app/views/common/common.sass delete mode 100644 Timeline/ClientApp/src/app/views/home/BoardWithUser.tsx delete mode 100644 Timeline/ClientApp/src/app/views/home/BoardWithoutUser.tsx delete mode 100644 Timeline/ClientApp/src/app/views/home/OfflineBoard.tsx delete mode 100644 Timeline/ClientApp/src/app/views/home/TimelineBoard.tsx delete mode 100644 Timeline/ClientApp/src/app/views/home/TimelineCreateDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/home/home.sass delete mode 100644 Timeline/ClientApp/src/app/views/home/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/login/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/login/login.sass delete mode 100644 Timeline/ClientApp/src/app/views/settings/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/InfoCardTemplate.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelineMember.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplate.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelinePostEdit.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/TimelineTop.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass delete mode 100644 Timeline/ClientApp/src/app/views/timeline/TimelineDeleteDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline/TimelinePageUI.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/timeline/timeline.sass delete mode 100644 Timeline/ClientApp/src/app/views/user/ChangeAvatarDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/user/ChangeNicknameDialog.tsx delete mode 100644 Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx delete mode 100644 Timeline/ClientApp/src/app/views/user/UserPageUI.tsx delete mode 100644 Timeline/ClientApp/src/app/views/user/index.tsx delete mode 100644 Timeline/ClientApp/src/app/views/user/user.sass delete mode 100644 Timeline/ClientApp/src/sw/sw.ts delete mode 100644 Timeline/ClientApp/src/sw/tsconfig.json delete mode 100644 Timeline/ClientApp/src/tsconfig.json delete mode 100644 Timeline/ClientApp/webpack.common.js delete mode 100644 Timeline/ClientApp/webpack.config.dev.js delete mode 100644 Timeline/ClientApp/webpack.config.prod.js delete mode 100644 Timeline/Configs/ApplicationConfiguration.cs delete mode 100644 Timeline/Configs/JwtConfiguration.cs delete mode 100644 Timeline/Controllers/ControllerAuthExtensions.cs delete mode 100644 Timeline/Controllers/Testing/TestingAuthController.cs delete mode 100644 Timeline/Controllers/TimelineController.cs delete mode 100644 Timeline/Controllers/TokenController.cs delete mode 100644 Timeline/Controllers/UserAvatarController.cs delete mode 100644 Timeline/Controllers/UserController.cs delete mode 100644 Timeline/Entities/DataEntity.cs delete mode 100644 Timeline/Entities/DatabaseContext.cs delete mode 100644 Timeline/Entities/JwtTokenEntity.cs delete mode 100644 Timeline/Entities/TimelineEntity.cs delete mode 100644 Timeline/Entities/TimelineMemberEntity.cs delete mode 100644 Timeline/Entities/TimelinePostEntity.cs delete mode 100644 Timeline/Entities/UserAvatarEntity.cs delete mode 100644 Timeline/Entities/UserEntity.cs delete mode 100644 Timeline/Entities/UtcDateAnnotation.cs delete mode 100644 Timeline/Filters/Header.cs delete mode 100644 Timeline/Filters/Timeline.cs delete mode 100644 Timeline/Formatters/BytesInputFormatter.cs delete mode 100644 Timeline/Formatters/StringInputFormatter.cs delete mode 100644 Timeline/GlobalSuppressions.cs delete mode 100644 Timeline/Helpers/DataCacheHelper.cs delete mode 100644 Timeline/Helpers/DateTimeExtensions.cs delete mode 100644 Timeline/Helpers/InvalidModelResponseFactory.cs delete mode 100644 Timeline/Helpers/LanguageHelper.cs delete mode 100644 Timeline/Helpers/Log.cs delete mode 100644 Timeline/Migrations/20200105150407_Initialize.Designer.cs delete mode 100644 Timeline/Migrations/20200105150407_Initialize.cs delete mode 100644 Timeline/Migrations/20200131100517_RefactorUser.Designer.cs delete mode 100644 Timeline/Migrations/20200131100517_RefactorUser.cs delete mode 100644 Timeline/Migrations/20200221064341_AddJwtToken.Designer.cs delete mode 100644 Timeline/Migrations/20200221064341_AddJwtToken.cs delete mode 100644 Timeline/Migrations/20200229103848_AddPostLocalId.Designer.cs delete mode 100644 Timeline/Migrations/20200229103848_AddPostLocalId.cs delete mode 100644 Timeline/Migrations/20200306110049_AddDataTable.Designer.cs delete mode 100644 Timeline/Migrations/20200306110049_AddDataTable.cs delete mode 100644 Timeline/Migrations/20200306111553_DropUserDetails.Designer.cs delete mode 100644 Timeline/Migrations/20200306111553_DropUserDetails.cs delete mode 100644 Timeline/Migrations/20200312112552_AddImagePost.Designer.cs delete mode 100644 Timeline/Migrations/20200312112552_AddImagePost.cs delete mode 100644 Timeline/Migrations/20200614061237_AddTimelineUniqueId.Designer.cs delete mode 100644 Timeline/Migrations/20200614061237_AddTimelineUniqueId.cs delete mode 100644 Timeline/Migrations/20200618064936_TimelineAddModifiedTime.Designer.cs delete mode 100644 Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs delete mode 100644 Timeline/Migrations/20200808071611_UserAddUniqueId.Designer.cs delete mode 100644 Timeline/Migrations/20200808071611_UserAddUniqueId.cs delete mode 100644 Timeline/Migrations/20200810155908_AddTimesToUser.Designer.cs delete mode 100644 Timeline/Migrations/20200810155908_AddTimesToUser.cs delete mode 100644 Timeline/Migrations/20200810170533_MakePostAuthorOptional.Designer.cs delete mode 100644 Timeline/Migrations/20200810170533_MakePostAuthorOptional.cs delete mode 100644 Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.Designer.cs delete mode 100644 Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.cs delete mode 100644 Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs delete mode 100644 Timeline/Migrations/20200826164553_TimelineAddTitle.cs delete mode 100644 Timeline/Migrations/DatabaseContextModelSnapshot.cs delete mode 100644 Timeline/MockClientApp/index.html delete mode 100644 Timeline/Models/ByteData.cs delete mode 100644 Timeline/Models/Converters/JsonDateTimeConverter.cs delete mode 100644 Timeline/Models/Converters/MyDateTimeConverter.cs delete mode 100644 Timeline/Models/Http/ActionContextAccessorExtensions.cs delete mode 100644 Timeline/Models/Http/Common.cs delete mode 100644 Timeline/Models/Http/ErrorResponse.cs delete mode 100644 Timeline/Models/Http/Timeline.cs delete mode 100644 Timeline/Models/Http/TimelineController.cs delete mode 100644 Timeline/Models/Http/TokenController.cs delete mode 100644 Timeline/Models/Http/UserController.cs delete mode 100644 Timeline/Models/Http/UserInfo.cs delete mode 100644 Timeline/Models/Timeline.cs delete mode 100644 Timeline/Models/User.cs delete mode 100644 Timeline/Models/Validation/GeneralTimelineNameValidator.cs delete mode 100644 Timeline/Models/Validation/NameValidator.cs delete mode 100644 Timeline/Models/Validation/NicknameValidator.cs delete mode 100644 Timeline/Models/Validation/TimelineNameValidator.cs delete mode 100644 Timeline/Models/Validation/UsernameValidator.cs delete mode 100644 Timeline/Models/Validation/Validator.cs delete mode 100644 Timeline/Program.cs delete mode 100644 Timeline/Properties/launchSettings.json delete mode 100644 Timeline/Resources/Authentication/AuthHandler.Designer.cs delete mode 100644 Timeline/Resources/Authentication/AuthHandler.resx delete mode 100644 Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs delete mode 100644 Timeline/Resources/Controllers/ControllerAuthExtensions.resx delete mode 100644 Timeline/Resources/Controllers/TimelineController.Designer.cs delete mode 100644 Timeline/Resources/Controllers/TimelineController.resx delete mode 100644 Timeline/Resources/Controllers/TokenController.Designer.cs delete mode 100644 Timeline/Resources/Controllers/TokenController.resx delete mode 100644 Timeline/Resources/Controllers/UserAvatarController.Designer.cs delete mode 100644 Timeline/Resources/Controllers/UserAvatarController.resx delete mode 100644 Timeline/Resources/Controllers/UserController.Designer.cs delete mode 100644 Timeline/Resources/Controllers/UserController.resx delete mode 100644 Timeline/Resources/Entities.Designer.cs delete mode 100644 Timeline/Resources/Entities.resx delete mode 100644 Timeline/Resources/Filters.Designer.cs delete mode 100644 Timeline/Resources/Filters.resx delete mode 100644 Timeline/Resources/Helper/DataCacheHelper.Designer.cs delete mode 100644 Timeline/Resources/Helper/DataCacheHelper.resx delete mode 100644 Timeline/Resources/Messages.Designer.cs delete mode 100644 Timeline/Resources/Messages.resx delete mode 100644 Timeline/Resources/Models/Http/Common.Designer.cs delete mode 100644 Timeline/Resources/Models/Http/Common.resx delete mode 100644 Timeline/Resources/Models/Http/Exception.Designer.cs delete mode 100644 Timeline/Resources/Models/Http/Exception.resx delete mode 100644 Timeline/Resources/Models/Validation/NameValidator.Designer.cs delete mode 100644 Timeline/Resources/Models/Validation/NameValidator.resx delete mode 100644 Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs delete mode 100644 Timeline/Resources/Models/Validation/NicknameValidator.resx delete mode 100644 Timeline/Resources/Models/Validation/Validator.Designer.cs delete mode 100644 Timeline/Resources/Models/Validation/Validator.resx delete mode 100644 Timeline/Resources/Services/DataManager.Designer.cs delete mode 100644 Timeline/Resources/Services/DataManager.resx delete mode 100644 Timeline/Resources/Services/Exception.Designer.cs delete mode 100644 Timeline/Resources/Services/Exception.resx delete mode 100644 Timeline/Resources/Services/Exceptions.Designer.cs delete mode 100644 Timeline/Resources/Services/Exceptions.resx delete mode 100644 Timeline/Resources/Services/TimelineService.Designer.cs delete mode 100644 Timeline/Resources/Services/TimelineService.resx delete mode 100644 Timeline/Resources/Services/UserAvatarService.Designer.cs delete mode 100644 Timeline/Resources/Services/UserAvatarService.resx delete mode 100644 Timeline/Resources/Services/UserService.Designer.cs delete mode 100644 Timeline/Resources/Services/UserService.resx delete mode 100644 Timeline/Resources/Services/UserTokenService.Designer.cs delete mode 100644 Timeline/Resources/Services/UserTokenService.resx delete mode 100644 Timeline/Routes/ApiRoutePrefixConvention.cs delete mode 100644 Timeline/Routes/UnknownEndpointMiddleware.cs delete mode 100644 Timeline/Services/BadPasswordException.cs delete mode 100644 Timeline/Services/Clock.cs delete mode 100644 Timeline/Services/DataManager.cs delete mode 100644 Timeline/Services/DatabaseBackupService.cs delete mode 100644 Timeline/Services/DatabaseCorruptedException.cs delete mode 100644 Timeline/Services/ETagGenerator.cs delete mode 100644 Timeline/Services/EntityNames.cs delete mode 100644 Timeline/Services/Exceptions/EntityAlreadyExistError.cs delete mode 100644 Timeline/Services/Exceptions/EntityNotExistError.cs delete mode 100644 Timeline/Services/Exceptions/ExceptionMessageHelper.cs delete mode 100644 Timeline/Services/Exceptions/ImageException.cs delete mode 100644 Timeline/Services/Exceptions/TimelineNotExistException.cs delete mode 100644 Timeline/Services/Exceptions/TimelinePostNoDataException.cs delete mode 100644 Timeline/Services/Exceptions/TimelinePostNotExistException.cs delete mode 100644 Timeline/Services/Exceptions/UserNotExistException.cs delete mode 100644 Timeline/Services/ImageValidator.cs delete mode 100644 Timeline/Services/JwtUserTokenBadFormatException.cs delete mode 100644 Timeline/Services/PasswordBadFormatException.cs delete mode 100644 Timeline/Services/PasswordService.cs delete mode 100644 Timeline/Services/PathProvider.cs delete mode 100644 Timeline/Services/TimelineService.cs delete mode 100644 Timeline/Services/UserAvatarService.cs delete mode 100644 Timeline/Services/UserDeleteService.cs delete mode 100644 Timeline/Services/UserRoleConvert.cs delete mode 100644 Timeline/Services/UserService.cs delete mode 100644 Timeline/Services/UserTokenException.cs delete mode 100644 Timeline/Services/UserTokenManager.cs delete mode 100644 Timeline/Services/UserTokenService.cs delete mode 100644 Timeline/Startup.cs delete mode 100644 Timeline/Swagger/ApiConvention.cs delete mode 100644 Timeline/Swagger/ByteDataRequestOperationProcessor.cs delete mode 100644 Timeline/Swagger/DefaultDescriptionOperationProcessor.cs delete mode 100644 Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs delete mode 100644 Timeline/Timeline.csproj delete mode 100644 Timeline/appsettings.Development.json delete mode 100644 Timeline/appsettings.json delete mode 100644 Timeline/default-avatar.png delete mode 100644 Timeline/packages.lock.json (limited to 'Timeline') diff --git a/Timeline/Auth/Attribute.cs b/Timeline/Auth/Attribute.cs deleted file mode 100644 index 86d0109b..00000000 --- a/Timeline/Auth/Attribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Timeline.Entities; - -namespace Timeline.Auth -{ - public class AdminAuthorizeAttribute : AuthorizeAttribute - { - public AdminAuthorizeAttribute() - { - Roles = UserRoles.Admin; - } - } - - public class UserAuthorizeAttribute : AuthorizeAttribute - { - public UserAuthorizeAttribute() - { - Roles = UserRoles.User; - } - } -} diff --git a/Timeline/Auth/MyAuthenticationHandler.cs b/Timeline/Auth/MyAuthenticationHandler.cs deleted file mode 100644 index 3c97c329..00000000 --- a/Timeline/Auth/MyAuthenticationHandler.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; -using System; -using System.Globalization; -using System.Linq; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Timeline.Services; -using static Timeline.Resources.Authentication.AuthHandler; - -namespace Timeline.Auth -{ - public static class AuthenticationConstants - { - public const string Scheme = "Bearer"; - public const string DisplayName = "My Jwt Auth Scheme"; - } - - public class MyAuthenticationOptions : AuthenticationSchemeOptions - { - /// - /// The query param key to search for token. If null then query params are not searched for token. Default to "token". - /// - public string TokenQueryParamKey { get; set; } = "token"; - } - - public class MyAuthenticationHandler : AuthenticationHandler - { - private readonly ILogger _logger; - private readonly IUserTokenManager _userTokenManager; - - public MyAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserTokenManager userTokenManager) - : base(options, logger, encoder, clock) - { - _logger = logger.CreateLogger(); - _userTokenManager = userTokenManager; - } - - // return null if no token is found - private string? ExtractToken() - { - // check the authorization header - string header = Request.Headers[HeaderNames.Authorization]; - if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.InvariantCultureIgnoreCase)) - { - var token = header.Substring("Bearer ".Length).Trim(); - _logger.LogInformation(LogTokenFoundInHeader, token); - return token; - } - - // check the query params - var paramQueryKey = Options.TokenQueryParamKey; - if (!string.IsNullOrEmpty(paramQueryKey)) - { - string token = Request.Query[paramQueryKey]; - if (!string.IsNullOrEmpty(token)) - { - _logger.LogInformation(LogTokenFoundInQuery, paramQueryKey, token); - return token; - } - } - - // not found anywhere then return null - return null; - } - - protected override async Task HandleAuthenticateAsync() - { - var token = ExtractToken(); - if (string.IsNullOrEmpty(token)) - { - _logger.LogInformation(LogTokenNotFound); - return AuthenticateResult.NoResult(); - } - - try - { - var userInfo = await _userTokenManager.VerifyToken(token); - - var identity = new ClaimsIdentity(AuthenticationConstants.Scheme); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfo.Id!.Value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64)); - identity.AddClaim(new Claim(identity.NameClaimType, userInfo.Username, ClaimValueTypes.String)); - identity.AddClaims(UserRoleConvert.ToArray(userInfo.Administrator!.Value).Select(role => new Claim(identity.RoleClaimType, role, ClaimValueTypes.String))); - - var principal = new ClaimsPrincipal(); - principal.AddIdentity(identity); - - return AuthenticateResult.Success(new AuthenticationTicket(principal, AuthenticationConstants.Scheme)); - } - catch (Exception e) when (!(e is ArgumentException)) - { - _logger.LogInformation(e, LogTokenValidationFail); - return AuthenticateResult.Fail(e); - } - } - } -} diff --git a/Timeline/Auth/PrincipalExtensions.cs b/Timeline/Auth/PrincipalExtensions.cs deleted file mode 100644 index ad7a887f..00000000 --- a/Timeline/Auth/PrincipalExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Security.Principal; -using Timeline.Entities; - -namespace Timeline.Auth -{ - internal static class PrincipalExtensions - { - internal static bool IsAdministrator(this IPrincipal principal) - { - return principal.IsInRole(UserRoles.Admin); - } - } -} diff --git a/Timeline/ClientApp/.babelrc b/Timeline/ClientApp/.babelrc deleted file mode 100644 index 092f2f73..00000000 --- a/Timeline/ClientApp/.babelrc +++ /dev/null @@ -1,27 +0,0 @@ -{ - "presets": [ - "@babel/env", - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining", - "@babel/plugin-proposal-nullish-coalescing-operator", - [ - "@babel/plugin-proposal-decorators", - { - "decoratorsBeforeExport": true - } - ], - [ - "babel-plugin-transform-builtin-extend", - { - "globals": [ - "Error", - "Array" - ] - } - ] - ] -} \ No newline at end of file diff --git a/Timeline/ClientApp/.editorconfig b/Timeline/ClientApp/.editorconfig deleted file mode 100644 index 779719e0..00000000 --- a/Timeline/ClientApp/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true -end_of_line = lf - -[*.ts] -tab_width = 2 - -[*.tsx] -tab_width = 2 - -[*.css] -tab_width = 2 - -[*.sass] -tab_width = 2 diff --git a/Timeline/ClientApp/.eslintignore b/Timeline/ClientApp/.eslintignore deleted file mode 100644 index f29f7466..00000000 --- a/Timeline/ClientApp/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -.yarn -node_modules -dist -webpack.*.js -.eslintrc.js -postcss.config.js diff --git a/Timeline/ClientApp/.eslintrc.js b/Timeline/ClientApp/.eslintrc.js deleted file mode 100644 index 900489ed..00000000 --- a/Timeline/ClientApp/.eslintrc.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = { - env: { - browser: true, - es2020: true, - }, - extends: [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:prettier/recommended", - "prettier/react", - "prettier/@typescript-eslint", - "plugin:react-hooks/recommended", - ], - globals: { - Atomics: "readonly", - SharedArrayBuffer: "readonly", - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: ["./src/app/tsconfig.json", "./src/sw/tsconfig.json"], - ecmaFeatures: { - jsx: true, - }, - sourceType: "module", - }, - plugins: ["react", "@typescript-eslint", "react-hooks"], - settings: { - react: { - version: "detect", - }, - }, - rules: { - "react/prop-types": "off", - "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], - "@typescript-eslint/explicit-function-return-type": [ - "warn", - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, - }, - ], - }, -}; diff --git a/Timeline/ClientApp/.gitattributes b/Timeline/ClientApp/.gitattributes deleted file mode 100644 index c1aa21ac..00000000 --- a/Timeline/ClientApp/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -.yarn/** linguist-vendored \ No newline at end of file diff --git a/Timeline/ClientApp/.gitignore b/Timeline/ClientApp/.gitignore deleted file mode 100644 index 1de0b58f..00000000 --- a/Timeline/ClientApp/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules - -.yarn/* -!.yarn/cache -!.yarn/releases -!.yarn/plugins -!.yarn/sdks -!.yarn/versions - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -.vscode/launch.json - -/dist \ No newline at end of file diff --git a/Timeline/ClientApp/.vscode/extensions.json b/Timeline/ClientApp/.vscode/extensions.json deleted file mode 100644 index be640996..00000000 --- a/Timeline/ClientApp/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "arcanis.vscode-zipfs", - "syler.sass-indented", - "editorconfig.editorconfig" - ] -} diff --git a/Timeline/ClientApp/.vscode/preview.yml b/Timeline/ClientApp/.vscode/preview.yml deleted file mode 100644 index eb5e452c..00000000 --- a/Timeline/ClientApp/.vscode/preview.yml +++ /dev/null @@ -1,10 +0,0 @@ - -# .vscode/preview.yml -autoOpen: false # 打开工作空间时是否自动开启所有应用的预览 -apps: - - port: 3000 # 应用的端口 - run: yarn start:mock # 应用的启动命令 - root: . # 应用的启动目录 - name: timeline # 应用名称 - description: Timeline App # 应用描述 - autoOpen: false # 打开工作空间时是否自动开启预览(优先级高于根级 autoOpen) diff --git a/Timeline/ClientApp/.vscode/settings.json b/Timeline/ClientApp/.vscode/settings.json deleted file mode 100644 index 3db658ba..00000000 --- a/Timeline/ClientApp/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "eslint.nodePath": ".yarn/sdks", - "prettier.prettierPath": ".yarn/sdks/prettier/index.js", - "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true -} diff --git a/Timeline/ClientApp/.yarnrc.yml b/Timeline/ClientApp/.yarnrc.yml deleted file mode 100644 index 2e4e43a7..00000000 --- a/Timeline/ClientApp/.yarnrc.yml +++ /dev/null @@ -1,5 +0,0 @@ -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" - -yarnPath: .yarn/releases/yarn-2.1.1.cjs diff --git a/Timeline/ClientApp/LICENSE b/Timeline/ClientApp/LICENSE deleted file mode 100644 index 238cd2d9..00000000 --- a/Timeline/ClientApp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 杨宇千 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json deleted file mode 100644 index 65c5cbe2..00000000 --- a/Timeline/ClientApp/package.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "name": "timeline", - "version": "0.1.0", - "private": true, - "homepage": "https://crupest.xyz", - "keywords": [], - "description": "Timeline app.", - "dependencies": { - "axios": "^0.21.0", - "bootstrap": "^4.5.3", - "bootstrap-icons": "^1.0.0", - "classnames": "^2.2.6", - "clsx": "^1.1.1", - "core-js": "^3.6.5", - "i18next": "^19.8.3", - "i18next-browser-languagedetector": "^6.0.1", - "localforage": "^1.9.0", - "lodash": "^4.17.20", - "pepjs": "^0.5.2", - "react": "^17.0.1", - "react-bootstrap": "^1.4.0", - "react-dom": "^17.0.1", - "react-hot-loader": "^4.13.0", - "react-i18next": "^11.7.3", - "react-inlinesvg": "^2.1.1", - "react-responsive": "^8.1.0", - "react-router": "^5.2.0", - "react-router-bootstrap": "^0.25.0", - "react-router-dom": "^5.2.0", - "regenerator-runtime": "^0.13.7", - "rxjs": "^6.6.3", - "workbox-precaching": "^5.1.4", - "workbox-routing": "^5.1.4", - "workbox-strategies": "^5.1.4", - "workbox-window": "^5.1.4", - "xregexp": "^4.3.0" - }, - "scripts": { - "start": "webpack-dev-server --config ./webpack.config.dev.js", - "build": "webpack --config ./webpack.config.prod.js", - "lint": "eslint src/ --ext .js --ext .jsx --ext .ts --ext .tsx" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-decorators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.1", - "@babel/preset-typescript": "^7.12.1", - "@hot-loader/react-dom": "^17.0.0", - "@types/classnames": "^2.2.10", - "@types/lodash": "^4.14.162", - "@types/node": "^14.14.5", - "@types/react": "^16.9.53", - "@types/react-dom": "^16.9.8", - "@types/react-responsive": "^8.0.2", - "@types/react-router": "^5.1.8", - "@types/react-router-bootstrap": "^0.24.5", - "@types/react-router-dom": "^5.1.6", - "@types/webpack-env": "^1.15.3", - "@types/xregexp": "^4.3.0", - "@typescript-eslint/eslint-plugin": "^4.6.0", - "@typescript-eslint/parser": "^4.6.0", - "@yarnpkg/pnpify": "^2.3.3", - "babel-loader": "^8.1.0", - "babel-plugin-transform-builtin-extend": "^1.1.2", - "clean-webpack-plugin": "^3.0.0", - "copy-webpack-plugin": "^6.2.1", - "css-loader": "^5.0.0", - "eslint": "^7.12.1", - "eslint-config-prettier": "^6.14.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "file-loader": "^6.1.1", - "html-webpack-plugin": "^4.5.0", - "http-server": "^0.12.3", - "mini-css-extract-plugin": "^1.2.0", - "postcss": "^8.1.4", - "postcss-loader": "^4.0.4", - "postcss-preset-env": "^6.7.0", - "prettier": "^2.1.2", - "sass": "^1.27.0", - "sass-loader": "^10.0.4", - "style-loader": "^2.0.0", - "ts-loader": "^8.0.7", - "typescript": "^4.0.5", - "url-loader": "^4.1.1", - "webpack": "^5.2.0", - "webpack-chain": "^6.5.1", - "webpack-cli": "^4.1.0", - "webpack-dev-server": "^3.11.0", - "workbox-webpack-plugin": "^5.1.4" - } -} diff --git a/Timeline/ClientApp/postcss.config.js b/Timeline/ClientApp/postcss.config.js deleted file mode 100644 index 74ee8155..00000000 --- a/Timeline/ClientApp/postcss.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - plugins: [ - [ - "postcss-preset-env", - { - // Options - }, - ], - ], -}; diff --git a/Timeline/ClientApp/public/android-chrome-192x192.png b/Timeline/ClientApp/public/android-chrome-192x192.png deleted file mode 100644 index da9b6b81..00000000 Binary files a/Timeline/ClientApp/public/android-chrome-192x192.png and /dev/null differ diff --git a/Timeline/ClientApp/public/android-chrome-512x512.png b/Timeline/ClientApp/public/android-chrome-512x512.png deleted file mode 100644 index fa84e055..00000000 Binary files a/Timeline/ClientApp/public/android-chrome-512x512.png and /dev/null differ diff --git a/Timeline/ClientApp/public/apple-touch-icon.png b/Timeline/ClientApp/public/apple-touch-icon.png deleted file mode 100644 index d5a3fb45..00000000 Binary files a/Timeline/ClientApp/public/apple-touch-icon.png and /dev/null differ diff --git a/Timeline/ClientApp/public/browserconfig.xml b/Timeline/ClientApp/public/browserconfig.xml deleted file mode 100644 index f2c89409..00000000 --- a/Timeline/ClientApp/public/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #2d89ef - - - diff --git a/Timeline/ClientApp/public/favicon-16x16.png b/Timeline/ClientApp/public/favicon-16x16.png deleted file mode 100644 index 6c978995..00000000 Binary files a/Timeline/ClientApp/public/favicon-16x16.png and /dev/null differ diff --git a/Timeline/ClientApp/public/favicon-32x32.png b/Timeline/ClientApp/public/favicon-32x32.png deleted file mode 100644 index bbde902f..00000000 Binary files a/Timeline/ClientApp/public/favicon-32x32.png and /dev/null differ diff --git a/Timeline/ClientApp/public/favicon.ico b/Timeline/ClientApp/public/favicon.ico deleted file mode 100644 index d4cd3db6..00000000 Binary files a/Timeline/ClientApp/public/favicon.ico and /dev/null differ diff --git a/Timeline/ClientApp/public/mstile-144x144.png b/Timeline/ClientApp/public/mstile-144x144.png deleted file mode 100644 index 61eaaf43..00000000 Binary files a/Timeline/ClientApp/public/mstile-144x144.png and /dev/null differ diff --git a/Timeline/ClientApp/public/mstile-150x150.png b/Timeline/ClientApp/public/mstile-150x150.png deleted file mode 100644 index 85fa83ee..00000000 Binary files a/Timeline/ClientApp/public/mstile-150x150.png and /dev/null differ diff --git a/Timeline/ClientApp/public/mstile-310x150.png b/Timeline/ClientApp/public/mstile-310x150.png deleted file mode 100644 index 41889953..00000000 Binary files a/Timeline/ClientApp/public/mstile-310x150.png and /dev/null differ diff --git a/Timeline/ClientApp/public/mstile-310x310.png b/Timeline/ClientApp/public/mstile-310x310.png deleted file mode 100644 index cddce02e..00000000 Binary files a/Timeline/ClientApp/public/mstile-310x310.png and /dev/null differ diff --git a/Timeline/ClientApp/public/mstile-70x70.png b/Timeline/ClientApp/public/mstile-70x70.png deleted file mode 100644 index 52f59d43..00000000 Binary files a/Timeline/ClientApp/public/mstile-70x70.png and /dev/null differ diff --git a/Timeline/ClientApp/public/safari-pinned-tab.svg b/Timeline/ClientApp/public/safari-pinned-tab.svg deleted file mode 100644 index e91f046a..00000000 --- a/Timeline/ClientApp/public/safari-pinned-tab.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/Timeline/ClientApp/public/site.webmanifest b/Timeline/ClientApp/public/site.webmanifest deleted file mode 100644 index 74f0901a..00000000 --- a/Timeline/ClientApp/public/site.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/web-manifest", - - "name": "Timeline", - "short_name": "Timeline", - "description": "Record your life in Timeline! Created by crupest.", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/Timeline/ClientApp/sandbox.config.json b/Timeline/ClientApp/sandbox.config.json deleted file mode 100644 index 42b540ce..00000000 --- a/Timeline/ClientApp/sandbox.config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "infiniteLoopProtection": true, - "hardReloadOnChange": false, - "view": "browser", - "container": { - "port": 3000, - "startScript": "start:mock" - }, - "port": 3000, - "startScript": "start:mock" -} diff --git a/Timeline/ClientApp/src/app/App.tsx b/Timeline/ClientApp/src/app/App.tsx deleted file mode 100644 index b68eddb6..00000000 --- a/Timeline/ClientApp/src/app/App.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react"; -import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; -import { hot } from "react-hot-loader/root"; - -import AppBar from "./views/common/AppBar"; -import LoadingPage from "./views/common/LoadingPage"; -import Home from "./views/home"; -import Login from "./views/login"; -import Settings from "./views/settings"; -import About from "./views/about"; -import User from "./views/user"; -import TimelinePage from "./views/timeline"; -import AlertHost from "./views/common/alert/AlertHost"; - -import { dataStorage } from "./services/common"; -import { userService, useRawUser } from "./services/user"; - -const NoMatch: React.FC = () => { - return ( - <> - -
-
Ah-oh, 404!
- - ); -}; - -const LazyAdmin = React.lazy( - () => import(/* webpackChunkName: "admin" */ "./views/admin/Admin") -); - -const App: React.FC = () => { - const [loading, setLoading] = React.useState(true); - - const user = useRawUser(); - - React.useEffect(() => { - void userService.checkLoginState(); - void dataStorage.ready().then(() => setLoading(false)); - }, []); - - if (user === undefined || loading) { - return ; - } else { - return ( - }> - - - - - - - - - - - - - - - - - - - - - - {user && user.administrator && ( - - - - )} - - - - - - - - ); - } -}; - -export default hot(App); diff --git a/Timeline/ClientApp/src/app/common.ts b/Timeline/ClientApp/src/app/common.ts deleted file mode 100644 index 0a2d345f..00000000 --- a/Timeline/ClientApp/src/app/common.ts +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; -import { Observable, Subject } from "rxjs"; - -// This error is thrown when ui goes wrong with bad logic. -// Such as a variable should not be null, but it does. -// This error should never occur. If it does, it indicates there is some logic bug in codes. -export class UiLogicError extends Error {} - -export function useEventEmiiter(): [() => Observable, () => void] { - const ref = React.useRef | null>(null); - - return React.useMemo(() => { - const getter = (): Subject => { - if (ref.current == null) { - ref.current = new Subject(); - } - return ref.current; - }; - const trigger = (): void => { - getter().next(null); - }; - return [getter, trigger]; - }, []); -} - -export function useValueEventEmiiter(): [ - () => Observable, - (value: T) => void -] { - const ref = React.useRef | null>(null); - - return React.useMemo(() => { - const getter = (): Subject => { - if (ref.current == null) { - ref.current = new Subject(); - } - return ref.current; - }; - const trigger = (value: T): void => { - getter().next(value); - }; - return [getter, trigger]; - }, []); -} diff --git a/Timeline/ClientApp/src/app/http/common.ts b/Timeline/ClientApp/src/app/http/common.ts deleted file mode 100644 index 54203d1a..00000000 --- a/Timeline/ClientApp/src/app/http/common.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -export const apiBaseUrl = "/api"; - -export function base64(blob: Blob): Promise { - return new Promise((resolve) => { - const reader = new FileReader(); - reader.onload = function () { - resolve((reader.result as string).replace(/^data:.+;base64,/, "")); - }; - reader.readAsDataURL(blob); - }); -} - -export function extractStatusCode(error: AxiosError): number | null { - if (error.isAxiosError) { - const code = error?.response?.status; - if (typeof code === "number") { - return code; - } - } - return null; -} - -export interface CommonErrorResponse { - code: number; - message: string; -} - -export function extractErrorCode( - error: AxiosError -): number | null { - if (error.isAxiosError) { - const code = error.response?.data?.code; - if (typeof code === "number") { - return code; - } - } - return null; -} - -export class HttpNetworkError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export class HttpForbiddenError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export class NotModified {} - -export interface BlobWithEtag { - data: Blob; - etag: string; -} - -export function extractResponseData(res: AxiosResponse): T { - return res.data; -} - -export function catchIfStatusCodeIs< - TResult, - TErrorHandlerResult extends TResult | PromiseLike | null | undefined ->( - statusCode: number, - errorHandler: (error: AxiosError) => TErrorHandlerResult -): (error: AxiosError) => TErrorHandlerResult { - return (error: AxiosError) => { - if (extractStatusCode(error) == statusCode) { - return errorHandler(error); - } else { - throw error; - } - }; -} - -export function convertToIfStatusCodeIs( - statusCode: number, - newErrorType: { - new (innerError: AxiosError): NewError; - } -): (error: AxiosError) => never { - return catchIfStatusCodeIs(statusCode, (error) => { - throw new newErrorType(error); - }); -} - -export function catchIfErrorCodeIs< - TResult, - TErrorHandlerResult extends TResult | PromiseLike | null | undefined ->( - errorCode: number, - errorHandler: (error: AxiosError) => TErrorHandlerResult -): (error: AxiosError) => TErrorHandlerResult { - return (error: AxiosError) => { - if (extractErrorCode(error) == errorCode) { - return errorHandler(error); - } else { - throw error; - } - }; -} -export function convertToIfErrorCodeIs( - errorCode: number, - newErrorType: { - new (innerError: AxiosError): NewError; - } -): (error: AxiosError) => never { - return catchIfErrorCodeIs(errorCode, (error) => { - throw new newErrorType(error); - }); -} - -export function convertToNetworkError( - error: AxiosError -): never { - if (error.isAxiosError && error.response == null) { - throw new HttpNetworkError(error); - } else { - throw error; - } -} - -export function convertToForbiddenError( - error: AxiosError -): never { - if ( - error.isAxiosError && - error.response != null && - (error.response.status == 401 || error.response.status == 403) - ) { - throw new HttpForbiddenError(error); - } else { - throw error; - } -} - -export function convertToNotModified( - error: AxiosError -): NotModified { - if ( - error.isAxiosError && - error.response != null && - error.response.status == 304 - ) { - return new NotModified(); - } else { - throw error; - } -} - -export function convertToBlobWithEtag(res: AxiosResponse): BlobWithEtag { - return { - data: res.data, - etag: (res.headers as Record<"etag", string>)["etag"], - }; -} diff --git a/Timeline/ClientApp/src/app/http/timeline.ts b/Timeline/ClientApp/src/app/http/timeline.ts deleted file mode 100644 index eb7d5065..00000000 --- a/Timeline/ClientApp/src/app/http/timeline.ts +++ /dev/null @@ -1,544 +0,0 @@ -import axios, { AxiosError } from "axios"; - -import { updateQueryString, applyQueryParameters } from "../utilities/url"; - -import { - apiBaseUrl, - extractResponseData, - convertToNetworkError, - base64, - convertToIfStatusCodeIs, - convertToIfErrorCodeIs, - BlobWithEtag, - NotModified, - convertToNotModified, - convertToForbiddenError, - convertToBlobWithEtag, -} from "./common"; -import { HttpUser } from "./user"; - -export const kTimelineVisibilities = ["Public", "Register", "Private"] as const; - -export type TimelineVisibility = typeof kTimelineVisibilities[number]; - -export interface HttpTimelineInfo { - uniqueId: string; - name: string; - description: string; - owner: HttpUser; - visibility: TimelineVisibility; - lastModified: Date; - members: HttpUser[]; -} - -export interface HttpTimelineListQuery { - visibility?: TimelineVisibility; - relate?: string; - relateType?: "own" | "join"; -} - -export interface HttpTimelinePostRequest { - name: string; -} - -export interface HttpTimelinePostTextContent { - type: "text"; - text: string; -} - -export interface HttpTimelinePostImageContent { - type: "image"; -} - -export type HttpTimelinePostContent = - | HttpTimelinePostTextContent - | HttpTimelinePostImageContent; - -export interface HttpTimelinePostInfo { - id: number; - content: HttpTimelinePostContent; - time: Date; - lastUpdated: Date; - author: HttpUser; - deleted: false; -} - -export interface HttpTimelineDeletedPostInfo { - id: number; - time: Date; - lastUpdated: Date; - author?: HttpUser; - deleted: true; -} - -export type HttpTimelineGenericPostInfo = - | HttpTimelinePostInfo - | HttpTimelineDeletedPostInfo; - -export interface HttpTimelinePostPostRequestTextContent { - type: "text"; - text: string; -} - -export interface HttpTimelinePostPostRequestImageContent { - type: "image"; - data: Blob; -} - -export type HttpTimelinePostPostRequestContent = - | HttpTimelinePostPostRequestTextContent - | HttpTimelinePostPostRequestImageContent; - -export interface HttpTimelinePostPostRequest { - content: HttpTimelinePostPostRequestContent; - time?: Date; -} - -export interface HttpTimelinePatchRequest { - visibility?: TimelineVisibility; - description?: string; -} - -export class HttpTimelineNotExistError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export class HttpTimelinePostNotExistError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export class HttpTimelineNameConflictError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -//-------------------- begin: internal model -------------------- - -interface RawTimelineInfo { - uniqueId: string; - name: string; - description: string; - owner: HttpUser; - visibility: TimelineVisibility; - lastModified: string; - members: HttpUser[]; -} - -interface RawTimelinePostTextContent { - type: "text"; - text: string; -} - -interface RawTimelinePostImageContent { - type: "image"; - url: string; -} - -type RawTimelinePostContent = - | RawTimelinePostTextContent - | RawTimelinePostImageContent; - -interface RawTimelinePostInfo { - id: number; - content: RawTimelinePostContent; - time: string; - lastUpdated: string; - author: HttpUser; - deleted: false; -} - -interface RawTimelineDeletedPostInfo { - id: number; - time: string; - lastUpdated: string; - author: HttpUser; - deleted: true; -} - -type RawTimelineGenericPostInfo = - | RawTimelinePostInfo - | RawTimelineDeletedPostInfo; - -interface RawTimelinePostPostRequestTextContent { - type: "text"; - text: string; -} - -interface RawTimelinePostPostRequestImageContent { - type: "image"; - data: string; -} - -type RawTimelinePostPostRequestContent = - | RawTimelinePostPostRequestTextContent - | RawTimelinePostPostRequestImageContent; - -interface RawTimelinePostPostRequest { - content: RawTimelinePostPostRequestContent; - time?: string; -} - -//-------------------- end: internal model -------------------- - -function processRawTimelineInfo(raw: RawTimelineInfo): HttpTimelineInfo { - return { - ...raw, - lastModified: new Date(raw.lastModified), - }; -} - -function processRawTimelinePostInfo( - raw: RawTimelinePostInfo -): HttpTimelinePostInfo; -function processRawTimelinePostInfo( - raw: RawTimelineGenericPostInfo -): HttpTimelineGenericPostInfo; -function processRawTimelinePostInfo( - raw: RawTimelineGenericPostInfo -): HttpTimelineGenericPostInfo { - return { - ...raw, - time: new Date(raw.time), - lastUpdated: new Date(raw.lastUpdated), - }; -} - -export interface IHttpTimelineClient { - listTimeline(query: HttpTimelineListQuery): Promise; - getTimeline(timelineName: string): Promise; - getTimeline( - timelineName: string, - query: { - checkUniqueId?: string; - } - ): Promise; - getTimeline( - timelineName: string, - query: { - checkUniqueId?: string; - ifModifiedSince: Date; - } - ): Promise; - postTimeline( - req: HttpTimelinePostRequest, - token: string - ): Promise; - patchTimeline( - timelineName: string, - req: HttpTimelinePatchRequest, - token: string - ): Promise; - deleteTimeline(timelineName: string, token: string): Promise; - memberPut( - timelineName: string, - username: string, - token: string - ): Promise; - memberDelete( - timelineName: string, - username: string, - token: string - ): Promise; - listPost( - timelineName: string, - token?: string - ): Promise; - listPost( - timelineName: string, - token: string | undefined, - query: { - modifiedSince?: Date; - includeDeleted?: false; - } - ): Promise; - listPost( - timelineName: string, - token: string | undefined, - query: { - modifiedSince?: Date; - includeDeleted: true; - } - ): Promise; - getPostData( - timelineName: string, - postId: number, - token?: string - ): Promise; - getPostData( - timelineName: string, - postId: number, - token: string | undefined, - etag: string - ): Promise; - postPost( - timelineName: string, - req: HttpTimelinePostPostRequest, - token: string - ): Promise; - deletePost( - timelineName: string, - postId: number, - token: string - ): Promise; -} - -export class HttpTimelineClient implements IHttpTimelineClient { - listTimeline(query: HttpTimelineListQuery): Promise { - return axios - .get( - applyQueryParameters(`${apiBaseUrl}/timelines`, query) - ) - .then(extractResponseData) - .then((list) => list.map(processRawTimelineInfo)) - .catch(convertToNetworkError); - } - - getTimeline(timelineName: string): Promise; - getTimeline( - timelineName: string, - query: { - checkUniqueId?: string; - } - ): Promise; - getTimeline( - timelineName: string, - query: { - checkUniqueId?: string; - ifModifiedSince: Date; - } - ): Promise; - getTimeline( - timelineName: string, - query?: { - checkUniqueId?: string; - ifModifiedSince?: Date; - } - ): Promise { - return axios - .get( - applyQueryParameters(`${apiBaseUrl}/timelines/${timelineName}`, query) - ) - .then((res) => { - if (res.status === 304) { - return new NotModified(); - } else { - return processRawTimelineInfo(res.data); - } - }) - .catch(convertToIfStatusCodeIs(404, HttpTimelineNotExistError)) - .catch(convertToNetworkError); - } - - postTimeline( - req: HttpTimelinePostRequest, - token: string - ): Promise { - return axios - .post(`${apiBaseUrl}/timelines?token=${token}`, req) - .then(extractResponseData) - .then(processRawTimelineInfo) - .catch(convertToIfErrorCodeIs(11040101, HttpTimelineNameConflictError)) - .catch(convertToNetworkError); - } - - patchTimeline( - timelineName: string, - req: HttpTimelinePatchRequest, - token: string - ): Promise { - return axios - .patch( - `${apiBaseUrl}/timelines/${timelineName}?token=${token}`, - req - ) - .then(extractResponseData) - .then(processRawTimelineInfo) - .catch(convertToNetworkError); - } - - deleteTimeline(timelineName: string, token: string): Promise { - return axios - .delete(`${apiBaseUrl}/timelines/${timelineName}?token=${token}`) - .catch(convertToNetworkError) - .then(); - } - - memberPut( - timelineName: string, - username: string, - token: string - ): Promise { - return axios - .put( - `${apiBaseUrl}/timelines/${timelineName}/members/${username}?token=${token}` - ) - .catch(convertToNetworkError) - .then(); - } - - memberDelete( - timelineName: string, - username: string, - token: string - ): Promise { - return axios - .delete( - `${apiBaseUrl}/timelines/${timelineName}/members/${username}?token=${token}` - ) - .catch(convertToNetworkError) - .then(); - } - - listPost( - timelineName: string, - token?: string - ): Promise; - listPost( - timelineName: string, - token: string | undefined, - query: { - modifiedSince?: Date; - includeDeleted?: false; - } - ): Promise; - listPost( - timelineName: string, - token: string | undefined, - query: { - modifiedSince?: Date; - includeDeleted: true; - } - ): Promise; - listPost( - timelineName: string, - token?: string, - query?: { - modifiedSince?: Date; - includeDeleted?: boolean; - } - ): Promise { - let url = `${apiBaseUrl}/timelines/${timelineName}/posts`; - url = updateQueryString("token", token, url); - if (query != null) { - if (query.modifiedSince != null) { - url = updateQueryString( - "modifiedSince", - query.modifiedSince.toISOString(), - url - ); - } - if (query.includeDeleted != null) { - url = updateQueryString( - "includeDeleted", - query.includeDeleted ? "true" : "false", - url - ); - } - } - - return axios - .get(url) - .then(extractResponseData) - .catch(convertToIfStatusCodeIs(404, HttpTimelineNotExistError)) - .catch(convertToForbiddenError) - .catch(convertToNetworkError) - .then((rawPosts) => - rawPosts.map((raw) => processRawTimelinePostInfo(raw)) - ); - } - - getPostData( - timelineName: string, - postId: number, - token: string - ): Promise; - getPostData( - timelineName: string, - postId: number, - token?: string, - etag?: string - ): Promise { - const headers = - etag != null - ? { - "If-None-Match": etag, - } - : undefined; - - let url = `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`; - url = updateQueryString("token", token, url); - - return axios - .get(url, { - responseType: "blob", - headers, - }) - .then(convertToBlobWithEtag) - .catch(convertToNotModified) - .catch(convertToIfStatusCodeIs(404, HttpTimelinePostNotExistError)) - .catch(convertToNetworkError); - } - - async postPost( - timelineName: string, - req: HttpTimelinePostPostRequest, - token: string - ): Promise { - let content: RawTimelinePostPostRequestContent; - if (req.content.type === "image") { - const base64Data = await base64(req.content.data); - content = { - ...req.content, - data: base64Data, - } as RawTimelinePostPostRequestImageContent; - } else { - content = req.content; - } - const rawReq: RawTimelinePostPostRequest = { - content, - }; - if (req.time != null) { - rawReq.time = req.time.toISOString(); - } - return await axios - .post( - `${apiBaseUrl}/timelines/${timelineName}/posts?token=${token}`, - rawReq - ) - .then(extractResponseData) - .catch(convertToNetworkError) - .then((rawPost) => processRawTimelinePostInfo(rawPost)); - } - - deletePost( - timelineName: string, - postId: number, - token: string - ): Promise { - return axios - .delete( - `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}?token=${token}` - ) - .catch(convertToNetworkError) - .then(); - } -} - -let client: IHttpTimelineClient = new HttpTimelineClient(); - -export function getHttpTimelineClient(): IHttpTimelineClient { - return client; -} - -export function setHttpTimelineClient( - newClient: IHttpTimelineClient -): IHttpTimelineClient { - const old = client; - client = newClient; - return old; -} diff --git a/Timeline/ClientApp/src/app/http/token.ts b/Timeline/ClientApp/src/app/http/token.ts deleted file mode 100644 index ae0cf3f6..00000000 --- a/Timeline/ClientApp/src/app/http/token.ts +++ /dev/null @@ -1,72 +0,0 @@ -import axios, { AxiosError } from "axios"; - -import { - apiBaseUrl, - convertToNetworkError, - convertToIfErrorCodeIs, - extractResponseData, -} from "./common"; -import { HttpUser } from "./user"; - -export interface HttpCreateTokenRequest { - username: string; - password: string; - expire: number; -} - -export interface HttpCreateTokenResponse { - token: string; - user: HttpUser; -} - -export interface HttpVerifyTokenRequest { - token: string; -} - -export interface HttpVerifyTokenResponse { - user: HttpUser; -} - -export class HttpCreateTokenBadCredentialError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export interface IHttpTokenClient { - create(req: HttpCreateTokenRequest): Promise; - verify(req: HttpVerifyTokenRequest): Promise; -} - -export class HttpTokenClient implements IHttpTokenClient { - create(req: HttpCreateTokenRequest): Promise { - return axios - .post(`${apiBaseUrl}/token/create`, req) - .then(extractResponseData) - .catch( - convertToIfErrorCodeIs(11010101, HttpCreateTokenBadCredentialError) - ) - .catch(convertToNetworkError); - } - - verify(req: HttpVerifyTokenRequest): Promise { - return axios - .post(`${apiBaseUrl}/token/verify`, req) - .then(extractResponseData) - .catch(convertToNetworkError); - } -} - -let client: IHttpTokenClient = new HttpTokenClient(); - -export function getHttpTokenClient(): IHttpTokenClient { - return client; -} - -export function setHttpTokenClient( - newClient: IHttpTokenClient -): IHttpTokenClient { - const old = client; - client = newClient; - return old; -} diff --git a/Timeline/ClientApp/src/app/http/user.ts b/Timeline/ClientApp/src/app/http/user.ts deleted file mode 100644 index a0a02cce..00000000 --- a/Timeline/ClientApp/src/app/http/user.ts +++ /dev/null @@ -1,134 +0,0 @@ -import axios, { AxiosError } from "axios"; - -import { - apiBaseUrl, - convertToNetworkError, - extractResponseData, - convertToIfStatusCodeIs, - convertToIfErrorCodeIs, - NotModified, - BlobWithEtag, - convertToBlobWithEtag, - convertToNotModified, -} from "./common"; - -export interface HttpUser { - uniqueId: string; - username: string; - administrator: boolean; - nickname: string; -} - -export interface HttpUserPatchRequest { - nickname?: string; -} - -export interface HttpChangePasswordRequest { - oldPassword: string; - newPassword: string; -} - -export class HttpUserNotExistError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export class HttpChangePasswordBadCredentialError extends Error { - constructor(public innerError?: AxiosError) { - super(); - } -} - -export interface IHttpUserClient { - get(username: string): Promise; - patch( - username: string, - req: HttpUserPatchRequest, - token: string - ): Promise; - getAvatar(username: string): Promise; - getAvatar( - username: string, - etag: string - ): Promise; - putAvatar(username: string, data: Blob, token: string): Promise; - changePassword(req: HttpChangePasswordRequest, token: string): Promise; -} - -export class HttpUserClient implements IHttpUserClient { - get(username: string): Promise { - return axios - .get(`${apiBaseUrl}/users/${username}`) - .then(extractResponseData) - .catch(convertToIfStatusCodeIs(404, HttpUserNotExistError)) - .catch(convertToNetworkError); - } - - patch( - username: string, - req: HttpUserPatchRequest, - token: string - ): Promise { - return axios - .patch(`${apiBaseUrl}/users/${username}?token=${token}`, req) - .then(extractResponseData) - .catch(convertToNetworkError); - } - - getAvatar(username: string): Promise; - getAvatar( - username: string, - etag?: string - ): Promise { - const headers = - etag != null - ? { - "If-None-Match": etag, - } - : undefined; - - return axios - .get(`${apiBaseUrl}/users/${username}/avatar`, { - responseType: "blob", - headers, - }) - .then(convertToBlobWithEtag) - .catch(convertToNotModified) - .catch(convertToIfStatusCodeIs(404, HttpUserNotExistError)) - .catch(convertToNetworkError); - } - - putAvatar(username: string, data: Blob, token: string): Promise { - return axios - .put(`${apiBaseUrl}/users/${username}/avatar?token=${token}`, data, { - headers: { - "Content-Type": data.type, - }, - }) - .catch(convertToNetworkError) - .then(); - } - - changePassword(req: HttpChangePasswordRequest, token: string): Promise { - return axios - .post(`${apiBaseUrl}/userop/changepassword?token=${token}`, req) - .catch( - convertToIfErrorCodeIs(11020201, HttpChangePasswordBadCredentialError) - ) - .catch(convertToNetworkError) - .then(); - } -} - -let client: IHttpUserClient = new HttpUserClient(); - -export function getHttpUserClient(): IHttpUserClient { - return client; -} - -export function setHttpUserClient(newClient: IHttpUserClient): IHttpUserClient { - const old = client; - client = newClient; - return old; -} diff --git a/Timeline/ClientApp/src/app/i18n.ts b/Timeline/ClientApp/src/app/i18n.ts deleted file mode 100644 index cdced7bf..00000000 --- a/Timeline/ClientApp/src/app/i18n.ts +++ /dev/null @@ -1,79 +0,0 @@ -import i18n, { BackendModule, ResourceKey } from "i18next"; -import LanguageDetector from "i18next-browser-languagedetector"; -import { initReactI18next } from "react-i18next"; - -const backend: BackendModule = { - type: "backend", - async read(language, namespace, callback) { - function error(message: string): void { - callback(new Error(message), false); - } - - function success(result: ResourceKey): void { - callback(null, result); - } - - if (namespace !== "translation") { - error("Namespace must be 'translation'."); - } - - if (language === "en") { - const res = ( - await import( - /* webpackChunkName: "locales-en" */ "./locales/en/translation" - ) - ).default; - success(res); - } else if (language === "zh-cn" || language === "zh") { - const res = ( - await import( - /* webpackChunkName: "locales-zh" */ "./locales/zh/translation" - ) - ).default; - success(res); - } else { - error(`Language ${language} is not supported.`); - } - }, - init() {}, // eslint-disable-line @typescript-eslint/no-empty-function - create() {}, // eslint-disable-line @typescript-eslint/no-empty-function -}; - -export const i18nPromise = i18n - .use(LanguageDetector) - .use(backend) - .use(initReactI18next) // bind react-i18next to the instance - .init({ - fallbackLng: false, - lowerCaseLng: true, - - debug: process.env.NODE_ENV === "development", - - interpolation: { - escapeValue: false, // not needed for react!! - }, - - // react i18next special options (optional) - // override if needed - omit if ok with defaults - /* - react: { - bindI18n: 'languageChanged', - bindI18nStore: '', - transEmptyNodeValue: '', - transSupportBasicHtmlNodes: true, - transKeepBasicHtmlNodesFor: ['br', 'strong', 'i'], - useSuspense: true, - } - */ - }); - -if (module.hot) { - module.hot.accept( - ["./locales/en/translation", "./locales/zh/translation"], - () => { - void i18n.reloadResources(); - } - ); -} - -export default i18n; diff --git a/Timeline/ClientApp/src/app/index.ejs b/Timeline/ClientApp/src/app/index.ejs deleted file mode 100644 index 49306786..00000000 --- a/Timeline/ClientApp/src/app/index.ejs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - -
- - - diff --git a/Timeline/ClientApp/src/app/index.sass b/Timeline/ClientApp/src/app/index.sass deleted file mode 100644 index 08e03bac..00000000 --- a/Timeline/ClientApp/src/app/index.sass +++ /dev/null @@ -1,66 +0,0 @@ -@import '~bootstrap/scss/bootstrap' - -@import './views/common/common' -@import './views/common/alert/alert' -@import './views/home/home' -@import './views/about/about' -@import './views/login/login' -@import './views/timeline-common/timeline-common' -@import './views/timeline/timeline' -@import './views/user/user' - -body - margin: 0 - -small - line-height: 1.2 - -.flex-fix-length - flex-grow: 0 - flex-shrink: 0 - -.position-lt - left: 0 - top: 0 - -.avatar - width: 60px - &.large - width: 100px - &.small - width: 40px - -.mt-appbar - margin-top: 56px - -.icon-button - font-size: 1.4em - &.large - font-size: 1.6em - -.cursor-pointer - cursor: pointer - -textarea - resize: none - -.white-space-no-wrap - white-space: nowrap - -.cru-card - @extend .shadow - @extend .border - @extend .border-primary - @extend .rounded - @extend .bg-light - -.full-viewport-center-child - position: fixed - width: 100vw - height: 100vh - display: flex - justify-content: center - align-items: center - -.text-orange - color: $orange diff --git a/Timeline/ClientApp/src/app/index.tsx b/Timeline/ClientApp/src/app/index.tsx deleted file mode 100644 index 00a75a4a..00000000 --- a/Timeline/ClientApp/src/app/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import "regenerator-runtime"; -import "core-js/modules/es.promise"; -import "core-js/modules/es.array.iterator"; -import "pepjs"; - -import React from "react"; -import ReactDOM from "react-dom"; - -import "./index.sass"; - -import "./i18n"; - -import App from "./App"; - -ReactDOM.render(, document.getElementById("app")); diff --git a/Timeline/ClientApp/src/app/locales/en/translation.ts b/Timeline/ClientApp/src/app/locales/en/translation.ts deleted file mode 100644 index c7f33d1e..00000000 --- a/Timeline/ClientApp/src/app/locales/en/translation.ts +++ /dev/null @@ -1,202 +0,0 @@ -import TranslationResource from "../scheme"; - -const translation: TranslationResource = { - welcome: "Welcome!", - search: "Search", - loadFailReload: "Load failed, click <1>here to reload.", - serviceWorker: { - availableOffline: - "Timeline is now cached in your computer and you can use it offline. 🎉🎉🎉", - upgradePrompt: "App is getting a new version!", - upgradeNow: "Update Now", - upgradeSuccess: - "Congratulations! App update succeeded! Still you can use it offline. 🎉🎉🎉", - externalActivatedPrompt: - "A new version of app is activated. Please refresh the page. Or it may be broken.", - reloadNow: "Refresh Now", - }, - nav: { - settings: "Settings", - login: "Login", - about: "About", - }, - chooseImage: "Choose a image", - loadImageError: "Failed to load image.", - home: { - go: "Go!", - allTimeline: "All Timelines", - joinTimeline: "Joined Timelines", - ownTimeline: "Owned Timelines", - offlinePrompt: - "Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here to refresh.", - createButton: "Create Timeline", - createDialog: { - title: "Create Timeline!", - name: "Name", - nameFormat: - "Name must consist of only letter including non-English letter, digit, hyphen(-) and underline(_) and be no longer than 26.", - badFormat: "Bad format.", - noEmpty: "Empty is not allowed.", - tooLong: "Too long.", - }, - }, - operationDialog: { - retry: "Retry", - nextStep: "Next", - previousStep: "Previous", - confirm: "Confirm", - cancel: "Cancel", - ok: "OK!", - processing: "Processing...", - success: "Success!", - error: "An error occured.", - }, - timeline: { - messageCantSee: "Sorry, you are not allowed to see this timeline.😅", - userNotExist: "The user does not exist!", - timelineNotExist: "The timeline does not exist!", - manage: "Manage", - memberButton: "Member", - send: "Send", - deletePostFailed: "Failed to delete post.", - sendPostFailed: "Failed to send post.", - visibility: { - public: "public to everyone", - register: "only registed people can see", - private: "only members can see", - }, - visibilityTooltip: { - public: - "Everyone including those without accounts can see content of the timeline.", - register: - "Only those who have an account and logined can see content of the timeline.", - private: "Only members of this timeline can see content of the timeline.", - }, - dialogChangeProperty: { - title: "Change Timeline Properties", - visibility: "Visibility", - description: "Description", - }, - member: { - alreadyMember: "The user is already a member.", - add: "Add", - remove: "Remove", - }, - manageItem: { - nickname: "Nickname", - avatar: "Avatar", - property: "Timeline Property", - member: "Timeline Member", - delete: "Delete Timeline", - }, - deleteDialog: { - title: "Delete Timeline", - inputPrompt: - "This is a dangerous action. If you are sure to delete timeline<1>{{name}}, please input its name below and click confirm button.", - notMatch: "Name does not match.", - }, - postSyncState: { - syncing: "Syncing", - synced: "Synced", - offline: "Offline", - }, - post: { - deleteDialog: { - title: "Confirm Delete", - prompt: - "Are you sure to delete the post? This operation is not recoverable.", - }, - }, - }, - user: { - username: "username", - password: "password", - login: "login", - rememberMe: "Remember Me", - welcomeBack: "Welcome back!", - verifyTokenFailed: "User login info is expired. Please login again!", - verifyTokenFailedNetwork: - "Verifying user login info failed. Please check your network and refresh page!", - }, - login: { - emptyUsername: "Username can't be empty.", - emptyPassword: "Password can't be empty.", - badCredential: "Username or password is invalid.", - alreadyLogin: "Already login! Redirect to home page in 3s!", - }, - userPage: { - dialogChangeNickname: { - title: "Change Nickname", - inputLabel: "New nickname", - }, - dialogChangeAvatar: { - title: "Change Avatar", - previewImgAlt: "preview", - prompt: { - select: "Please select a picture.", - crop: "Please crop the picture.", - processingCrop: "Cropping picture...", - uploading: "Uploading...", - preview: "Please preview avatar", - }, - upload: "upload", - }, - }, - settings: { - subheaders: { - account: "Account", - customization: "Customization", - }, - languagePrimary: "Choose display language.", - languageSecondary: - "You language preference will be saved locally. Next time you visit this page, last language option will be used.", - changePassword: "Change account's password.", - logout: "Log out this account.", - gotoSelf: - "Click here to go to timeline of myself to change nickname and avatar.", - dialogChangePassword: { - title: "Change Password", - prompt: - "You are changing your password. You need to input the correct old password. After change, you need to login again and all old login will be invalid.", - inputOldPassword: "Old password", - inputNewPassword: "New password", - inputRetypeNewPassword: "Retype new password", - errorEmptyOldPassword: "Old password can't be empty.", - errorEmptyNewPassword: "New password can't be empty.", - errorRetypeNotMatch: "Password retyped does not match.", - }, - dialogConfirmLogout: { - title: "Confirm Logout", - prompt: - "Are you sure to log out? All cached data in the browser will be deleted.", - }, - }, - about: { - author: { - title: "Site Developer", - fullname: "Fullname: ", - nickname: "Nickname: ", - introduction: "Introduction: ", - introductionContent: "A programmer coding based on coincidence", - links: "Links: ", - }, - site: { - title: "Site Information", - content: - "The name of this site is <1>Timeline, which is a Web App with <3>timeline as its core concept. Its frontend and backend are both developed by <5>me, and open source on GitHub. It is relatively easy to deploy it on your own server, which is also one of my goals. Welcome to comment anything in GitHub repository.", - repo: "GitHub Repo", - }, - credits: { - title: "Credits", - content: - "Timeline is works standing on shoulders of gaints. Special appreciation for many open source projects listed below or not. Related licenses could be found in GitHub repository.", - frontend: "Frontend: ", - backend: "Backend: ", - }, - }, - admin: { - title: "admin", - }, -}; - -export default translation; diff --git a/Timeline/ClientApp/src/app/locales/scheme.ts b/Timeline/ClientApp/src/app/locales/scheme.ts deleted file mode 100644 index 9e3534ac..00000000 --- a/Timeline/ClientApp/src/app/locales/scheme.ts +++ /dev/null @@ -1,182 +0,0 @@ -export default interface TranslationResource { - welcome: string; - search: string; - chooseImage: string; - loadImageError: string; - loadFailReload: string; - serviceWorker: { - availableOffline: string; - upgradePrompt: string; - upgradeNow: string; - upgradeSuccess: string; - externalActivatedPrompt: string; - reloadNow: string; - }; - nav: { - settings: string; - login: string; - about: string; - }; - home: { - go: string; - allTimeline: string; - joinTimeline: string; - ownTimeline: string; - offlinePrompt: string; - createButton: string; - createDialog: { - title: string; - name: string; - nameFormat: string; - badFormat: string; - noEmpty: string; - tooLong: string; - }; - }; - operationDialog: { - retry: string; - nextStep: string; - previousStep: string; - confirm: string; - cancel: string; - ok: string; - processing: string; - success: string; - error: string; - }; - timeline: { - messageCantSee: string; - userNotExist: string; - timelineNotExist: string; - manage: string; - memberButton: string; - send: string; - deletePostFailed: string; - sendPostFailed: string; - visibility: { - public: string; - register: string; - private: string; - }; - visibilityTooltip: { - public: string; - register: string; - private: string; - }; - dialogChangeProperty: { - title: string; - visibility: string; - description: string; - }; - member: { - alreadyMember: string; - add: string; - remove: string; - }; - manageItem: { - nickname: string; - avatar: string; - property: string; - member: string; - delete: string; - }; - deleteDialog: { - title: string; - inputPrompt: string; - notMatch: string; - }; - postSyncState: { - syncing: string; - synced: string; - offline: string; - }; - post: { - deleteDialog: { - title: string; - prompt: string; - }; - }; - }; - user: { - username: string; - password: string; - login: string; - rememberMe: string; - welcomeBack: string; - verifyTokenFailed: string; - verifyTokenFailedNetwork: string; - }; - login: { - emptyUsername: string; - emptyPassword: string; - badCredential: string; - alreadyLogin: string; - }; - userPage: { - dialogChangeNickname: { - title: string; - inputLabel: string; - }; - dialogChangeAvatar: { - title: string; - previewImgAlt: string; - prompt: { - select: string; - crop: string; - processingCrop: string; - preview: string; - uploading: string; - }; - upload: string; - }; - }; - settings: { - subheaders: { - account: string; - customization: string; - }; - languagePrimary: string; - languageSecondary: string; - changePassword: string; - logout: string; - gotoSelf: string; - dialogChangePassword: { - title: string; - prompt: string; - inputOldPassword: string; - inputNewPassword: string; - inputRetypeNewPassword: string; - errorEmptyOldPassword: string; - errorEmptyNewPassword: string; - errorRetypeNotMatch: string; - }; - dialogConfirmLogout: { - title: string; - prompt: string; - }; - }; - about: { - author: { - title: string; - fullname: string; - nickname: string; - introduction: string; - introductionContent: string; - links: string; - }; - site: { - title: string; - content: string; - repo: string; - }; - credits: { - title: string; - content: string; - frontend: string; - backend: string; - }; - }; - admin: { - title: string; - }; -} diff --git a/Timeline/ClientApp/src/app/locales/zh/translation.ts b/Timeline/ClientApp/src/app/locales/zh/translation.ts deleted file mode 100644 index df316366..00000000 --- a/Timeline/ClientApp/src/app/locales/zh/translation.ts +++ /dev/null @@ -1,195 +0,0 @@ -import TranslationResource from "../scheme"; - -const translation: TranslationResource = { - welcome: "欢迎!", - search: "搜索", - loadFailReload: "加载失败,<1>点击重试。", - serviceWorker: { - availableOffline: "Timeline 已经缓存在本地,你可以离线使用它。🎉🎉🎉", - upgradePrompt: "App 有新版本!", - upgradeNow: "现在升级", - upgradeSuccess: "App 升级成功,当然,你仍可以离线使用它。 🎉🎉🎉", - externalActivatedPrompt: - "一个新的 App 版本已经激活,请刷新页面使用,否则页面可能会出现故障。", - reloadNow: "立刻刷新", - }, - nav: { - settings: "设置", - login: "登陆", - about: "关于", - }, - chooseImage: "选择一个图片", - loadImageError: "加载图片失败", - home: { - go: "冲!", - allTimeline: "所有的时间线", - joinTimeline: "加入的时间线", - ownTimeline: "拥有的时间线", - offlinePrompt: - "你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。", - createButton: "创建时间线", - createDialog: { - title: "创建时间线!", - name: "名字", - nameFormat: - "名字只能由字母、汉字、数字、下划线(_)和连字符(-)构成,且长度不能超过26.", - badFormat: "格式错误", - noEmpty: "不能为空", - tooLong: "太长了", - }, - }, - operationDialog: { - retry: "重试", - nextStep: "下一步", - previousStep: "上一步", - confirm: "确定", - cancel: "取消", - ok: "好的!", - processing: "处理中...", - success: "成功!", - error: "出错啦!", - }, - timeline: { - messageCantSee: "不好意思,你没有权限查看这个时间线。😅", - userNotExist: "该用户不存在!", - timelineNotExist: "该时间线不存在!", - manage: "管理", - memberButton: "成员", - send: "发送", - deletePostFailed: "删除消息失败。", - sendPostFailed: "发送消息失败。", - visibility: { - public: "对所有人公开", - register: "仅注册可见", - private: "仅成员可见", - }, - visibilityTooltip: { - public: "所有人都可以看到这个时间线的内容,包括没有注册的人。", - register: "只有拥有本网站的账号且登陆了的人才能看到这个时间线的内容。", - private: "只有这个时间线的成员可以看到这个时间线的内容。", - }, - dialogChangeProperty: { - title: "修改时间线属性", - visibility: "可见性", - description: "描述", - }, - member: { - alreadyMember: "该用户已经是一个成员。", - add: "添加", - remove: "移除", - }, - manageItem: { - nickname: "昵称", - avatar: "头像", - property: "时间线属性", - member: "时间线成员", - delete: "删除时间线", - }, - deleteDialog: { - title: "删除时间线", - inputPrompt: - "这是一个危险的操作。如果您确认要删除时间线<1>{{name}},请在下面输入它的名字并点击确认。", - notMatch: "名字不匹配", - }, - postSyncState: { - syncing: "同步中", - synced: "同步成功", - offline: "离线", - }, - post: { - deleteDialog: { - title: "确认删除", - prompt: "确定删除这个消息?这个操作不可撤销。", - }, - }, - }, - user: { - username: "用户名", - password: "密码", - login: "登录", - rememberMe: "记住我", - welcomeBack: "欢迎回来!", - verifyTokenFailed: "用户登录信息已过期,请重新登陆!", - verifyTokenFailedNetwork: - "验证用户登录信息失败,请检查网络连接并刷新页面!", - }, - login: { - emptyUsername: "用户名不能为空。", - emptyPassword: "密码不能为空。", - badCredential: "用户名或密码错误。", - alreadyLogin: "已经登陆,三秒后导航到首页!", - }, - userPage: { - dialogChangeNickname: { - title: "更改昵称", - inputLabel: "新昵称", - }, - dialogChangeAvatar: { - title: "修改头像", - previewImgAlt: "预览", - prompt: { - select: "请选择一个图片", - crop: "请裁剪图片", - processingCrop: "正在裁剪图片", - uploading: "正在上传", - preview: "请预览图片", - }, - upload: "上传", - }, - }, - settings: { - subheaders: { - account: "账户", - customization: "个性化", - }, - languagePrimary: "选择显示的语言。", - languageSecondary: - "您的语言偏好将会存储在本地,下次浏览时将自动使用上次保存的语言选项。", - changePassword: "更改账号的密码。", - logout: "注销此账号。", - gotoSelf: "点击前往个人时间线修改昵称和头像!", - dialogChangePassword: { - title: "修改密码", - prompt: - "您正在修改密码,您需要输入正确的旧密码。成功修改后您需要重新登陆,而且以前所有的登录都会失效。", - inputOldPassword: "旧密码", - inputNewPassword: "新密码", - inputRetypeNewPassword: "再次输入新密码", - errorEmptyOldPassword: "旧密码不能为空。", - errorEmptyNewPassword: "新密码不能为空", - errorRetypeNotMatch: "两次输入的密码不一致", - }, - dialogConfirmLogout: { - title: "确定注销", - prompt: "您确定注销此账号?这将删除所有已经缓存在浏览器的数据。", - }, - }, - about: { - author: { - title: "网站作者", - fullname: "姓名:", - nickname: "昵称:", - introduction: "简介:", - introductionContent: "一个基于巧合编程的代码爱好者。", - links: "链接:", - }, - site: { - title: "网站信息", - content: - "这个网站的名字叫 <1>Timeline,是一个以<3>时间线为核心概念的 Web App . 它的前端和后端都是由<5>我开发,并且在 GitHub 上开源。大家可以相对轻松的把它们部署在自己的服务器上,这也是我的目标之一。欢迎大家前往 GitHub 仓库提出任何意见。", - repo: "GitHub 仓库", - }, - credits: { - title: "鸣谢", - content: - "Timeline 是站在巨人肩膀上的作品,感谢以下列出的和其他未列出的许多开源项目,相关 License 请在 GitHub 仓库中查看。", - frontend: "前端:", - backend: "后端:", - }, - }, - admin: { - title: "管理", - }, -}; - -export default translation; diff --git a/Timeline/ClientApp/src/app/service-worker.tsx b/Timeline/ClientApp/src/app/service-worker.tsx deleted file mode 100644 index 3be54bc1..00000000 --- a/Timeline/ClientApp/src/app/service-worker.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Button } from "react-bootstrap"; - -import { pushAlert } from "./services/alert"; - -if ("serviceWorker" in navigator) { - let isThisTriggerUpgrade = false; - - const upgradeSuccessLocalStorageKey = "TIMELINE_UPGRADE_SUCCESS"; - - if (window.localStorage.getItem(upgradeSuccessLocalStorageKey)) { - pushAlert({ - message: { - type: "i18n", - key: "serviceWorker.upgradeSuccess", - }, - type: "success", - }); - window.localStorage.removeItem(upgradeSuccessLocalStorageKey); - } - - void import("workbox-window").then(({ Workbox, messageSW }) => { - const wb = new Workbox("/sw.js"); - let registration: ServiceWorkerRegistration | undefined; - - // externalactivated is not usable but I still use its name. - wb.addEventListener("controlling", () => { - const upgradeReload = (): void => { - window.localStorage.setItem(upgradeSuccessLocalStorageKey, "true"); - window.location.reload(); - }; - - if (isThisTriggerUpgrade) { - upgradeReload(); - } else { - const Message: React.FC = () => { - const { t } = useTranslation(); - return ( - <> - {t("serviceWorker.externalActivatedPrompt")} - - - ); - }; - - pushAlert({ - message: Message, - dismissTime: "never", - type: "warning", - }); - } - }); - - wb.addEventListener("activated", (event) => { - if (!event.isUpdate) { - pushAlert({ - message: { - type: "i18n", - key: "serviceWorker.availableOffline", - }, - type: "success", - }); - } - }); - - const showSkipWaitingPrompt = (): void => { - const upgrade = (): void => { - isThisTriggerUpgrade = true; - if (registration && registration.waiting) { - // Send a message to the waiting service worker, - // instructing it to activate. - // Note: for this to work, you have to add a message - // listener in your service worker. See below. - void messageSW(registration.waiting, { type: "SKIP_WAITING" }); - } - }; - - const UpgradeMessage: React.FC = () => { - const { t } = useTranslation(); - return ( - <> - {t("serviceWorker.upgradePrompt")} - - - ); - }; - - pushAlert({ - message: UpgradeMessage, - dismissTime: "never", - type: "success", - }); - }; - - // Add an event listener to detect when the registered - // service worker has installed but is waiting to activate. - wb.addEventListener("waiting", showSkipWaitingPrompt); - wb.addEventListener("externalwaiting", showSkipWaitingPrompt); - - void wb.register().then((reg) => { - registration = reg; - }); - }); -} diff --git a/Timeline/ClientApp/src/app/services/DataHub.ts b/Timeline/ClientApp/src/app/services/DataHub.ts deleted file mode 100644 index 93a9b41f..00000000 --- a/Timeline/ClientApp/src/app/services/DataHub.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { pull } from "lodash"; -import { Observable, BehaviorSubject, combineLatest } from "rxjs"; -import { map } from "rxjs/operators"; - -export type Subscriber = (data: TData) => void; - -export type WithSyncStatus = T & { syncing: boolean }; - -export class DataLine { - private _current: TData | undefined = undefined; - - private _syncPromise: Promise | null = null; - private _syncingSubject = new BehaviorSubject(false); - - private _observers: Subscriber[] = []; - - constructor( - private config: { - sync: () => Promise; - destroyable?: (value: TData | undefined) => boolean; - disableInitSync?: boolean; - } - ) { - if (config.disableInitSync !== true) { - setImmediate(() => void this.sync()); - } - } - - private subscribe(subscriber: Subscriber): void { - this._observers.push(subscriber); - if (this._current !== undefined) { - subscriber(this._current); - } - } - - private unsubscribe(subscriber: Subscriber): void { - if (!this._observers.includes(subscriber)) return; - pull(this._observers, subscriber); - } - - getObservable(): Observable { - return new Observable((observer) => { - const f = (data: TData): void => { - observer.next(data); - }; - this.subscribe(f); - - return () => { - this.unsubscribe(f); - }; - }); - } - - getSyncStatusObservable(): Observable { - return this._syncingSubject.asObservable(); - } - - getDataWithSyncStatusObservable(): Observable> { - return combineLatest([ - this.getObservable(), - this.getSyncStatusObservable(), - ]).pipe( - map(([data, syncing]) => ({ - ...data, - syncing, - })) - ); - } - - get value(): TData | undefined { - return this._current; - } - - next(value: TData): void { - this._current = value; - this._observers.forEach((observer) => observer(value)); - } - - get isSyncing(): boolean { - return this._syncPromise != null; - } - - sync(): Promise { - if (this._syncPromise == null) { - this._syncingSubject.next(true); - this._syncPromise = this.config.sync().then(() => { - this._syncingSubject.next(false); - this._syncPromise = null; - }); - } - - return this._syncPromise; - } - - syncWithAction( - syncAction: (line: DataLine) => Promise - ): Promise { - if (this._syncPromise == null) { - this._syncingSubject.next(true); - this._syncPromise = syncAction(this).then(() => { - this._syncingSubject.next(false); - this._syncPromise = null; - }); - } - - return this._syncPromise; - } - - get destroyable(): boolean { - const customDestroyable = this.config?.destroyable; - - return ( - this._observers.length === 0 && - !this.isSyncing && - (customDestroyable != null ? customDestroyable(this._current) : true) - ); - } -} - -export class DataHub { - private sync: (key: TKey, line: DataLine) => Promise; - private keyToString: (key: TKey) => string; - private destroyable?: (key: TKey, value: TData | undefined) => boolean; - - private readonly subscriptionLineMap = new Map>(); - - private cleanTimerId = 0; - - // setup is called after creating line and if it returns a function as destroyer, then when the line is destroyed the destroyer will be called. - constructor(config: { - sync: (key: TKey, line: DataLine) => Promise; - keyToString?: (key: TKey) => string; - destroyable?: (key: TKey, value: TData | undefined) => boolean; - }) { - this.sync = config.sync; - this.keyToString = - config.keyToString ?? - ((value): string => { - if (typeof value === "string") return value; - else - throw new Error( - "Default keyToString function only pass string value." - ); - }); - - this.destroyable = config.destroyable; - } - - private cleanLines(): void { - const toDelete: string[] = []; - for (const [key, line] of this.subscriptionLineMap.entries()) { - if (line.destroyable) { - toDelete.push(key); - } - } - - if (toDelete.length === 0) return; - - for (const key of toDelete) { - this.subscriptionLineMap.delete(key); - } - - if (this.subscriptionLineMap.size === 0) { - window.clearInterval(this.cleanTimerId); - this.cleanTimerId = 0; - } - } - - private createLine(key: TKey, disableInitSync = false): DataLine { - const keyString = this.keyToString(key); - const { destroyable } = this; - const newLine: DataLine = new DataLine({ - sync: () => this.sync(key, newLine), - destroyable: - destroyable != null ? (value) => destroyable(key, value) : undefined, - disableInitSync: disableInitSync, - }); - this.subscriptionLineMap.set(keyString, newLine); - if (this.subscriptionLineMap.size === 1) { - this.cleanTimerId = window.setInterval(this.cleanLines.bind(this), 20000); - } - return newLine; - } - - getObservable(key: TKey): Observable { - return this.getLineOrCreate(key).getObservable(); - } - - getSyncStatusObservable(key: TKey): Observable { - return this.getLineOrCreate(key).getSyncStatusObservable(); - } - - getDataWithSyncStatusObservable( - key: TKey - ): Observable> { - return this.getLineOrCreate(key).getDataWithSyncStatusObservable(); - } - - getLine(key: TKey): DataLine | null { - const keyString = this.keyToString(key); - return this.subscriptionLineMap.get(keyString) ?? null; - } - - getLineOrCreate(key: TKey): DataLine { - const keyString = this.keyToString(key); - return this.subscriptionLineMap.get(keyString) ?? this.createLine(key); - } - - getLineOrCreateWithoutInitSync(key: TKey): DataLine { - const keyString = this.keyToString(key); - return ( - this.subscriptionLineMap.get(keyString) ?? this.createLine(key, true) - ); - } - - optionalInitLineWithSyncAction( - key: TKey, - syncAction: (line: DataLine) => Promise - ): Promise { - const optionalLine = this.getLine(key); - if (optionalLine != null) return Promise.resolve(); - const line = this.createLine(key, true); - return line.syncWithAction(syncAction); - } -} diff --git a/Timeline/ClientApp/src/app/services/alert.ts b/Timeline/ClientApp/src/app/services/alert.ts deleted file mode 100644 index e4c0e653..00000000 --- a/Timeline/ClientApp/src/app/services/alert.ts +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import pull from "lodash/pull"; - -export interface AlertInfo { - type?: "primary" | "secondary" | "success" | "danger" | "warning" | "info"; - message: string | React.FC | { type: "i18n"; key: string }; - dismissTime?: number | "never"; -} - -export interface AlertInfoEx extends AlertInfo { - id: number; -} - -export type AlertConsumer = (alerts: AlertInfoEx) => void; - -export class AlertService { - private consumers: AlertConsumer[] = []; - private savedAlerts: AlertInfoEx[] = []; - private currentId = 1; - - private produce(alert: AlertInfoEx): void { - for (const consumer of this.consumers) { - consumer(alert); - } - } - - registerConsumer(consumer: AlertConsumer): void { - this.consumers.push(consumer); - if (this.savedAlerts.length !== 0) { - for (const alert of this.savedAlerts) { - this.produce(alert); - } - this.savedAlerts = []; - } - } - - unregisterConsumer(consumer: AlertConsumer): void { - pull(this.consumers, consumer); - } - - push(alert: AlertInfo): void { - const newAlert: AlertInfoEx = { ...alert, id: this.currentId++ }; - if (this.consumers.length === 0) { - this.savedAlerts.push(newAlert); - } else { - this.produce(newAlert); - } - } -} - -export const alertService = new AlertService(); - -export function pushAlert(alert: AlertInfo): void { - alertService.push(alert); -} - -export const kAlertHostId = "alert-host"; - -export function getAlertHost(): HTMLElement | null { - return document.getElementById(kAlertHostId); -} diff --git a/Timeline/ClientApp/src/app/services/common.ts b/Timeline/ClientApp/src/app/services/common.ts deleted file mode 100644 index 3bb6b9d7..00000000 --- a/Timeline/ClientApp/src/app/services/common.ts +++ /dev/null @@ -1,23 +0,0 @@ -import localforage from "localforage"; - -import { HttpNetworkError } from "@/http/common"; - -export const dataStorage = localforage.createInstance({ - name: "data", - description: "Database for offline data.", - driver: localforage.INDEXEDDB, -}); - -export class ForbiddenError extends Error { - constructor(message?: string) { - super(message); - } -} - -export function throwIfNotNetworkError(e: unknown): void { - if (!(e instanceof HttpNetworkError)) { - throw e; - } -} - -export type BlobOrStatus = Blob | "loading" | "error"; diff --git a/Timeline/ClientApp/src/app/services/timeline.ts b/Timeline/ClientApp/src/app/services/timeline.ts deleted file mode 100644 index 9db76281..00000000 --- a/Timeline/ClientApp/src/app/services/timeline.ts +++ /dev/null @@ -1,702 +0,0 @@ -import React from "react"; -import XRegExp from "xregexp"; -import { Observable, from, combineLatest, of } from "rxjs"; -import { map, switchMap, startWith } from "rxjs/operators"; -import { uniqBy } from "lodash"; - -import { convertError } from "@/utilities/rxjs"; -import { - TimelineVisibility, - HttpTimelineInfo, - HttpTimelinePatchRequest, - HttpTimelinePostPostRequest, - HttpTimelinePostPostRequestContent, - HttpTimelinePostPostRequestTextContent, - HttpTimelinePostPostRequestImageContent, - HttpTimelinePostInfo, - HttpTimelinePostTextContent, - getHttpTimelineClient, - HttpTimelineNotExistError, - HttpTimelineNameConflictError, -} from "@/http/timeline"; -import { BlobWithEtag, NotModified, HttpForbiddenError } from "@/http/common"; -import { HttpUser } from "@/http/user"; - -export { kTimelineVisibilities } from "@/http/timeline"; - -export type { TimelineVisibility } from "@/http/timeline"; - -import { dataStorage, throwIfNotNetworkError, BlobOrStatus } from "./common"; -import { DataHub, WithSyncStatus } from "./DataHub"; -import { UserAuthInfo, checkLogin, userService, userInfoService } from "./user"; - -export type TimelineInfo = HttpTimelineInfo; -export type TimelineChangePropertyRequest = HttpTimelinePatchRequest; -export type TimelineCreatePostRequest = HttpTimelinePostPostRequest; -export type TimelineCreatePostContent = HttpTimelinePostPostRequestContent; -export type TimelineCreatePostTextContent = HttpTimelinePostPostRequestTextContent; -export type TimelineCreatePostImageContent = HttpTimelinePostPostRequestImageContent; - -export type TimelinePostTextContent = HttpTimelinePostTextContent; - -export interface TimelinePostImageContent { - type: "image"; - data: BlobOrStatus; -} - -export type TimelinePostContent = - | TimelinePostTextContent - | TimelinePostImageContent; - -export interface TimelinePostInfo { - id: number; - content: TimelinePostContent; - time: Date; - lastUpdated: Date; - author: HttpUser; -} - -export const timelineVisibilityTooltipTranslationMap: Record< - TimelineVisibility, - string -> = { - Public: "timeline.visibilityTooltip.public", - Register: "timeline.visibilityTooltip.register", - Private: "timeline.visibilityTooltip.private", -}; - -export class TimelineNotExistError extends Error {} -export class TimelineNameConflictError extends Error {} - -export type TimelineWithSyncStatus = WithSyncStatus< - | { - type: "cache"; - timeline: TimelineInfo; - } - | { - type: "offline" | "synced"; - timeline: TimelineInfo | null; - } ->; - -export type TimelinePostsWithSyncState = WithSyncStatus<{ - type: - | "cache" - | "offline" // Sync failed and use cache. - | "synced" // Sync succeeded. - | "forbid" // The list is forbidden to see. - | "notexist"; // The timeline does not exist. - posts: TimelinePostInfo[]; -}>; - -type TimelineData = Omit & { - owner: string; - members: string[]; -}; - -type TimelinePostData = Omit & { - author: string; -}; - -export class TimelineService { - private getCachedTimeline( - timelineName: string - ): Promise { - return dataStorage.getItem(`timeline.${timelineName}`); - } - - private saveTimeline( - timelineName: string, - data: TimelineData - ): Promise { - return dataStorage - .setItem(`timeline.${timelineName}`, data) - .then(); - } - - private async clearTimelineData(timelineName: string): Promise { - const keys = (await dataStorage.keys()).filter((k) => - k.startsWith(`timeline.${timelineName}`) - ); - await Promise.all(keys.map((k) => dataStorage.removeItem(k))); - } - - private convertHttpTimelineToData(timeline: HttpTimelineInfo): TimelineData { - return { - ...timeline, - owner: timeline.owner.username, - members: timeline.members.map((m) => m.username), - }; - } - - private _timelineHub = new DataHub< - string, - | { - type: "cache"; - timeline: TimelineData; - } - | { - type: "offline" | "synced"; - timeline: TimelineData | null; - } - >({ - sync: async (key, line) => { - const cache = await this.getCachedTimeline(key); - - if (line.value == undefined) { - if (cache != null) { - line.next({ type: "cache", timeline: cache }); - } - } - - try { - const httpTimeline = await getHttpTimelineClient().getTimeline(key); - - userInfoService.saveUsers([ - httpTimeline.owner, - ...httpTimeline.members, - ]); - - const timeline = this.convertHttpTimelineToData(httpTimeline); - - if (cache != null && timeline.uniqueId !== cache.uniqueId) { - console.log( - `Timeline with name ${key} has changed to a new one. Clear old data.` - ); - await this.clearTimelineData(key); // If timeline has changed, clear all old data. - } - - await this.saveTimeline(key, timeline); - - line.next({ type: "synced", timeline }); - } catch (e) { - if (e instanceof HttpTimelineNotExistError) { - line.next({ type: "synced", timeline: null }); - } else { - if (cache == null) { - line.next({ type: "offline", timeline: null }); - } else { - line.next({ type: "offline", timeline: cache }); - } - throwIfNotNetworkError(e); - } - } - }, - }); - - syncTimeline(timelineName: string): Promise { - return this._timelineHub.getLineOrCreate(timelineName).sync(); - } - - getTimeline$(timelineName: string): Observable { - return this._timelineHub.getDataWithSyncStatusObservable(timelineName).pipe( - switchMap((state) => { - const { timeline } = state; - if (timeline != null) { - return combineLatest( - [timeline.owner, ...timeline.members].map((u) => - userInfoService.getUser$(u) - ) - ).pipe( - map((users) => { - return { - ...state, - timeline: { - ...timeline, - owner: users[0], - members: users.slice(1), - }, - }; - }) - ); - } else { - return of(state as TimelineWithSyncStatus); - } - }) - ); - } - - createTimeline(timelineName: string): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient().postTimeline( - { - name: timelineName, - }, - user.token - ) - ).pipe( - convertError(HttpTimelineNameConflictError, TimelineNameConflictError) - ); - } - - changeTimelineProperty( - timelineName: string, - req: TimelineChangePropertyRequest - ): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient() - .patchTimeline(timelineName, req, user.token) - .then((timeline) => { - void this.syncTimeline(timelineName); - return timeline; - }) - ); - } - - deleteTimeline(timelineName: string): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient().deleteTimeline(timelineName, user.token) - ); - } - - addMember(timelineName: string, username: string): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient() - .memberPut(timelineName, username, user.token) - .then(() => { - void this.syncTimeline(timelineName); - }) - ); - } - - removeMember(timelineName: string, username: string): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient() - .memberDelete(timelineName, username, user.token) - .then(() => { - void this.syncTimeline(timelineName); - }) - ); - } - - private convertHttpPostToData(post: HttpTimelinePostInfo): TimelinePostData { - return { - ...post, - author: post.author.username, - }; - } - - private convertHttpPostToDataList( - posts: HttpTimelinePostInfo[] - ): TimelinePostData[] { - return posts.map((post) => this.convertHttpPostToData(post)); - } - - private getCachedPosts( - timelineName: string - ): Promise { - return dataStorage.getItem( - `timeline.${timelineName}.posts` - ); - } - - private savePosts( - timelineName: string, - data: TimelinePostData[] - ): Promise { - return dataStorage - .setItem(`timeline.${timelineName}.posts`, data) - .then(); - } - - private syncPosts(timelineName: string): Promise { - return this._postsHub.getLineOrCreate(timelineName).sync(); - } - - private _postsHub = new DataHub< - string, - { - type: "cache" | "offline" | "synced" | "forbid" | "notexist"; - posts: TimelinePostData[]; - } - >({ - sync: async (key, line) => { - // Wait for timeline synced. In case the timeline has changed to another and old data has been cleaned. - await this.syncTimeline(key); - - if (line.value == null) { - const cache = await this.getCachedPosts(key); - if (cache != null) { - line.next({ type: "cache", posts: cache }); - } - } - - const now = new Date(); - - const lastUpdatedTime = await dataStorage.getItem( - `timeline.${key}.lastUpdated` - ); - - try { - if (lastUpdatedTime == null) { - const httpPosts = await getHttpTimelineClient().listPost( - key, - userService.currentUser?.token - ); - - userInfoService.saveUsers( - uniqBy( - httpPosts.map((post) => post.author), - "username" - ) - ); - - const posts = this.convertHttpPostToDataList(httpPosts); - await this.savePosts(key, posts); - await dataStorage.setItem(`timeline.${key}.lastUpdated`, now); - - line.next({ type: "synced", posts }); - } else { - const httpPosts = await getHttpTimelineClient().listPost( - key, - userService.currentUser?.token, - { - modifiedSince: lastUpdatedTime, - includeDeleted: true, - } - ); - - const deletedIds = httpPosts - .filter((p) => p.deleted) - .map((p) => p.id); - const changed = httpPosts.filter( - (p): p is HttpTimelinePostInfo => !p.deleted - ); - - userInfoService.saveUsers( - uniqBy( - httpPosts - .map((post) => post.author) - .filter((u): u is HttpUser => u != null), - "username" - ) - ); - - const cache = (await this.getCachedPosts(key)) ?? []; - - const posts = cache.filter((p) => !deletedIds.includes(p.id)); - - for (const changedPost of changed) { - const savedChangedPostIndex = posts.findIndex( - (p) => p.id === changedPost.id - ); - if (savedChangedPostIndex === -1) { - posts.push(this.convertHttpPostToData(changedPost)); - } else { - posts[savedChangedPostIndex] = this.convertHttpPostToData( - changedPost - ); - } - } - - await this.savePosts(key, posts); - await dataStorage.setItem(`timeline.${key}.lastUpdated`, now); - line.next({ type: "synced", posts }); - } - } catch (e) { - if (e instanceof HttpTimelineNotExistError) { - line.next({ type: "notexist", posts: [] }); - } else if (e instanceof HttpForbiddenError) { - line.next({ type: "forbid", posts: [] }); - } else { - const cache = await this.getCachedPosts(key); - if (cache == null) { - line.next({ type: "offline", posts: [] }); - } else { - line.next({ type: "offline", posts: cache }); - } - throwIfNotNetworkError(e); - } - } - }, - }); - - getPosts$(timelineName: string): Observable { - return this._postsHub.getDataWithSyncStatusObservable(timelineName).pipe( - switchMap((state) => { - if (state.posts.length === 0) { - return of({ - ...state, - posts: [], - }); - } - - return combineLatest([ - combineLatest( - state.posts.map((post) => userInfoService.getUser$(post.author)) - ), - combineLatest( - state.posts.map((post) => { - if (post.content.type === "image") { - return this.getPostData$(timelineName, post.id); - } else { - return of(null); - } - }) - ), - ]).pipe( - map(([authors, datas]) => { - return { - ...state, - posts: state.posts.map((post, i) => { - const { content } = post; - - return { - ...post, - author: authors[i], - content: (() => { - if (content.type === "text") return content; - else - return { - type: "image", - data: datas[i], - } as TimelinePostImageContent; - })(), - }; - }), - }; - }) - ); - }) - ); - } - - private getCachedPostData(key: { - timelineName: string; - postId: number; - }): Promise { - return dataStorage.getItem( - `timeline.${key.timelineName}.post.${key.postId}.data` - ); - } - - private savePostData( - key: { - timelineName: string; - postId: number; - }, - data: BlobWithEtag - ): Promise { - return dataStorage - .setItem( - `timeline.${key.timelineName}.post.${key.postId}.data`, - data - ) - .then(); - } - - private syncPostData(key: { - timelineName: string; - postId: number; - }): Promise { - return this._postDataHub.getLineOrCreate(key).sync(); - } - - private _postDataHub = new DataHub< - { timelineName: string; postId: number }, - | { data: Blob; type: "cache" | "synced" | "offline" } - | { data?: undefined; type: "notexist" | "offline" } - >({ - keyToString: (key) => `${key.timelineName}.${key.postId}`, - sync: async (key, line) => { - const cache = await this.getCachedPostData(key); - if (line.value == null) { - if (cache != null) { - line.next({ type: "cache", data: cache.data }); - } - } - - if (cache == null) { - try { - const res = await getHttpTimelineClient().getPostData( - key.timelineName, - key.postId - ); - await this.savePostData(key, res); - line.next({ data: res.data, type: "synced" }); - } catch (e) { - line.next({ type: "offline" }); - throwIfNotNetworkError(e); - } - } else { - try { - const res = await getHttpTimelineClient().getPostData( - key.timelineName, - key.postId, - cache.etag - ); - if (res instanceof NotModified) { - line.next({ data: cache.data, type: "synced" }); - } else { - await this.savePostData(key, res); - line.next({ data: res.data, type: "synced" }); - } - } catch (e) { - line.next({ data: cache.data, type: "offline" }); - throwIfNotNetworkError(e); - } - } - }, - }); - - getPostData$(timelineName: string, postId: number): Observable { - return this._postDataHub.getObservable({ timelineName, postId }).pipe( - map((state): BlobOrStatus => state.data ?? "error"), - startWith("loading") - ); - } - - createPost( - timelineName: string, - request: TimelineCreatePostRequest - ): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient() - .postPost(timelineName, request, user.token) - .then(() => { - void this.syncPosts(timelineName); - }) - ); - } - - deletePost(timelineName: string, postId: number): Observable { - const user = checkLogin(); - return from( - getHttpTimelineClient() - .deletePost(timelineName, postId, user.token) - .then(() => { - void this.syncPosts(timelineName); - }) - ); - } - - isMemberOf(username: string, timeline: TimelineInfo): boolean { - return timeline.members.findIndex((m) => m.username == username) >= 0; - } - - hasReadPermission( - user: UserAuthInfo | null | undefined, - timeline: TimelineInfo - ): boolean { - if (user != null && user.administrator) return true; - - const { visibility } = timeline; - if (visibility === "Public") { - return true; - } else if (visibility === "Register") { - if (user != null) return true; - } else if (visibility === "Private") { - if ( - user != null && - (user.username === timeline.owner.username || - this.isMemberOf(user.username, timeline)) - ) { - return true; - } - } - return false; - } - - hasPostPermission( - user: UserAuthInfo | null | undefined, - timeline: TimelineInfo - ): boolean { - if (user != null && user.administrator) return true; - - return ( - user != null && - (timeline.owner.username === user.username || - this.isMemberOf(user.username, timeline)) - ); - } - - hasManagePermission( - user: UserAuthInfo | null | undefined, - timeline: TimelineInfo - ): boolean { - if (user != null && user.administrator) return true; - - return user != null && user.username == timeline.owner.username; - } - - hasModifyPostPermission( - user: UserAuthInfo | null | undefined, - timeline: TimelineInfo, - post: TimelinePostInfo - ): boolean { - if (user != null && user.administrator) return true; - - return ( - user != null && - (user.username === timeline.owner.username || - user.username === post.author.username) - ); - } -} - -export const timelineService = new TimelineService(); - -const timelineNameReg = XRegExp("^[-_\\p{L}]*$", "u"); - -export function validateTimelineName(name: string): boolean { - return timelineNameReg.test(name); -} - -export function useTimelineInfo( - timelineName: string -): TimelineWithSyncStatus | undefined { - const [state, setState] = React.useState( - undefined - ); - React.useEffect(() => { - const subscription = timelineService - .getTimeline$(timelineName) - .subscribe((data) => { - setState(data); - }); - return () => { - subscription.unsubscribe(); - }; - }, [timelineName]); - return state; -} - -export function usePostList( - timelineName: string | null | undefined -): TimelinePostsWithSyncState | undefined { - const [state, setState] = React.useState< - TimelinePostsWithSyncState | undefined - >(undefined); - React.useEffect(() => { - if (timelineName == null) { - setState(undefined); - return; - } - - const subscription = timelineService - .getPosts$(timelineName) - .subscribe((data) => { - setState(data); - }); - return () => { - subscription.unsubscribe(); - }; - }, [timelineName]); - return state; -} - -export async function getAllCachedTimelineNames(): Promise { - const keys = await dataStorage.keys(); - return keys - .filter( - (key) => - key.startsWith("timeline.") && (key.match(/\./g) ?? []).length === 1 - ) - .map((key) => key.substr("timeline.".length)); -} diff --git a/Timeline/ClientApp/src/app/services/user.ts b/Timeline/ClientApp/src/app/services/user.ts deleted file mode 100644 index f253fc19..00000000 --- a/Timeline/ClientApp/src/app/services/user.ts +++ /dev/null @@ -1,393 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { BehaviorSubject, Observable, from } from "rxjs"; -import { map, filter } from "rxjs/operators"; - -import { UiLogicError } from "@/common"; -import { convertError } from "@/utilities/rxjs"; - -import { HttpNetworkError, BlobWithEtag, NotModified } from "@/http/common"; -import { - getHttpTokenClient, - HttpCreateTokenBadCredentialError, -} from "@/http/token"; -import { - getHttpUserClient, - HttpUserNotExistError, - HttpUser, -} from "@/http/user"; - -import { dataStorage, throwIfNotNetworkError } from "./common"; -import { DataHub } from "./DataHub"; -import { pushAlert } from "./alert"; - -export type User = HttpUser; - -export interface UserAuthInfo { - username: string; - administrator: boolean; -} - -export interface UserWithToken extends User { - token: string; -} - -export interface LoginCredentials { - username: string; - password: string; -} - -export class BadCredentialError { - message = "login.badCredential"; -} - -const USER_STORAGE_KEY = "currentuser"; - -export class UserService { - private userSubject = new BehaviorSubject( - undefined - ); - - get user$(): Observable { - return this.userSubject; - } - - get currentUser(): UserWithToken | null | undefined { - return this.userSubject.value; - } - - async checkLoginState(): Promise { - if (this.currentUser !== undefined) { - console.warn("Already checked user. Can't check twice."); - } - - const savedUser = await dataStorage.getItem( - USER_STORAGE_KEY - ); - - if (savedUser == null) { - this.userSubject.next(null); - return null; - } - - this.userSubject.next(savedUser); - - const savedToken = savedUser.token; - try { - const res = await getHttpTokenClient().verify({ token: savedToken }); - const user: UserWithToken = { ...res.user, token: savedToken }; - await dataStorage.setItem(USER_STORAGE_KEY, user); - this.userSubject.next(user); - pushAlert({ - type: "success", - message: { - type: "i18n", - key: "user.welcomeBack", - }, - }); - return user; - } catch (error) { - if (error instanceof HttpNetworkError) { - pushAlert({ - type: "danger", - message: { type: "i18n", key: "user.verifyTokenFailedNetwork" }, - }); - return savedUser; - } else { - await dataStorage.removeItem(USER_STORAGE_KEY); - this.userSubject.next(null); - pushAlert({ - type: "danger", - message: { type: "i18n", key: "user.verifyTokenFailed" }, - }); - return null; - } - } - } - - async login( - credentials: LoginCredentials, - rememberMe: boolean - ): Promise { - if (this.currentUser) { - throw new UiLogicError("Already login."); - } - try { - const res = await getHttpTokenClient().create({ - ...credentials, - expire: 30, - }); - const user: UserWithToken = { - ...res.user, - token: res.token, - }; - if (rememberMe) { - await dataStorage.setItem(USER_STORAGE_KEY, user); - } - this.userSubject.next(user); - } catch (e) { - if (e instanceof HttpCreateTokenBadCredentialError) { - throw new BadCredentialError(); - } else { - throw e; - } - } - } - - async logout(): Promise { - if (this.currentUser === undefined) { - throw new UiLogicError("Please check user first."); - } - if (this.currentUser === null) { - throw new UiLogicError("No login."); - } - await dataStorage.removeItem(USER_STORAGE_KEY); - this.userSubject.next(null); - } - - changePassword( - oldPassword: string, - newPassword: string - ): Observable { - if (this.currentUser == undefined) { - throw new UiLogicError("Not login or checked now, can't log out."); - } - const $ = from( - getHttpUserClient().changePassword( - { - oldPassword, - newPassword, - }, - this.currentUser.token - ) - ); - $.subscribe(() => { - void this.logout(); - }); - return $; - } -} - -export const userService = new UserService(); - -export function useRawUser(): UserWithToken | null | undefined { - const [user, setUser] = useState( - userService.currentUser - ); - useEffect(() => { - const subscription = userService.user$.subscribe((u) => setUser(u)); - return () => { - subscription.unsubscribe(); - }; - }); - return user; -} - -export function useUser(): UserWithToken | null { - const [user, setUser] = useState(() => { - const initUser = userService.currentUser; - if (initUser === undefined) { - throw new UiLogicError( - "This is a logic error in user module. Current user can't be undefined in useUser." - ); - } - return initUser; - }); - useEffect(() => { - const sub = userService.user$.subscribe((u) => { - if (u === undefined) { - throw new UiLogicError( - "This is a logic error in user module. User emitted can't be undefined later." - ); - } - setUser(u); - }); - return () => { - sub.unsubscribe(); - }; - }); - return user; -} - -export function useUserLoggedIn(): UserWithToken { - const user = useUser(); - if (user == null) { - throw new UiLogicError("You assert user has logged in but actually not."); - } - return user; -} - -export function checkLogin(): UserWithToken { - const user = userService.currentUser; - if (user == null) { - throw new UiLogicError("You must login to perform the operation."); - } - return user; -} - -export class UserNotExistError extends Error {} - -export class UserInfoService { - saveUser(user: HttpUser): void { - const key = user.username; - void this._userHub.optionalInitLineWithSyncAction(key, async (line) => { - await this.doSaveUser(user); - line.next({ user, type: "synced" }); - }); - } - - saveUsers(users: HttpUser[]): void { - return users.forEach((user) => this.saveUser(user)); - } - - private getCachedUser(username: string): Promise { - return dataStorage.getItem(`user.${username}`); - } - - private doSaveUser(user: HttpUser): Promise { - return dataStorage.setItem(`user.${user.username}`, user).then(); - } - - syncUser(username: string): Promise { - return this._userHub.getLineOrCreate(username).sync(); - } - - private _userHub = new DataHub< - string, - | { user: User; type: "cache" | "synced" | "offline" } - | { user?: undefined; type: "notexist" | "offline" } - >({ - sync: async (key, line) => { - if (line.value == undefined) { - const cache = await this.getCachedUser(key); - if (cache != null) { - line.next({ user: cache, type: "cache" }); - } - } - - try { - const res = await getHttpUserClient().get(key); - await this.doSaveUser(res); - line.next({ user: res, type: "synced" }); - } catch (e) { - if (e instanceof HttpUserNotExistError) { - line.next({ type: "notexist" }); - } else { - const cache = await this.getCachedUser(key); - line.next({ user: cache ?? undefined, type: "offline" }); - throwIfNotNetworkError(e); - } - } - }, - }); - - getUser$(username: string): Observable { - return this._userHub.getObservable(username).pipe( - map((state) => state?.user), - filter((user): user is User => user != null) - ); - } - - private getCachedAvatar(username: string): Promise { - return dataStorage.getItem(`user.${username}.avatar`); - } - - private saveAvatar(username: string, data: BlobWithEtag): Promise { - return dataStorage - .setItem(`user.${username}.avatar`, data) - .then(); - } - - syncAvatar(username: string): Promise { - return this._avatarHub.getLineOrCreate(username).sync(); - } - - private _avatarHub = new DataHub< - string, - | { data: Blob; type: "cache" | "synced" | "offline" } - | { data?: undefined; type: "notexist" | "offline" } - >({ - sync: async (key, line) => { - const cache = await this.getCachedAvatar(key); - if (line.value == null) { - if (cache != null) { - line.next({ data: cache.data, type: "cache" }); - } - } - - if (cache == null) { - try { - const avatar = await getHttpUserClient().getAvatar(key); - await this.saveAvatar(key, avatar); - line.next({ data: avatar.data, type: "synced" }); - } catch (e) { - line.next({ type: "offline" }); - throwIfNotNetworkError(e); - } - } else { - try { - const res = await getHttpUserClient().getAvatar(key, cache.etag); - if (res instanceof NotModified) { - line.next({ data: cache.data, type: "synced" }); - } else { - const avatar = res; - await this.saveAvatar(key, avatar); - line.next({ data: avatar.data, type: "synced" }); - } - } catch (e) { - line.next({ data: cache.data, type: "offline" }); - throwIfNotNetworkError(e); - } - } - }, - }); - - getAvatar$(username: string): Observable { - return this._avatarHub.getObservable(username).pipe( - map((state) => state.data), - filter((blob): blob is Blob => blob != null) - ); - } - - getUserInfo(username: string): Observable { - return from(getHttpUserClient().get(username)).pipe( - convertError(HttpUserNotExistError, UserNotExistError) - ); - } - - async setAvatar(username: string, blob: Blob): Promise { - const user = checkLogin(); - await getHttpUserClient().putAvatar(username, blob, user.token); - this._avatarHub.getLine(username)?.next({ data: blob, type: "synced" }); - } - - async setNickname(username: string, nickname: string): Promise { - const user = checkLogin(); - return getHttpUserClient() - .patch(username, { nickname }, user.token) - .then((user) => { - this.saveUser(user); - }); - } -} - -export const userInfoService = new UserInfoService(); - -export function useAvatar(username?: string): Blob | undefined { - const [state, setState] = React.useState(undefined); - React.useEffect(() => { - if (username == null) { - setState(undefined); - return; - } - - const subscription = userInfoService - .getAvatar$(username) - .subscribe((blob) => { - setState(blob); - }); - return () => { - subscription.unsubscribe(); - }; - }, [username]); - return state; -} diff --git a/Timeline/ClientApp/src/app/tsconfig.json b/Timeline/ClientApp/src/app/tsconfig.json deleted file mode 100644 index 14e6327f..00000000 --- a/Timeline/ClientApp/src/app/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "lib": [ - "dom", - "dom.iterable", - "esnext" - ] - }, - "include": [ - "." - ] -} diff --git a/Timeline/ClientApp/src/app/typings.d.ts b/Timeline/ClientApp/src/app/typings.d.ts deleted file mode 100644 index 34381682..00000000 --- a/Timeline/ClientApp/src/app/typings.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module "*.png" { - const content: string; - export default content; -} - -declare module "*.jpeg" { - const content: string; - export default content; -} - -declare module "*.jpg" { - const content: string; - export default content; -} - -declare module "*.gif" { - const content: string; - export default content; -} - -declare module "*.svg" { - const content: string; - export default content; -} diff --git a/Timeline/ClientApp/src/app/utilities/rxjs.ts b/Timeline/ClientApp/src/app/utilities/rxjs.ts deleted file mode 100644 index 0730b899..00000000 --- a/Timeline/ClientApp/src/app/utilities/rxjs.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OperatorFunction } from "rxjs"; -import { catchError } from "rxjs/operators"; - -export function convertError( - oldErrorType: { new (...args: never[]): unknown }, - newErrorType: { new (): NewError } -): OperatorFunction { - return catchError((error) => { - if (error instanceof oldErrorType) { - throw new newErrorType(); - } - throw error; - }); -} diff --git a/Timeline/ClientApp/src/app/utilities/url.ts b/Timeline/ClientApp/src/app/utilities/url.ts deleted file mode 100644 index 17ead5b2..00000000 --- a/Timeline/ClientApp/src/app/utilities/url.ts +++ /dev/null @@ -1,52 +0,0 @@ -//copied from https://stackoverflow.com/questions/5999118/how-can-i-add-or-update-a-query-string-parameter -export function updateQueryString( - key: string, - value: undefined | string | null, - url: string -): string { - const re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"); - let hash; - - if (re.test(url)) { - if (typeof value !== "undefined" && value !== null) { - return url.replace(re, "$1" + key + "=" + value + "$2$3"); - } else { - hash = url.split("#"); - url = hash[0].replace(re, "$1$3").replace(/(&|\?)$/, ""); - if (typeof hash[1] !== "undefined" && hash[1] !== null) { - url += "#" + hash[1]; - } - return url; - } - } else { - if (typeof value !== "undefined" && value !== null) { - const separator = url.includes("?") ? "&" : "?"; - hash = url.split("#"); - url = hash[0] + separator + key + "=" + value; - if (typeof hash[1] !== "undefined" && hash[1] !== null) { - url += "#" + hash[1]; - } - return url; - } else { - return url; - } - } -} - -export function applyQueryParameters(url: string, query: T): string { - if (query == null) return url; - - for (const [key, value] of Object.entries(query)) { - if (typeof value === "string") url = updateQueryString(key, value, url); - else if (typeof value === "number") - url = updateQueryString(key, String(value), url); - else if (typeof value === "boolean") - url = updateQueryString(key, value ? "true" : "false", url); - else if (value instanceof Date) - url = updateQueryString(key, value.toISOString(), url); - else { - console.error("Unknown query parameter type. Param: ", value); - } - } - return url; -} diff --git a/Timeline/ClientApp/src/app/views/about/about.sass b/Timeline/ClientApp/src/app/views/about/about.sass deleted file mode 100644 index 3b5840cd..00000000 --- a/Timeline/ClientApp/src/app/views/about/about.sass +++ /dev/null @@ -1,4 +0,0 @@ -.about-link-icon - @extend .mx-2 - width: 1.2em - height: 1.2em diff --git a/Timeline/ClientApp/src/app/views/about/author-avatar.png b/Timeline/ClientApp/src/app/views/about/author-avatar.png deleted file mode 100644 index d890d8d0..00000000 Binary files a/Timeline/ClientApp/src/app/views/about/author-avatar.png and /dev/null differ diff --git a/Timeline/ClientApp/src/app/views/about/github.png b/Timeline/ClientApp/src/app/views/about/github.png deleted file mode 100644 index ea6ff545..00000000 Binary files a/Timeline/ClientApp/src/app/views/about/github.png and /dev/null differ diff --git a/Timeline/ClientApp/src/app/views/about/index.tsx b/Timeline/ClientApp/src/app/views/about/index.tsx deleted file mode 100644 index e7771cec..00000000 --- a/Timeline/ClientApp/src/app/views/about/index.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React from "react"; -import { useTranslation, Trans } from "react-i18next"; - -import authorAvatarUrl from "./author-avatar.png"; -import githubLogoUrl from "./github.png"; - -const frontendCredits: { - name: string; - url: string; -}[] = [ - { - name: "reactjs", - url: "https://reactjs.org", - }, - { - name: "typescript", - url: "https://www.typescriptlang.org", - }, - { - name: "bootstrap", - url: "https://getbootstrap.com", - }, - { - name: "react-bootstrap", - url: "https://react-bootstrap.github.io", - }, - { - name: "babeljs", - url: "https://babeljs.io", - }, - { - name: "webpack", - url: "https://webpack.js.org", - }, - { - name: "sass", - url: "https://sass-lang.com", - }, - { - name: "eslint", - url: "https://eslint.org", - }, - { - name: "prettier", - url: "https://prettier.io", - }, - { - name: "pepjs", - url: "https://github.com/jquery/PEP", - }, - { - name: "react-inlinesvg", - url: "https://github.com/gilbarbara/react-inlinesvg", - }, -]; - -const backendCredits: { - name: string; - url: string; -}[] = [ - { - name: "ASP.NET Core", - url: "https://dotnet.microsoft.com/learn/aspnet/what-is-aspnet-core", - }, - { name: "sqlite", url: "https://sqlite.org" }, - { - name: "ImageSharp", - url: "https://github.com/SixLabors/ImageSharp", - }, -]; - -const AboutPage: React.FC = () => { - const { t } = useTranslation(); - - return ( -
-
-

{t("about.author.title")}

-
-
- -
-

- {t("about.author.fullname")} - 杨宇千 -

-

- {t("about.author.nickname")} - crupest -

-

- {t("about.author.introduction")} - {t("about.author.introductionContent")} -

-
-
-

- {t("about.author.links")} - - - -

-
-
-
-

{t("about.site.title")}

-

- - 01234 - 56 - -

-

- - {t("about.site.repo")} - -

-
-
-

{t("about.credits.title")}

-

{t("about.credits.content")}

-

{t("about.credits.frontend")}

-
    - {frontendCredits.map((item, index) => { - return ( -
  • - - {item.name} - -
  • - ); - })} -
  • ...
  • -
-

{t("about.credits.backend")}

-
    - {backendCredits.map((item, index) => { - return ( -
  • - - {item.name} - -
  • - ); - })} -
  • ...
  • -
-
-
- ); -}; - -export default AboutPage; diff --git a/Timeline/ClientApp/src/app/views/admin/Admin.tsx b/Timeline/ClientApp/src/app/views/admin/Admin.tsx deleted file mode 100644 index 9c0250e7..00000000 --- a/Timeline/ClientApp/src/app/views/admin/Admin.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { Fragment } from "react"; -import { - Redirect, - Route, - Switch, - useRouteMatch, - useHistory, -} from "react-router"; -import { Nav } from "react-bootstrap"; - -import { UserWithToken } from "@/services/user"; - -import UserAdmin from "./UserAdmin"; - -interface AdminProps { - user: UserWithToken; -} - -const Admin: React.FC = (props) => { - const match = useRouteMatch(); - const history = useHistory(); - type TabNames = "users" | "more"; - - const tabName = history.location.pathname.replace(match.path + "/", ""); - - function toggle(newTab: TabNames): void { - history.push(`${match.url}/${newTab}`); - } - - const createRoute = ( - name: string, - body: React.ReactNode - ): React.ReactNode => { - return ( - -
- - {body} - - ); - }; - - return ( - - - - {createRoute("users", )} - {createRoute("more",
More Page Works
)} -
-
- ); -}; - -export default Admin; diff --git a/Timeline/ClientApp/src/app/views/admin/UserAdmin.tsx b/Timeline/ClientApp/src/app/views/admin/UserAdmin.tsx deleted file mode 100644 index 18b77ca8..00000000 --- a/Timeline/ClientApp/src/app/views/admin/UserAdmin.tsx +++ /dev/null @@ -1,460 +0,0 @@ -import React, { useState, useEffect } from "react"; -import axios from "axios"; -import { - ListGroup, - Row, - Col, - Dropdown, - Spinner, - Button, -} from "react-bootstrap"; - -import OperationDialog from "../common/OperationDialog"; -import { User, UserWithToken } from "@/services/user"; - -const apiBaseUrl = "/api"; - -async function fetchUserList(_token: string): Promise { - const res = await axios.get(`${apiBaseUrl}/users`); - return res.data; -} - -interface CreateUserInfo { - username: string; - password: string; - administrator: boolean; -} - -async function createUser(user: CreateUserInfo, token: string): Promise { - const res = await axios.post( - `${apiBaseUrl}/userop/createuser?token=${token}`, - user - ); - return res.data; -} - -function deleteUser(username: string, token: string): Promise { - return axios.delete(`${apiBaseUrl}/users/${username}?token=${token}`); -} - -function changeUsername( - oldUsername: string, - newUsername: string, - token: string -): Promise { - return axios.patch(`${apiBaseUrl}/users/${oldUsername}?token=${token}`, { - username: newUsername, - }); -} - -function changePassword( - username: string, - newPassword: string, - token: string -): Promise { - return axios.patch(`${apiBaseUrl}/users/${username}?token=${token}`, { - password: newPassword, - }); -} - -function changePermission( - username: string, - newPermission: boolean, - token: string -): Promise { - return axios.patch(`${apiBaseUrl}/users/${username}?token=${token}`, { - administrator: newPermission, - }); -} - -const kChangeUsername = "changeusername"; -const kChangePassword = "changepassword"; -const kChangePermission = "changepermission"; -const kDelete = "delete"; - -type TChangeUsername = typeof kChangeUsername; -type TChangePassword = typeof kChangePassword; -type TChangePermission = typeof kChangePermission; -type TDelete = typeof kDelete; - -type ContextMenuItem = - | TChangeUsername - | TChangePassword - | TChangePermission - | TDelete; - -interface UserCardProps { - onContextMenu: (item: ContextMenuItem) => void; - user: User; -} - -const UserItem: React.FC = (props) => { - const user = props.user; - - const createClickCallback = (item: ContextMenuItem): (() => void) => { - return () => { - props.onContextMenu(item); - }; - }; - - return ( - - - -

{user.username}

- - {user.administrator ? "administrator" : "user"} - - - - - - Manage - - - - Change Username - - - Change Password - - - Change Permission - - - Delete - - - - -
-
- ); -}; - -interface DialogProps { - open: boolean; - close: () => void; -} - -interface CreateUserDialogProps extends DialogProps { - process: (user: CreateUserInfo) => Promise; -} - -const CreateUserDialog: React.FC = (props) => { - return ( - - props.process({ - username: username as string, - password: password as string, - administrator: administrator as boolean, - }) - } - close={props.close} - open={props.open} - /> - ); -}; - -const UsernameLabel: React.FC = (props) => { - return {props.children}; -}; - -interface UserDeleteDialogProps extends DialogProps { - username: string; - process: () => Promise; -} - -const UserDeleteDialog: React.FC = (props) => { - return ( - ( - <> - {"You are deleting user "} - {props.username} - {" !"} - - )} - onProcess={props.process} - /> - ); -}; - -interface UserModifyDialogProps extends DialogProps { - username: string; - process: (value: T) => Promise; -} - -const UserChangeUsernameDialog: React.FC> = ( - props -) => { - return ( - ( - <> - {"You are change the username of user "} - {props.username} - {" !"} - - )} - inputScheme={[{ type: "text", label: "New Username" }]} - onProcess={([newUsername]) => { - return props.process(newUsername as string); - }} - /> - ); -}; - -const UserChangePasswordDialog: React.FC> = ( - props -) => { - return ( - ( - <> - {"You are change the password of user "} - {props.username} - {" !"} - - )} - inputScheme={[{ type: "text", label: "New Password" }]} - onProcess={([newPassword]) => { - return props.process(newPassword as string); - }} - /> - ); -}; - -interface UserChangePermissionDialogProps extends DialogProps { - username: string; - newPermission: boolean; - process: () => Promise; -} - -const UserChangePermissionDialog: React.FC = ( - props -) => { - return ( - ( - <> - {"You are change user "} - {props.username} - {" to "} - - {props.newPermission ? "administrator" : "normal user"} - - {" !"} - - )} - onProcess={props.process} - /> - ); -}; - -interface UserAdminProps { - user: UserWithToken; -} - -const UserAdmin: React.FC = (props) => { - type DialogInfo = - | null - | { - type: "create"; - } - | { type: TDelete; username: string } - | { - type: TChangeUsername; - username: string; - } - | { - type: TChangePassword; - username: string; - } - | { - type: TChangePermission; - username: string; - newPermission: boolean; - }; - - const [users, setUsers] = useState(null); - const [dialog, setDialog] = useState(null); - - const token = props.user.token; - - useEffect(() => { - let subscribe = true; - void fetchUserList(props.user.token).then((us) => { - if (subscribe) { - setUsers(us); - } - }); - return () => { - subscribe = false; - }; - }, [props.user]); - - let dialogNode: React.ReactNode; - if (dialog) - switch (dialog.type) { - case "create": - dialogNode = ( - setDialog(null)} - process={async (user) => { - const u = await createUser(user, token); - setUsers((oldUsers) => [...(oldUsers ?? []), u]); - }} - /> - ); - break; - case "delete": - dialogNode = ( - setDialog(null)} - username={dialog.username} - process={async () => { - await deleteUser(dialog.username, token); - setUsers((oldUsers) => - (oldUsers ?? []).filter((u) => u.username !== dialog.username) - ); - }} - /> - ); - break; - case kChangeUsername: - dialogNode = ( - setDialog(null)} - username={dialog.username} - process={async (newUsername) => { - await changeUsername(dialog.username, newUsername, token); - setUsers((oldUsers) => { - const users = (oldUsers ?? []).slice(); - const findedUser = users.find( - (u) => u.username === dialog.username - ); - if (findedUser) findedUser.username = newUsername; - return users; - }); - }} - /> - ); - break; - case kChangePassword: - dialogNode = ( - setDialog(null)} - username={dialog.username} - process={async (newPassword) => { - await changePassword(dialog.username, newPassword, token); - }} - /> - ); - break; - case kChangePermission: { - const newPermission = dialog.newPermission; - dialogNode = ( - setDialog(null)} - username={dialog.username} - newPermission={newPermission} - process={async () => { - await changePermission(dialog.username, newPermission, token); - setUsers((oldUsers) => { - const users = (oldUsers ?? []).slice(); - const findedUser = users.find( - (u) => u.username === dialog.username - ); - if (findedUser) findedUser.administrator = newPermission; - return users; - }); - }} - /> - ); - break; - } - } - - if (users) { - const userComponents = users.map((user) => { - return ( - { - setDialog( - item === kChangePermission - ? { - type: kChangePermission, - username: user.username, - newPermission: !user.administrator, - } - : { - type: item, - username: user.username, - } - ); - }} - /> - ); - }); - - return ( - <> - - {userComponents} - {dialogNode} - - ); - } else { - return ; - } -}; - -export default UserAdmin; diff --git a/Timeline/ClientApp/src/app/views/common/AppBar.tsx b/Timeline/ClientApp/src/app/views/common/AppBar.tsx deleted file mode 100644 index ee4ead8f..00000000 --- a/Timeline/ClientApp/src/app/views/common/AppBar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { LinkContainer } from "react-router-bootstrap"; -import { Navbar, Nav } from "react-bootstrap"; - -import { useUser, useAvatar } from "@/services/user"; - -import TimelineLogo from "./TimelineLogo"; -import BlobImage from "./BlobImage"; - -const AppBar: React.FC = (_) => { - const user = useUser(); - const avatar = useAvatar(user?.username); - - const { t } = useTranslation(); - - const isAdministrator = user && user.administrator; - - return ( - - - - - Timeline - - - - - - - - - - ); -}; - -export default AppBar; diff --git a/Timeline/ClientApp/src/app/views/common/BlobImage.tsx b/Timeline/ClientApp/src/app/views/common/BlobImage.tsx deleted file mode 100644 index 0dd25c52..00000000 --- a/Timeline/ClientApp/src/app/views/common/BlobImage.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; - -const BlobImage: React.FC< - Omit, "src"> & { - blob?: Blob | unknown; - } -> = (props) => { - const { blob, ...otherProps } = props; - - const [url, setUrl] = React.useState(undefined); - - React.useEffect(() => { - if (blob instanceof Blob) { - const url = URL.createObjectURL(blob); - setUrl(url); - return () => { - URL.revokeObjectURL(url); - }; - } else { - setUrl(undefined); - } - }, [blob]); - - return ; -}; - -export default BlobImage; diff --git a/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx b/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx deleted file mode 100644 index b9db8b99..00000000 --- a/Timeline/ClientApp/src/app/views/common/ImageCropper.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import React from "react"; -import clsx from "clsx"; - -import { UiLogicError } from "@/common"; - -export interface Clip { - left: number; - top: number; - width: number; -} - -interface NormailizedClip extends Clip { - height: number; -} - -interface ImageInfo { - width: number; - height: number; - landscape: boolean; - ratio: number; - maxClipWidth: number; - maxClipHeight: number; -} - -interface ImageCropperSavedState { - clip: NormailizedClip; - x: number; - y: number; - pointerId: number; -} - -export interface ImageCropperProps { - clip: Clip | null; - imageUrl: string; - onChange: (clip: Clip) => void; - imageElementCallback?: (element: HTMLImageElement | null) => void; - className?: string; -} - -const ImageCropper = (props: ImageCropperProps): React.ReactElement => { - const { clip, imageUrl, onChange, imageElementCallback, className } = props; - - const [oldState, setOldState] = React.useState( - null - ); - const [imageInfo, setImageInfo] = React.useState(null); - - const normalizeClip = (c: Clip | null | undefined): NormailizedClip => { - if (c == null) { - return { left: 0, top: 0, width: 0, height: 0 }; - } - - return { - left: c.left || 0, - top: c.top || 0, - width: c.width || 0, - height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0, - }; - }; - - const c = normalizeClip(clip); - - const imgElementRef = React.useRef(null); - - const onImageRef = React.useCallback( - (e: HTMLImageElement | null) => { - imgElementRef.current = e; - if (imageElementCallback != null && e == null) { - imageElementCallback(null); - } - }, - [imageElementCallback] - ); - - const onImageLoad = React.useCallback( - (e: React.SyntheticEvent) => { - const img = e.currentTarget; - const landscape = img.naturalWidth >= img.naturalHeight; - - const info = { - width: img.naturalWidth, - height: img.naturalHeight, - landscape, - ratio: img.naturalHeight / img.naturalWidth, - maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1, - maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight, - }; - setImageInfo(info); - onChange({ left: 0, top: 0, width: info.maxClipWidth }); - if (imageElementCallback != null) { - imageElementCallback(img); - } - }, - [onChange, imageElementCallback] - ); - - const onPointerDown = React.useCallback( - (e: React.PointerEvent) => { - if (oldState != null) return; - e.currentTarget.setPointerCapture(e.pointerId); - setOldState({ - x: e.clientX, - y: e.clientY, - clip: c, - pointerId: e.pointerId, - }); - }, - [oldState, c] - ); - - const onPointerUp = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null || oldState.pointerId !== e.pointerId) return; - e.currentTarget.releasePointerCapture(e.pointerId); - setOldState(null); - }, - [oldState] - ); - - const onPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.y / imgElement.height, - }; - - const newRatio = { - x: oldClip.left + moveRatio.x, - y: oldClip.top + moveRatio.y, - }; - if (newRatio.x < 0) { - newRatio.x = 0; - } else if (newRatio.x > 1 - oldClip.width) { - newRatio.x = 1 - oldClip.width; - } - if (newRatio.y < 0) { - newRatio.y = 0; - } else if (newRatio.y > 1 - oldClip.height) { - newRatio.y = 1 - oldClip.height; - } - - onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width }); - }, - [oldState, onChange] - ); - - const onHandlerPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const ratio = imageInfo == null ? 1 : imageInfo.ratio; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.x / imgElement.width / ratio, - }; - - const newRatio = { - x: oldClip.width + moveRatio.x, - y: oldClip.height + moveRatio.y, - }; - - const maxRatio = { - x: Math.min(1 - oldClip.left, newRatio.x), - y: Math.min(1 - oldClip.top, newRatio.y), - }; - - const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio); - - let newWidth; - if (newRatio.x < 0) { - newWidth = 0; - } else if (newRatio.x > maxWidthRatio) { - newWidth = maxWidthRatio; - } else { - newWidth = newRatio.x; - } - - onChange({ left: oldClip.left, top: oldClip.top, width: newWidth }); - }, - [imageInfo, oldState, onChange] - ); - - const toPercentage = (n: number): string => `${n}%`; - - // fuck!!! I just can't find a better way to implement this in pure css - const containerStyle: React.CSSProperties = (() => { - if (imageInfo == null) { - return { width: "100%", paddingTop: "100%", height: 0 }; - } else { - if (imageInfo.ratio > 1) { - return { - width: toPercentage(100 / imageInfo.ratio), - paddingTop: "100%", - height: 0, - }; - } else { - return { - width: "100%", - paddingTop: toPercentage(100 * imageInfo.ratio), - height: 0, - }; - } - } - })(); - - return ( -
- to crop -
-
-
-
-
- ); -}; - -export default ImageCropper; - -export function applyClipToImage( - image: HTMLImageElement, - clip: Clip, - mimeType: string -): Promise { - return new Promise((resolve, reject) => { - const naturalSize = { - width: image.naturalWidth, - height: image.naturalHeight, - }; - const clipArea = { - x: naturalSize.width * clip.left, - y: naturalSize.height * clip.top, - length: naturalSize.width * clip.width, - }; - - const canvas = document.createElement("canvas"); - canvas.width = clipArea.length; - canvas.height = clipArea.length; - const context = canvas.getContext("2d"); - - if (context == null) throw new Error("Failed to create context."); - - context.drawImage( - image, - clipArea.x, - clipArea.y, - clipArea.length, - clipArea.length, - 0, - 0, - clipArea.length, - clipArea.length - ); - - canvas.toBlob((blob) => { - if (blob == null) { - reject(new Error("canvas.toBlob returns null")); - } else { - resolve(blob); - } - }, mimeType); - }); -} diff --git a/Timeline/ClientApp/src/app/views/common/LoadingButton.tsx b/Timeline/ClientApp/src/app/views/common/LoadingButton.tsx deleted file mode 100644 index 154334a7..00000000 --- a/Timeline/ClientApp/src/app/views/common/LoadingButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { Button, ButtonProps, Spinner } from "react-bootstrap"; - -const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({ - loading, - variant, - disabled, - ...otherProps -}) => { - return ( - - ); -}; - -export default LoadingButton; diff --git a/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx b/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx deleted file mode 100644 index 590fafa0..00000000 --- a/Timeline/ClientApp/src/app/views/common/LoadingPage.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import { Spinner } from "react-bootstrap"; - -const LoadingPage: React.FC = () => { - return ( -
- -
- ); -}; - -export default LoadingPage; diff --git a/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx b/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx deleted file mode 100644 index 841392a6..00000000 --- a/Timeline/ClientApp/src/app/views/common/OperationDialog.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Form, Button, Modal } from "react-bootstrap"; - -import { UiLogicError } from "@/common"; - -import LoadingButton from "./LoadingButton"; - -interface DefaultErrorPromptProps { - error?: string; -} - -const DefaultErrorPrompt: React.FC = (props) => { - const { t } = useTranslation(); - - let result =

{t("operationDialog.error")}

; - - if (props.error != null) { - result = ( - <> - {result} -

{props.error}

- - ); - } - - return result; -}; - -export type OperationInputOptionalError = undefined | null | string; - -export interface OperationInputErrorInfo { - [index: number]: OperationInputOptionalError; -} - -export type OperationInputValidator = ( - value: TValue, - values: (string | boolean)[] -) => OperationInputOptionalError | OperationInputErrorInfo; - -export interface OperationTextInputInfo { - type: "text"; - password?: boolean; - label?: string; - initValue?: string; - textFieldProps?: Omit< - React.InputHTMLAttributes, - "type" | "value" | "onChange" | "aria-relevant" - >; - helperText?: string; - validator?: OperationInputValidator; -} - -export interface OperationBoolInputInfo { - type: "bool"; - label: string; - initValue?: boolean; -} - -export interface OperationSelectInputInfoOption { - value: string; - label: string; - icon?: React.ReactElement; -} - -export interface OperationSelectInputInfo { - type: "select"; - label: string; - options: OperationSelectInputInfoOption[]; - initValue?: string; -} - -export type OperationInputInfo = - | OperationTextInputInfo - | OperationBoolInputInfo - | OperationSelectInputInfo; - -interface OperationResult { - type: "success" | "failure"; - data: unknown; -} - -interface OperationDialogProps { - open: boolean; - close: () => void; - title: React.ReactNode; - titleColor?: "default" | "dangerous" | "create" | string; - onProcess: (inputs: (string | boolean)[]) => Promise; - inputScheme?: OperationInputInfo[]; - inputPrompt?: string | (() => React.ReactNode); - processPrompt?: () => React.ReactNode; - successPrompt?: (data: unknown) => React.ReactNode; - failurePrompt?: (error: unknown) => React.ReactNode; - onSuccessAndClose?: () => void; -} - -const OperationDialog: React.FC = (props) => { - const inputScheme = props.inputScheme ?? []; - - const { t } = useTranslation(); - - type Step = "input" | "process" | OperationResult; - const [step, setStep] = useState("input"); - const [values, setValues] = useState<(boolean | string)[]>( - inputScheme.map((i) => { - if (i.type === "bool") { - return i.initValue ?? false; - } else if (i.type === "text" || i.type === "select") { - return i.initValue ?? ""; - } else { - throw new UiLogicError("Unknown input scheme."); - } - }) - ); - const [inputError, setInputError] = useState({}); - - const close = (): void => { - if (step !== "process") { - props.close(); - if ( - typeof step === "object" && - step.type === "success" && - props.onSuccessAndClose - ) { - props.onSuccessAndClose(); - } - } else { - console.log("Attempt to close modal when processing."); - } - }; - - const onConfirm = (): void => { - setStep("process"); - props.onProcess(values).then( - (d: unknown) => { - setStep({ - type: "success", - data: d, - }); - }, - (e: unknown) => { - setStep({ - type: "failure", - data: e, - }); - } - ); - }; - - let body: React.ReactNode; - if (step === "input" || step === "process") { - const process = step === "process"; - - let inputPrompt = - typeof props.inputPrompt === "function" - ? props.inputPrompt() - : props.inputPrompt; - inputPrompt =
{inputPrompt}
; - - const updateValue = ( - index: number, - newValue: string | boolean - ): (string | boolean)[] => { - const oldValues = values; - const newValues = oldValues.slice(); - newValues[index] = newValue; - setValues(newValues); - return newValues; - }; - - const testErrorInfo = (errorInfo: OperationInputErrorInfo): boolean => { - for (let i = 0; i < inputScheme.length; i++) { - if (inputScheme[i].type === "text" && errorInfo[i] != null) { - return true; - } - } - return false; - }; - - const calculateError = ( - oldError: OperationInputErrorInfo, - index: number, - newError: OperationInputOptionalError | OperationInputErrorInfo - ): OperationInputErrorInfo => { - if (newError === undefined) { - return oldError; - } else if (newError === null || typeof newError === "string") { - return { ...oldError, [index]: newError }; - } else { - const newInputError: OperationInputErrorInfo = { ...oldError }; - for (const [index, error] of Object.entries(newError)) { - if (error !== undefined) { - newInputError[+index] = error as OperationInputOptionalError; - } - } - return newInputError; - } - }; - - const validateAll = (): boolean => { - let newInputError = inputError; - for (let i = 0; i < inputScheme.length; i++) { - const item = inputScheme[i]; - if (item.type === "text") { - newInputError = calculateError( - newInputError, - i, - item.validator?.(values[i] as string, values) - ); - } - } - const result = !testErrorInfo(newInputError); - setInputError(newInputError); - return result; - }; - - body = ( - <> - - {inputPrompt} - {inputScheme.map((item, index) => { - const value = values[index]; - const error: string | undefined = ((e) => - typeof e === "string" ? t(e) : undefined)(inputError?.[index]); - - if (item.type === "text") { - return ( - - {item.label && {t(item.label)}} - { - const v = e.target.value; - const newValues = updateValue(index, v); - setInputError( - calculateError( - inputError, - index, - item.validator?.(v, newValues) - ) - ); - }} - isInvalid={error != null} - disabled={process} - /> - {error != null && ( - - {error} - - )} - {item.helperText && ( - {t(item.helperText)} - )} - - ); - } else if (item.type === "bool") { - return ( - - - type="checkbox" - checked={value as boolean} - onChange={(event) => { - updateValue(index, event.currentTarget.checked); - }} - label={t(item.label)} - disabled={process} - /> - - ); - } else if (item.type === "select") { - return ( - - {t(item.label)} - { - updateValue(index, event.target.value); - }} - disabled={process} - > - {item.options.map((option, i) => { - return ( - - ); - })} - - - ); - } - })} - - - - { - if (validateAll()) { - onConfirm(); - } - }} - > - {t("operationDialog.confirm")} - - - - ); - } else { - let content: React.ReactNode; - const result = step; - if (result.type === "success") { - content = - props.successPrompt?.(result.data) ?? t("operationDialog.success"); - if (typeof content === "string") - content =

{content}

; - } else { - content = props.failurePrompt?.(result.data) ?? ; - if (typeof content === "string") - content = ; - } - body = ( - <> - {content} - - - - - ); - } - - const title = typeof props.title === "string" ? t(props.title) : props.title; - - return ( - - - {title} - - {body} - - ); -}; - -export default OperationDialog; diff --git a/Timeline/ClientApp/src/app/views/common/SearchInput.tsx b/Timeline/ClientApp/src/app/views/common/SearchInput.tsx deleted file mode 100644 index 9833d515..00000000 --- a/Timeline/ClientApp/src/app/views/common/SearchInput.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback } from "react"; -import clsx from "clsx"; -import { useTranslation } from "react-i18next"; -import { Spinner, Form, Button } from "react-bootstrap"; - -export interface SearchInputProps { - value: string; - onChange: (value: string) => void; - onButtonClick: () => void; - className?: string; - loading?: boolean; - buttonText?: string; - placeholder?: string; - additionalButton?: React.ReactNode; -} - -const SearchInput: React.FC = (props) => { - const { onChange, onButtonClick } = props; - - const { t } = useTranslation(); - - const onInputChange = useCallback( - (event: React.ChangeEvent): void => { - onChange(event.currentTarget.value); - }, - [onChange] - ); - - const onInputKeyPress = useCallback( - (event: React.KeyboardEvent): void => { - if (event.key === "Enter") { - onButtonClick(); - } - }, - [onButtonClick] - ); - - return ( -
- -
- {props.additionalButton} -
-
- {props.loading ? ( - - ) : ( - - )} -
- - ); -}; - -export default SearchInput; diff --git a/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx b/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx deleted file mode 100644 index 27d188fc..00000000 --- a/Timeline/ClientApp/src/app/views/common/TimelineLogo.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { SVGAttributes } from "react"; - -export interface TimelineLogoProps extends SVGAttributes { - color?: string; -} - -const TimelineLogo: React.FC = (props) => { - const { color, ...forwardProps } = props; - const coercedColor = color ?? "currentcolor"; - return ( - - - - - - ); -}; - -export default TimelineLogo; diff --git a/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx b/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx deleted file mode 100644 index 29f6a69f..00000000 --- a/Timeline/ClientApp/src/app/views/common/UserTimelineLogo.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { SVGAttributes } from "react"; - -export interface UserTimelineLogoProps extends SVGAttributes { - color?: string; -} - -const UserTimelineLogo: React.FC = (props) => { - const { color, ...forwardProps } = props; - const coercedColor = color ?? "currentcolor"; - - return ( - - - - - - - - - - - - ); -}; - -export default UserTimelineLogo; diff --git a/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx b/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx deleted file mode 100644 index c74f18e2..00000000 --- a/Timeline/ClientApp/src/app/views/common/alert/AlertHost.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useCallback } from "react"; -import without from "lodash/without"; -import concat from "lodash/concat"; -import { useTranslation } from "react-i18next"; -import { Alert } from "react-bootstrap"; - -import { - alertService, - AlertInfoEx, - kAlertHostId, - AlertInfo, -} from "@/services/alert"; - -interface AutoCloseAlertProps { - alert: AlertInfo; - close: () => void; -} - -export const AutoCloseAlert: React.FC = (props) => { - const { alert } = props; - const { dismissTime } = alert; - - const { t } = useTranslation(); - - React.useEffect(() => { - const tag = - dismissTime === "never" - ? null - : typeof dismissTime === "number" - ? window.setTimeout(props.close, dismissTime) - : window.setTimeout(props.close, 5000); - return () => { - if (tag != null) { - window.clearTimeout(tag); - } - }; - }, [dismissTime, props.close]); - - return ( - - {(() => { - const { message } = alert; - if (typeof message === "function") { - const Message = message; - return ; - } else if (typeof message === "object" && message.type === "i18n") { - return t(message.key); - } else return alert.message; - })()} - - ); -}; - -// oh what a bad name! -interface AlertInfoExEx extends AlertInfoEx { - close: () => void; -} - -const AlertHost: React.FC = () => { - const [alerts, setAlerts] = React.useState([]); - - // react guarantee that state setters are stable, so we don't need to add it to dependency list - - const consume = useCallback((alert: AlertInfoEx): void => { - const alertEx: AlertInfoExEx = { - ...alert, - close: () => { - setAlerts((oldAlerts) => { - return without(oldAlerts, alertEx); - }); - }, - }; - setAlerts((oldAlerts) => { - return concat(oldAlerts, alertEx); - }); - }, []); - - React.useEffect(() => { - alertService.registerConsumer(consume); - return () => { - alertService.unregisterConsumer(consume); - }; - }, [consume]); - - return ( -
- {alerts.map((alert) => { - return ( - - ); - })} -
- ); -}; - -export default AlertHost; diff --git a/Timeline/ClientApp/src/app/views/common/alert/alert.sass b/Timeline/ClientApp/src/app/views/common/alert/alert.sass deleted file mode 100644 index 5b6e65c2..00000000 --- a/Timeline/ClientApp/src/app/views/common/alert/alert.sass +++ /dev/null @@ -1,15 +0,0 @@ -.alert-container - position: fixed - z-index: $zindex-popover - -@include media-breakpoint-up(sm) - .alert-container - bottom: 0 - right: 0 - -@include media-breakpoint-down(sm) - .alert-container - bottom: 0 - right: 0 - left: 0 - text-align: center diff --git a/Timeline/ClientApp/src/app/views/common/common.sass b/Timeline/ClientApp/src/app/views/common/common.sass deleted file mode 100644 index 15d34d7c..00000000 --- a/Timeline/ClientApp/src/app/views/common/common.sass +++ /dev/null @@ -1,33 +0,0 @@ -.image-cropper-container - position: relative - box-sizing: border-box - user-select: none - -.image-cropper-container img - position: absolute - left: 0 - top: 0 - width: 100% - height: 100% - -.image-cropper-mask-container - position: absolute - left: 0 - top: 0 - right: 0 - bottom: 0 - overflow: hidden - -.image-cropper-mask - position: absolute - box-shadow: 0 0 0 10000px rgba(255, 255, 255, 80%) - touch-action: none - -.image-cropper-handler - position: absolute - width: 26px - height: 26px - border: black solid 2px - border-radius: 50% - background: white - touch-action: none diff --git a/Timeline/ClientApp/src/app/views/home/BoardWithUser.tsx b/Timeline/ClientApp/src/app/views/home/BoardWithUser.tsx deleted file mode 100644 index dcd39cbe..00000000 --- a/Timeline/ClientApp/src/app/views/home/BoardWithUser.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from "react"; -import { Row, Col } from "react-bootstrap"; -import { useTranslation } from "react-i18next"; - -import { UserWithToken } from "@/services/user"; -import { TimelineInfo } from "@/services/timeline"; -import { getHttpTimelineClient } from "@/http/timeline"; - -import TimelineBoard from "./TimelineBoard"; -import OfflineBoard from "./OfflineBoard"; - -const BoardWithUser: React.FC<{ user: UserWithToken }> = ({ user }) => { - const { t } = useTranslation(); - - const [ownTimelines, setOwnTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - const [joinTimelines, setJoinTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - - React.useEffect(() => { - let subscribe = true; - if (ownTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ relate: user.username, relateType: "own" }) - .then( - (timelines) => { - if (subscribe) { - setOwnTimelines(timelines); - } - }, - () => { - setOwnTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [user, ownTimelines]); - - React.useEffect(() => { - let subscribe = true; - if (joinTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ relate: user.username, relateType: "join" }) - .then( - (timelines) => { - if (subscribe) { - setJoinTimelines(timelines); - } - }, - () => { - setJoinTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [user, joinTimelines]); - - return ( - - {ownTimelines === "offline" && joinTimelines === "offline" ? ( - - { - setOwnTimelines("loading"); - setJoinTimelines("loading"); - }} - /> - - ) : ( - <> - - { - setOwnTimelines("loading"); - }} - /> - - - { - setJoinTimelines("loading"); - }} - /> - - - )} - - ); -}; - -export default BoardWithUser; diff --git a/Timeline/ClientApp/src/app/views/home/BoardWithoutUser.tsx b/Timeline/ClientApp/src/app/views/home/BoardWithoutUser.tsx deleted file mode 100644 index ebfddb50..00000000 --- a/Timeline/ClientApp/src/app/views/home/BoardWithoutUser.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from "react"; -import { Row, Col } from "react-bootstrap"; - -import { TimelineInfo } from "@/services/timeline"; -import { getHttpTimelineClient } from "@/http/timeline"; - -import TimelineBoard from "./TimelineBoard"; -import OfflineBoard from "./OfflineBoard"; - -const BoardWithoutUser: React.FC = () => { - const [publicTimelines, setPublicTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - - React.useEffect(() => { - let subscribe = true; - if (publicTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ visibility: "Public" }) - .then( - (timelines) => { - if (subscribe) { - setPublicTimelines(timelines); - } - }, - () => { - setPublicTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [publicTimelines]); - - return ( - - {publicTimelines === "offline" ? ( - - { - setPublicTimelines("loading"); - }} - /> - - ) : ( - - { - setPublicTimelines("loading"); - }} - /> - - )} - - ); -}; - -export default BoardWithoutUser; diff --git a/Timeline/ClientApp/src/app/views/home/OfflineBoard.tsx b/Timeline/ClientApp/src/app/views/home/OfflineBoard.tsx deleted file mode 100644 index fc05bd74..00000000 --- a/Timeline/ClientApp/src/app/views/home/OfflineBoard.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import { Trans } from "react-i18next"; - -import { getAllCachedTimelineNames } from "@/services/timeline"; -import UserTimelineLogo from "../common/UserTimelineLogo"; -import TimelineLogo from "../common/TimelineLogo"; - -export interface OfflineBoardProps { - onReload: () => void; -} - -const OfflineBoard: React.FC = ({ onReload }) => { - const [timelines, setTimelines] = React.useState([]); - - React.useEffect(() => { - let subscribe = true; - void getAllCachedTimelineNames().then((t) => { - if (subscribe) setTimelines(t); - }); - return () => { - subscribe = false; - }; - }); - - return ( - <> - - 0 - { - onReload(); - e.preventDefault(); - }} - > - 1 - - 2 - - {timelines.map((timeline) => { - const isPersonal = timeline.startsWith("@"); - const url = isPersonal - ? `/users/${timeline.slice(1)}` - : `/timelines/${timeline}`; - return ( -
- {isPersonal ? ( - - ) : ( - - )} - {timeline} -
- ); - })} - - ); -}; - -export default OfflineBoard; diff --git a/Timeline/ClientApp/src/app/views/home/TimelineBoard.tsx b/Timeline/ClientApp/src/app/views/home/TimelineBoard.tsx deleted file mode 100644 index a3d176e1..00000000 --- a/Timeline/ClientApp/src/app/views/home/TimelineBoard.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { Link } from "react-router-dom"; -import { Trans } from "react-i18next"; -import { Spinner } from "react-bootstrap"; - -import { TimelineInfo } from "@/services/timeline"; -import TimelineLogo from "../common/TimelineLogo"; -import UserTimelineLogo from "../common/UserTimelineLogo"; - -export interface TimelineBoardProps { - title?: string; - timelines: TimelineInfo[] | "offline" | "loading"; - onReload: () => void; - className?: string; -} - -const TimelineBoard: React.FC = (props) => { - const { title, timelines, className } = props; - - return ( -
- {title != null &&

{title}

} - {(() => { - if (timelines === "loading") { - return ( -
- -
- ); - } else if (timelines === "offline") { - return ( - - ); - } else { - return timelines.map((timeline) => { - const { name } = timeline; - const isPersonal = name.startsWith("@"); - const url = isPersonal - ? `/users/${timeline.owner.username}` - : `/timelines/${name}`; - return ( -
- {isPersonal ? ( - - ) : ( - - )} - {name} -
- ); - }); - } - })()} -
- ); -}; - -export default TimelineBoard; diff --git a/Timeline/ClientApp/src/app/views/home/TimelineCreateDialog.tsx b/Timeline/ClientApp/src/app/views/home/TimelineCreateDialog.tsx deleted file mode 100644 index d9467719..00000000 --- a/Timeline/ClientApp/src/app/views/home/TimelineCreateDialog.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import { useHistory } from "react-router"; - -import { validateTimelineName, timelineService } from "@/services/timeline"; -import OperationDialog from "../common/OperationDialog"; - -interface TimelineCreateDialogProps { - open: boolean; - close: () => void; -} - -const TimelineCreateDialog: React.FC = (props) => { - const history = useHistory(); - - let nameSaved: string; - - return ( - { - if (name.length === 0) { - return "home.createDialog.noEmpty"; - } else if (name.length > 26) { - return "home.createDialog.tooLong"; - } else if (!validateTimelineName(name)) { - return "home.createDialog.badFormat"; - } else { - return null; - } - }, - }, - ]} - onProcess={([name]) => { - nameSaved = name as string; - return timelineService.createTimeline(nameSaved).toPromise(); - }} - onSuccessAndClose={() => { - history.push(`timelines/${nameSaved}`); - }} - failurePrompt={(e) => `${e as string}`} - /> - ); -}; - -export default TimelineCreateDialog; diff --git a/Timeline/ClientApp/src/app/views/home/home.sass b/Timeline/ClientApp/src/app/views/home/home.sass deleted file mode 100644 index f5d6ffc3..00000000 --- a/Timeline/ClientApp/src/app/views/home/home.sass +++ /dev/null @@ -1,13 +0,0 @@ -.timeline-board-item - font-size: 1.1em - @extend .my-2 - .icon - height: 1.3em - @extend .mr-2 - -.timeline-board - @extend .cru-card - @extend .d-flex - @extend .flex-column - @extend .p-3 - min-height: 200px diff --git a/Timeline/ClientApp/src/app/views/home/index.tsx b/Timeline/ClientApp/src/app/views/home/index.tsx deleted file mode 100644 index 760adcea..00000000 --- a/Timeline/ClientApp/src/app/views/home/index.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import { useHistory } from "react-router"; -import { useTranslation } from "react-i18next"; -import { Row, Container, Button, Col } from "react-bootstrap"; - -import { useUser } from "@/services/user"; -import SearchInput from "../common/SearchInput"; - -import BoardWithoutUser from "./BoardWithoutUser"; -import BoardWithUser from "./BoardWithUser"; -import TimelineCreateDialog from "./TimelineCreateDialog"; - -const HomePage: React.FC = () => { - const history = useHistory(); - - const { t } = useTranslation(); - - const user = useUser(); - - const [navText, setNavText] = React.useState(""); - - const [dialog, setDialog] = React.useState<"create" | null>(null); - - const goto = React.useCallback((): void => { - if (navText === "") { - history.push("users/crupest"); - } else if (navText.startsWith("@")) { - history.push(`users/${navText.slice(1)}`); - } else { - history.push(`timelines/${navText}`); - } - }, [navText, history]); - - return ( - <> - - - - { - setDialog("create"); - }} - > - {t("home.createButton")} - - ) - } - /> - - - {(() => { - if (user == null) { - return ; - } else { - return ; - } - })()} - - - {dialog === "create" && ( - { - setDialog(null); - }} - /> - )} - - ); -}; - -export default HomePage; diff --git a/Timeline/ClientApp/src/app/views/login/index.tsx b/Timeline/ClientApp/src/app/views/login/index.tsx deleted file mode 100644 index 61b9a525..00000000 --- a/Timeline/ClientApp/src/app/views/login/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from "react"; -import { useHistory } from "react-router"; -import { useTranslation } from "react-i18next"; -import { Container, Form } from "react-bootstrap"; - -import { useUser, userService } from "@/services/user"; - -import AppBar from "../common/AppBar"; -import LoadingButton from "../common/LoadingButton"; - -const LoginPage: React.FC = (_) => { - const { t } = useTranslation(); - const history = useHistory(); - const [username, setUsername] = React.useState(""); - const [usernameDirty, setUsernameDirty] = React.useState(false); - const [password, setPassword] = React.useState(""); - const [passwordDirty, setPasswordDirty] = React.useState(false); - const [rememberMe, setRememberMe] = React.useState(true); - const [process, setProcess] = React.useState(false); - const [error, setError] = React.useState(null); - - const user = useUser(); - - React.useEffect(() => { - if (user != null) { - const id = setTimeout(() => history.push("/"), 3000); - return () => { - clearTimeout(id); - }; - } - }, [history, user]); - - if (user != null) { - return ( - <> - -

{t("login.alreadyLogin")}

- - ); - } - - const submit = (): void => { - if (username === "" || password === "") { - setUsernameDirty(true); - setPasswordDirty(true); - return; - } - - setProcess(true); - userService - .login( - { - username: username, - password: password, - }, - rememberMe - ) - .then( - () => { - if (history.length === 0) { - history.push("/"); - } else { - history.goBack(); - } - }, - (e: Error) => { - setProcess(false); - setError(e.message); - } - ); - }; - - const onEnterPressInPassword: React.KeyboardEventHandler = (e) => { - if (e.key === "Enter") { - submit(); - } - }; - - return ( - -

{t("welcome")}

-
- - {t("user.username")} - { - setUsername(e.target.value); - setUsernameDirty(true); - }} - value={username} - isInvalid={usernameDirty && username === ""} - /> - {usernameDirty && username === "" && ( - - {t("login.emptyUsername")} - - )} - - - {t("user.password")} - { - setPassword(e.target.value); - setPasswordDirty(true); - }} - value={password} - onKeyDown={onEnterPressInPassword} - isInvalid={passwordDirty && password === ""} - /> - {passwordDirty && password === "" && ( - - {t("login.emptyPassword")} - - )} - - - - id="remember-me" - type="checkbox" - checked={rememberMe} - onChange={(e) => { - setRememberMe(e.currentTarget.checked); - }} - label={t("user.rememberMe")} - /> - - {error ?

{t(error)}

: null} -
- { - submit(); - e.preventDefault(); - }} - disabled={username === "" || password === "" ? true : undefined} - > - {t("user.login")} - -
-
-
- ); -}; - -export default LoginPage; diff --git a/Timeline/ClientApp/src/app/views/login/login.sass b/Timeline/ClientApp/src/app/views/login/login.sass deleted file mode 100644 index 0bf385f5..00000000 --- a/Timeline/ClientApp/src/app/views/login/login.sass +++ /dev/null @@ -1,2 +0,0 @@ -.login-container - max-width: 600px diff --git a/Timeline/ClientApp/src/app/views/settings/index.tsx b/Timeline/ClientApp/src/app/views/settings/index.tsx deleted file mode 100644 index 964e7442..00000000 --- a/Timeline/ClientApp/src/app/views/settings/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React, { useState } from "react"; -import { useHistory } from "react-router"; -import { useTranslation } from "react-i18next"; -import { Form, Container, Row, Col, Button, Modal } from "react-bootstrap"; - -import { useUser, userService } from "@/services/user"; -import OperationDialog, { - OperationInputErrorInfo, -} from "../common/OperationDialog"; - -interface ChangePasswordDialogProps { - open: boolean; - close: () => void; -} - -const ChangePasswordDialog: React.FC = (props) => { - const history = useHistory(); - const { t } = useTranslation(); - - const [redirect, setRedirect] = useState(false); - - return ( - - v === "" - ? "settings.dialogChangePassword.errorEmptyOldPassword" - : null, - }, - { - type: "text", - label: t("settings.dialogChangePassword.inputNewPassword"), - password: true, - validator: (v, values) => { - const error: OperationInputErrorInfo = {}; - error[1] = - v === "" - ? "settings.dialogChangePassword.errorEmptyNewPassword" - : null; - if (v === values[2]) { - error[2] = null; - } else { - if (values[2] !== "") { - error[2] = "settings.dialogChangePassword.errorRetypeNotMatch"; - } - } - return error; - }, - }, - { - type: "text", - label: t("settings.dialogChangePassword.inputRetypeNewPassword"), - password: true, - validator: (v, values) => - v !== values[1] - ? "settings.dialogChangePassword.errorRetypeNotMatch" - : null, - }, - ]} - onProcess={async ([oldPassword, newPassword]) => { - await userService - .changePassword(oldPassword as string, newPassword as string) - .toPromise(); - await userService.logout(); - setRedirect(true); - }} - close={() => { - props.close(); - if (redirect) { - history.push("/login"); - } - }} - /> - ); -}; - -const ConfirmLogoutDialog: React.FC<{ - toggle: () => void; - onConfirm: () => void; -}> = ({ toggle, onConfirm }) => { - const { t } = useTranslation(); - - return ( - - - - {t("settings.dialogConfirmLogout.title")} - - - {t("settings.dialogConfirmLogout.prompt")} - - - - - - ); -}; - -const SettingsPage: React.FC = (_) => { - const { i18n, t } = useTranslation(); - const user = useUser(); - const history = useHistory(); - - const [dialog, setDialog] = useState( - null - ); - - const language = i18n.language.slice(0, 2); - - return ( - - {user ? ( - <> - - -
{ - history.push(`/users/${user.username}`); - }} - > - {t("settings.gotoSelf")} -
- -
- - -
setDialog("changepassword")} - > - {t("settings.changePassword")} -
- -
- - -
{ - setDialog("logout"); - }} - > - {t("settings.logout")} -
- -
- - ) : null} - - -
{t("settings.languagePrimary")}
-

{t("settings.languageSecondary")}

- - - { - void i18n.changeLanguage(e.target.value); - }} - > - - - - -
- {(() => { - switch (dialog) { - case "changepassword": - return ( - { - setDialog(null); - }} - /> - ); - case "logout": - return ( - setDialog(null)} - onConfirm={() => { - void userService.logout().then(() => { - history.push("/"); - }); - }} - /> - ); - default: - return null; - } - })()} -
- ); -}; - -export default SettingsPage; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx b/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx deleted file mode 100644 index 3c52150f..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/CollapseButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import Svg from "react-inlinesvg"; -import arrowsAngleContractIcon from "bootstrap-icons/icons/arrows-angle-contract.svg"; -import arrowsAngleExpandIcon from "bootstrap-icons/icons/arrows-angle-expand.svg"; - -const CollapseButton: React.FC<{ - collapse: boolean; - onClick: () => void; - className?: string; - style?: React.CSSProperties; -}> = ({ collapse, onClick, className, style }) => { - return ( - - ); -}; - -export default CollapseButton; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/InfoCardTemplate.tsx b/Timeline/ClientApp/src/app/views/timeline-common/InfoCardTemplate.tsx deleted file mode 100644 index a8de20aa..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/InfoCardTemplate.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import clsx from "clsx"; - -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import SyncStatusBadge from "../timeline-common/SyncStatusBadge"; -import CollapseButton from "../timeline-common/CollapseButton"; - -const InfoCardTemplate: React.FC< - Pick< - TimelineCardComponentProps<"">, - "collapse" | "toggleCollapse" | "syncStatus" | "className" - > & { children: React.ReactElement[] } -> = ({ collapse, toggleCollapse, syncStatus, className, children }) => { - return ( -
-
- - -
- -
{children}
-
- ); -}; - -export default InfoCardTemplate; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx b/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx deleted file mode 100644 index e67cfb43..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/SyncStatusBadge.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { useTranslation } from "react-i18next"; - -import { UiLogicError } from "@/common"; - -export type TimelineSyncStatus = "syncing" | "synced" | "offline"; - -const SyncStatusBadge: React.FC<{ - status: TimelineSyncStatus; - style?: React.CSSProperties; - className?: string; -}> = ({ status, style, className }) => { - const { t } = useTranslation(); - - return ( -
- {(() => { - switch (status) { - case "syncing": { - return ( - <> - - - {t("timeline.postSyncState.syncing")} - - - ); - } - case "synced": { - return ( - <> - - - {t("timeline.postSyncState.synced")} - - - ); - } - case "offline": { - return ( - <> - - - {t("timeline.postSyncState.offline")} - - - ); - } - default: - throw new UiLogicError("Unknown sync state."); - } - })()} -
- ); -}; - -export default SyncStatusBadge; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx b/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx deleted file mode 100644 index fd051d45..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/Timeline.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react"; -import clsx from "clsx"; - -import { TimelinePostInfo } from "@/services/timeline"; - -import TimelineItem from "./TimelineItem"; - -export interface TimelinePostInfoEx extends TimelinePostInfo { - deletable: boolean; -} - -export type TimelineDeleteCallback = (index: number, id: number) => void; - -export interface TimelineProps { - className?: string; - posts: TimelinePostInfoEx[]; - onDelete: TimelineDeleteCallback; - onResize?: () => void; - containerRef?: React.Ref; -} - -const Timeline: React.FC = (props) => { - const { posts, onDelete, onResize } = props; - - const [indexShowDeleteButton, setIndexShowDeleteButton] = React.useState< - number - >(-1); - - const onItemClick = React.useCallback(() => { - setIndexShowDeleteButton(-1); - }, []); - - const onToggleDelete = React.useMemo(() => { - return posts.map((post, i) => { - return post.deletable - ? () => { - setIndexShowDeleteButton((oldIndexShowDeleteButton) => { - return oldIndexShowDeleteButton !== i ? i : -1; - }); - } - : undefined; - }); - }, [posts]); - - const onItemDelete = React.useMemo(() => { - return posts.map((post, i) => { - return () => { - onDelete(i, post.id); - }; - }); - }, [posts, onDelete]); - - return ( -
- {(() => { - const length = posts.length; - return posts.map((post, i) => { - const toggleMore = onToggleDelete[i]; - - return ( - - ); - }); - })()} -
- ); -}; - -export default Timeline; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx deleted file mode 100644 index 4db23371..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelineItem.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { Link } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import Svg from "react-inlinesvg"; -import chevronDownIcon from "bootstrap-icons/icons/chevron-down.svg"; -import trashIcon from "bootstrap-icons/icons/trash.svg"; -import { Modal, Button } from "react-bootstrap"; - -import { useAvatar } from "@/services/user"; -import { TimelinePostInfo } from "@/services/timeline"; - -import BlobImage from "../common/BlobImage"; - -const TimelinePostDeleteConfirmDialog: React.FC<{ - toggle: () => void; - onConfirm: () => void; -}> = ({ toggle, onConfirm }) => { - const { t } = useTranslation(); - - return ( - - - - {t("timeline.post.deleteDialog.title")} - - - {t("timeline.post.deleteDialog.prompt")} - - - - - - ); -}; - -export interface TimelineItemProps { - post: TimelinePostInfo; - current?: boolean; - more?: { - isOpen: boolean; - toggle: () => void; - onDelete: () => void; - }; - onClick?: () => void; - onResize?: () => void; - className?: string; - style?: React.CSSProperties; -} - -const TimelineItem: React.FC = (props) => { - const { i18n } = useTranslation(); - - const current = props.current === true; - - const { more, onResize } = props; - - const avatar = useAvatar(props.post.author.username); - - const [deleteDialog, setDeleteDialog] = React.useState(false); - const toggleDeleteDialog = React.useCallback( - () => setDeleteDialog((old) => !old), - [] - ); - - return ( -
-
-
-
-
-
-
-
- {current &&
} -
-
-
-
- - - {props.post.time.toLocaleString(i18n.languages)} - - {props.post.author.nickname} - - {more != null ? ( - { - more.toggle(); - e.stopPropagation(); - }} - /> - ) : null} -
-
- - - - {(() => { - const { content } = props.post; - if (content.type === "text") { - return content.text; - } else { - return ( - - ); - } - })()} -
-
- {more != null && more.isOpen ? ( - <> -
- { - toggleDeleteDialog(); - e.stopPropagation(); - }} - /> -
- {deleteDialog ? ( - { - toggleDeleteDialog(); - more.toggle(); - }} - onConfirm={more.onDelete} - /> - ) : null} - - ) : null} -
- ); -}; - -export default TimelineItem; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelineMember.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelineMember.tsx deleted file mode 100644 index 67a8543a..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelineMember.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Container, ListGroup, Modal, Row, Col, Button } from "react-bootstrap"; - -import { User, useAvatar } from "@/services/user"; - -import SearchInput from "../common/SearchInput"; -import BlobImage from "../common/BlobImage"; - -const TimelineMemberItem: React.FC<{ - user: User; - owner: boolean; - onRemove?: (username: string) => void; -}> = ({ user, owner, onRemove }) => { - const { t } = useTranslation(); - - const avatar = useAvatar(user.username); - - return ( - - - - - - - {user.nickname} - - {"@" + user.username} - - - {(() => { - if (owner) { - return null; - } - if (onRemove == null) { - return null; - } - return ( - - ); - })()} - - - ); -}; - -export interface TimelineMemberCallbacks { - onCheckUser: (username: string) => Promise; - onAddUser: (user: User) => Promise; - onRemoveUser: (username: string) => void; -} - -export interface TimelineMemberProps { - members: User[]; - edit: TimelineMemberCallbacks | null | undefined; -} - -const TimelineMember: React.FC = (props) => { - const { t } = useTranslation(); - - const [userSearchText, setUserSearchText] = useState(""); - const [userSearchState, setUserSearchState] = useState< - | { - type: "user"; - data: User; - } - | { type: "error"; data: string } - | { type: "loading" } - | { type: "init" } - >({ type: "init" }); - - const userSearchAvatar = useAvatar( - userSearchState.type === "user" ? userSearchState.data.username : undefined - ); - - const members = props.members; - - return ( - - - {members.map((member, index) => ( - - ))} - - {(() => { - const edit = props.edit; - if (edit != null) { - return ( - <> - { - setUserSearchText(v); - }} - loading={userSearchState.type === "loading"} - onButtonClick={() => { - if (userSearchText === "") { - setUserSearchState({ - type: "error", - data: "login.emptyUsername", - }); - return; - } - - setUserSearchState({ type: "loading" }); - edit.onCheckUser(userSearchText).then( - (u) => { - if (u == null) { - setUserSearchState({ - type: "error", - data: "timeline.userNotExist", - }); - } else { - setUserSearchState({ type: "user", data: u }); - } - }, - (e) => { - setUserSearchState({ - type: "error", - data: `${e as string}`, - }); - } - ); - }} - /> - {(() => { - if (userSearchState.type === "user") { - const u = userSearchState.data; - const addable = - members.findIndex((m) => m.username === u.username) === -1; - return ( - <> - {!addable ? ( -

{t("timeline.member.alreadyMember")}

- ) : null} - - - - - - - {u.nickname} - - {"@" + u.username} - - - - - - - ); - } else if (userSearchState.type === "error") { - return ( -

{t(userSearchState.data)}

- ); - } - })()} - - ); - } else { - return null; - } - })()} -
- ); -}; - -export default TimelineMember; - -export interface TimelineMemberDialogProps extends TimelineMemberProps { - open: boolean; - onClose: () => void; -} - -export const TimelineMemberDialog: React.FC = ( - props -) => { - return ( - - - - ); -}; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplate.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplate.tsx deleted file mode 100644 index d5c91622..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { of } from "rxjs"; -import { catchError } from "rxjs/operators"; - -import { UiLogicError } from "@/common"; -import { pushAlert } from "@/services/alert"; -import { useUser, userInfoService, UserNotExistError } from "@/services/user"; -import { - timelineService, - usePostList, - useTimelineInfo, -} from "@/services/timeline"; - -import { TimelineDeleteCallback } from "./Timeline"; -import { TimelineMemberDialog } from "./TimelineMember"; -import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; -import { TimelinePageTemplateUIProps } from "./TimelinePageTemplateUI"; -import { TimelinePostSendCallback } from "./TimelinePostEdit"; - -export interface TimelinePageTemplateProps { - name: string; - onManage: (item: TManageItem) => void; - UiComponent: React.ComponentType< - Omit, "CardComponent"> - >; - notFoundI18nKey: string; -} - -export default function TimelinePageTemplate( - props: TimelinePageTemplateProps -): React.ReactElement | null { - const { t } = useTranslation(); - - const { name } = props; - - const service = timelineService; - - const user = useUser(); - - const [dialog, setDialog] = React.useState( - null - ); - - const timelineState = useTimelineInfo(name); - - const timeline = timelineState?.timeline; - - const postListState = usePostList(name); - - const error: string | undefined = (() => { - if (timelineState != null) { - const { type, timeline } = timelineState; - if (type === "offline" && timeline == null) return "Network Error"; - if (type === "synced" && timeline == null) - return t(props.notFoundI18nKey); - } - return undefined; - })(); - - const closeDialog = React.useCallback((): void => { - setDialog(null); - }, []); - - let dialogElement: React.ReactElement | undefined; - - if (dialog === "property") { - if (timeline == null) { - throw new UiLogicError( - "Timeline is null but attempt to open change property dialog." - ); - } - - dialogElement = ( - { - return service.changeTimelineProperty(name, req).toPromise().then(); - }} - /> - ); - } else if (dialog === "member") { - if (timeline == null) { - throw new UiLogicError( - "Timeline is null but attempt to open change property dialog." - ); - } - - dialogElement = ( - { - return userInfoService - .getUserInfo(u) - .pipe( - catchError((e) => { - if (e instanceof UserNotExistError) { - return of(null); - } else { - throw e; - } - }) - ) - .toPromise(); - }, - onAddUser: (u) => { - return service.addMember(name, u.username).toPromise().then(); - }, - onRemoveUser: (u) => { - service.removeMember(name, u); - }, - } - : null - } - /> - ); - } - - const { UiComponent } = props; - - const onDelete: TimelineDeleteCallback = React.useCallback( - (index, id) => { - service.deletePost(name, id).subscribe(null, () => { - pushAlert({ - type: "danger", - message: t("timeline.deletePostFailed"), - }); - }); - }, - [service, name, t] - ); - - const onPost: TimelinePostSendCallback = React.useCallback( - (req) => { - return service.createPost(name, req).toPromise().then(); - }, - [service, name] - ); - - const onManageProp = props.onManage; - - const onManage = React.useCallback( - (item: "property" | TManageItem) => { - if (item === "property") { - setDialog(item); - } else { - onManageProp(item); - } - }, - [onManageProp] - ); - - return ( - <> - setDialog("member")} - /> - {dialogElement} - - ); -} diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx deleted file mode 100644 index 6c2c43c1..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { fromEvent } from "rxjs"; -import { Spinner } from "react-bootstrap"; - -import { getAlertHost } from "@/services/alert"; -import { useEventEmiiter, UiLogicError } from "@/common"; -import { - TimelineInfo, - TimelinePostsWithSyncState, - timelineService, -} from "@/services/timeline"; -import { userService } from "@/services/user"; - -import Timeline, { - TimelinePostInfoEx, - TimelineDeleteCallback, -} from "./Timeline"; -import TimelineTop from "./TimelineTop"; -import TimelinePostEdit, { TimelinePostSendCallback } from "./TimelinePostEdit"; -import { TimelineSyncStatus } from "./SyncStatusBadge"; - -export interface TimelineCardComponentProps { - timeline: TimelineInfo; - onManage?: (item: TManageItems | "property") => void; - onMember: () => void; - className?: string; - collapse: boolean; - syncStatus: TimelineSyncStatus; - toggleCollapse: () => void; -} - -export interface TimelinePageTemplateUIProps { - timeline?: TimelineInfo; - postListState?: TimelinePostsWithSyncState; - CardComponent: React.ComponentType>; - onMember: () => void; - onManage?: (item: TManageItems | "property") => void; - onPost?: TimelinePostSendCallback; - onDelete: TimelineDeleteCallback; - error?: string; -} - -export default function TimelinePageTemplateUI( - props: TimelinePageTemplateUIProps -): React.ReactElement | null { - const { timeline, postListState } = props; - - const { t } = useTranslation(); - - const bottomSpaceRef = React.useRef(null); - - const onPostEditHeightChange = React.useCallback((height: number): void => { - const { current: bottomSpaceDiv } = bottomSpaceRef; - if (bottomSpaceDiv != null) { - bottomSpaceDiv.style.height = `${height}px`; - } - if (height === 0) { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.removeProperty("margin-bottom"); - } - } else { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.marginBottom = `${height}px`; - } - } - }, []); - - const timelineRef = React.useRef(null); - - const [getResizeEvent, triggerResizeEvent] = useEventEmiiter(); - - React.useEffect(() => { - const { current: timelineElement } = timelineRef; - if (timelineElement != null) { - let loadingScrollToBottom = true; - let pinBottom = false; - - const isAtBottom = (): boolean => - window.innerHeight + window.scrollY + 10 >= document.body.scrollHeight; - - const disableLoadingScrollToBottom = (): void => { - loadingScrollToBottom = false; - if (isAtBottom()) pinBottom = true; - }; - - const checkAndScrollToBottom = (): void => { - if (loadingScrollToBottom || pinBottom) { - window.scrollTo(0, document.body.scrollHeight); - } - }; - - const subscriptions = [ - fromEvent(timelineElement, "wheel").subscribe( - disableLoadingScrollToBottom - ), - fromEvent(timelineElement, "pointerdown").subscribe( - disableLoadingScrollToBottom - ), - fromEvent(timelineElement, "keydown").subscribe( - disableLoadingScrollToBottom - ), - fromEvent(window, "scroll").subscribe(() => { - if (loadingScrollToBottom) return; - - if (isAtBottom()) { - pinBottom = true; - } else { - pinBottom = false; - } - }), - fromEvent(window, "resize").subscribe(checkAndScrollToBottom), - getResizeEvent().subscribe(checkAndScrollToBottom), - ]; - - return () => { - subscriptions.forEach((s) => s.unsubscribe()); - }; - } - }, [getResizeEvent, triggerResizeEvent, timeline, postListState]); - - const genCardCollapseLocalStorageKey = (uniqueId: string): string => - `timeline.${uniqueId}.cardCollapse`; - - const cardCollapseLocalStorageKey = - timeline != null ? genCardCollapseLocalStorageKey(timeline.uniqueId) : null; - - const [cardCollapse, setCardCollapse] = React.useState(true); - React.useEffect(() => { - if (cardCollapseLocalStorageKey != null) { - const savedCollapse = - window.localStorage.getItem(cardCollapseLocalStorageKey) === "true"; - setCardCollapse(savedCollapse); - } - }, [cardCollapseLocalStorageKey]); - - const toggleCardCollapse = (): void => { - const newState = !cardCollapse; - setCardCollapse(newState); - if (timeline != null) { - window.localStorage.setItem( - genCardCollapseLocalStorageKey(timeline.uniqueId), - newState.toString() - ); - } - }; - - let body: React.ReactElement; - - if (props.error != null) { - body =

{t(props.error)}

; - } else { - if (timeline != null) { - let timelineBody: React.ReactElement; - if (postListState != null) { - if (postListState.type === "notexist") { - throw new UiLogicError( - "Timeline is not null but post list state is notexist." - ); - } - if (postListState.type === "forbid") { - timelineBody = ( -

{t("timeline.messageCantSee")}

- ); - } else { - const posts: TimelinePostInfoEx[] = postListState.posts.map( - (post) => ({ - ...post, - deletable: timelineService.hasModifyPostPermission( - userService.currentUser, - timeline, - post - ), - }) - ); - - timelineBody = ( - - ); - if (props.onPost != null) { - timelineBody = ( - <> - {timelineBody} -
- - - ); - } - } - } else { - timelineBody = ( -
- -
- ); - } - - const { CardComponent } = props; - const syncStatus: TimelineSyncStatus = - postListState == null || postListState.syncing - ? "syncing" - : postListState.type === "synced" - ? "synced" - : "offline"; - - body = ( - <> - - - {timelineBody} - - ); - } else { - body = ( -
- -
- ); - } - } - - return body; -} diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePostEdit.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePostEdit.tsx deleted file mode 100644 index dfa2f879..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePostEdit.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { useTranslation } from "react-i18next"; -import Svg from "react-inlinesvg"; -import { Button, Spinner, Row, Col, Form } from "react-bootstrap"; -import textIcon from "bootstrap-icons/icons/card-text.svg"; -import imageIcon from "bootstrap-icons/icons/image.svg"; - -import { UiLogicError } from "@/common"; - -import { pushAlert } from "@/services/alert"; -import { TimelineCreatePostRequest } from "@/services/timeline"; - -interface TimelinePostEditImageProps { - onSelect: (blob: Blob | null) => void; -} - -const TimelinePostEditImage: React.FC = (props) => { - const { onSelect } = props; - const { t } = useTranslation(); - - const [file, setFile] = React.useState(null); - const [fileUrl, setFileUrl] = React.useState(null); - const [error, setError] = React.useState(null); - - React.useEffect(() => { - if (file != null) { - const url = URL.createObjectURL(file); - setFileUrl(url); - return () => { - URL.revokeObjectURL(url); - }; - } - }, [file]); - - const onInputChange: React.ChangeEventHandler = React.useCallback( - (e) => { - const files = e.target.files; - if (files == null || files.length === 0) { - setFile(null); - setFileUrl(null); - } else { - setFile(files[0]); - } - onSelect(null); - setError(null); - }, - [onSelect] - ); - - const onImgLoad = React.useCallback(() => { - onSelect(file); - }, [onSelect, file]); - - const onImgError = React.useCallback(() => { - setError("loadImageError"); - }, []); - - return ( - <> - - {fileUrl && error == null && ( - - )} - {error != null &&
{t(error)}
} - - ); -}; - -export type TimelinePostSendCallback = ( - content: TimelineCreatePostRequest -) => Promise; - -export interface TimelinePostEditProps { - className?: string; - onPost: TimelinePostSendCallback; - onHeightChange?: (height: number) => void; - timelineUniqueId: string; -} - -const TimelinePostEdit: React.FC = (props) => { - const { onPost } = props; - - const { t } = useTranslation(); - - const [state, setState] = React.useState<"input" | "process">("input"); - const [kind, setKind] = React.useState<"text" | "image">("text"); - const [text, setText] = React.useState(""); - const [imageBlob, setImageBlob] = React.useState(null); - - const draftLocalStorageKey = `timeline.${props.timelineUniqueId}.postDraft`; - - React.useEffect(() => { - setText(window.localStorage.getItem(draftLocalStorageKey) ?? ""); - }, [draftLocalStorageKey]); - - const canSend = kind === "text" || (kind === "image" && imageBlob != null); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const containerRef = React.useRef(null!); - - const notifyHeightChange = (): void => { - if (props.onHeightChange) { - props.onHeightChange(containerRef.current.clientHeight); - } - }; - - React.useEffect(() => { - if (props.onHeightChange) { - props.onHeightChange(containerRef.current.clientHeight); - } - return () => { - if (props.onHeightChange) { - props.onHeightChange(0); - } - }; - }); - - const toggleKind = React.useCallback(() => { - setKind((oldKind) => (oldKind === "text" ? "image" : "text")); - setImageBlob(null); - }, []); - - const onSend = React.useCallback(() => { - setState("process"); - - const req: TimelineCreatePostRequest = (() => { - switch (kind) { - case "text": - return { - content: { - type: "text", - text: text, - }, - } as TimelineCreatePostRequest; - case "image": - if (imageBlob == null) { - throw new UiLogicError( - "Content type is image but image blob is null." - ); - } - return { - content: { - type: "image", - data: imageBlob, - }, - } as TimelineCreatePostRequest; - default: - throw new UiLogicError("Unknown content type."); - } - })(); - - onPost(req).then( - (_) => { - if (kind === "text") { - setText(""); - window.localStorage.removeItem(draftLocalStorageKey); - } - setState("input"); - setKind("text"); - }, - (_) => { - pushAlert({ - type: "danger", - message: t("timeline.sendPostFailed"), - }); - setState("input"); - } - ); - }, [onPost, kind, text, imageBlob, t, draftLocalStorageKey]); - - const onImageSelect = React.useCallback((blob: Blob | null) => { - setImageBlob(blob); - }, []); - - return ( -
- - - {kind === "text" ? ( - ) => { - const value = event.currentTarget.value; - setText(value); - window.localStorage.setItem(draftLocalStorageKey, value); - }} - /> - ) : ( - - )} - - - {(() => { - if (state === "input") { - return ( - <> -
- -
- - - ); - } else { - return ; - } - })()} - -
-
- ); -}; - -export default TimelinePostEdit; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx deleted file mode 100644 index 87638f31..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from "react"; - -import { - TimelineVisibility, - kTimelineVisibilities, - TimelineChangePropertyRequest, -} from "@/services/timeline"; - -import OperationDialog, { - OperationSelectInputInfoOption, -} from "../common/OperationDialog"; - -export interface TimelinePropertyInfo { - visibility: TimelineVisibility; - description: string; -} - -export interface TimelinePropertyChangeDialogProps { - open: boolean; - close: () => void; - oldInfo: TimelinePropertyInfo; - onProcess: (request: TimelineChangePropertyRequest) => Promise; -} - -const labelMap: { [key in TimelineVisibility]: string } = { - Private: "timeline.visibility.private", - Public: "timeline.visibility.public", - Register: "timeline.visibility.register", -}; - -const TimelinePropertyChangeDialog: React.FC = ( - props -) => { - return ( - ( - (v) => ({ - label: labelMap[v], - value: v, - }) - ), - initValue: props.oldInfo.visibility, - }, - { - type: "text", - label: "timeline.dialogChangeProperty.description", - initValue: props.oldInfo.description, - }, - ]} - open={props.open} - close={props.close} - onProcess={([newVisibility, newDescription]) => { - const req: TimelineChangePropertyRequest = {}; - if (newVisibility !== props.oldInfo.visibility) { - req.visibility = newVisibility as TimelineVisibility; - } - if (newDescription !== props.oldInfo.description) { - req.description = newDescription as string; - } - return props.onProcess(req); - }} - /> - ); -}; - -export default TimelinePropertyChangeDialog; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/TimelineTop.tsx b/Timeline/ClientApp/src/app/views/timeline-common/TimelineTop.tsx deleted file mode 100644 index 93a2a32c..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/TimelineTop.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -export interface TimelineTopProps { - height?: number | string; - children?: React.ReactElement; -} - -const TimelineTop: React.FC = ({ height, children }) => { - return ( -
-
-
-
-
-
- {children} -
- ); -}; - -export default TimelineTop; diff --git a/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass b/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass deleted file mode 100644 index 4151bfcc..00000000 --- a/Timeline/ClientApp/src/app/views/timeline-common/timeline-common.sass +++ /dev/null @@ -1,146 +0,0 @@ -@use 'sass:color' - -.timeline - z-index: 0 - position: relative - - &-item - display: flex - -$timeline-line-width: 7px -$timeline-line-node-radius: 18px -$timeline-line-color: $primary -$timeline-line-color-current: #36c2e6 - -@keyframes timeline-line-node-noncurrent - from - background: $timeline-line-color - - to - background: color.adjust($timeline-line-color, $lightness: +10%) - box-shadow: 0 0 20px 3px color.adjust($timeline-line-color, $lightness: +10%, $alpha: -0.1) - -@keyframes timeline-line-node-current - from - background: $timeline-line-color-current - - to - background: color.adjust($timeline-line-color-current, $lightness: +10%) - box-shadow: 0 0 20px 3px color.adjust($timeline-line-color-current, $lightness: +10%, $alpha: -0.1) - -.timeline-line - &-area-container - display: flex - justify-content: flex-end - padding-right: 5px - - flex: 0 0 auto - width: 60px - - &-area - display: flex - flex-direction: column - align-items: center - width: 30px - - &-segment - width: $timeline-line-width - background: $timeline-line-color - - &.start - height: 14px - flex: 0 0 auto - - &.end - flex: 1 1 auto - - &.current-end - height: 20px - flex: 0 0 auto - background: linear-gradient($timeline-line-color-current, transparent) - - &-node-container - flex: 0 0 auto - position: relative - width: $timeline-line-node-radius - height: $timeline-line-node-radius - - &-node - width: $timeline-line-node-radius + 2 - height: $timeline-line-node-radius + 2 - position: absolute - left: -1px - top: -1px - border-radius: 50% - box-sizing: border-box - z-index: 1 - animation: 1s infinite alternate - animation-name: timeline-line-node-noncurrent - -.timeline-top - display: flex - justify-content: space-between - - .timeline-line-segment - flex: 1 1 auto - -.current - .timeline-line - &-segment - - &.start - background: linear-gradient($timeline-line-color, $timeline-line-color-current) - - &.end - background: $timeline-line-color-current - - &-node - animation-name: timeline-line-node-current - -.timeline-content-area - padding: 10px 0 - flex-grow: 1 - -.timeline-item-delete-button - position: absolute - right: 0 - bottom: 0 - -.timeline-content - white-space: pre-line - -.timeline-content-image - max-width: 60% - max-height: 200px - -.timeline-post-edit-image - max-width: 100px - max-height: 100px - -.mask - background: change-color($color: white, $alpha: 0.8) - z-index: 100 - -.timeline-page-top-space - transition: height 0.5s - -.timeline-sync-state-badge - font-size: 0.8em - padding: 3px 8px - border-radius: 5px - background: #e8fbff - -.timeline-sync-state-badge-pin - display: inline-block - width: 0.4em - height: 0.4em - border-radius: 50% - vertical-align: middle - margin-right: 0.6em - -.timeline-template-card - position: fixed - z-index: 1 - top: 56px - right: 0 - margin: 0.5em diff --git a/Timeline/ClientApp/src/app/views/timeline/TimelineDeleteDialog.tsx b/Timeline/ClientApp/src/app/views/timeline/TimelineDeleteDialog.tsx deleted file mode 100644 index 894b8195..00000000 --- a/Timeline/ClientApp/src/app/views/timeline/TimelineDeleteDialog.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import { useHistory } from "react-router"; -import { Trans } from "react-i18next"; - -import { timelineService } from "@/services/timeline"; - -import OperationDialog from "../common/OperationDialog"; - -interface TimelineDeleteDialog { - open: boolean; - name: string; - close: () => void; -} - -const TimelineDeleteDialog: React.FC = (props) => { - const history = useHistory(); - - const { name } = props; - - return ( - { - return ( - - 0{{ name }}2 - - ); - }} - inputScheme={[ - { - type: "text", - validator: (value) => { - if (value !== name) { - return "timeline.deleteDialog.notMatch"; - } else { - return null; - } - }, - }, - ]} - onProcess={() => { - return timelineService.deleteTimeline(name).toPromise(); - }} - onSuccessAndClose={() => { - history.replace("/"); - }} - /> - ); -}; - -export default TimelineDeleteDialog; diff --git a/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx b/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx deleted file mode 100644 index 2d787709..00000000 --- a/Timeline/ClientApp/src/app/views/timeline/TimelineInfoCard.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Dropdown, Button } from "react-bootstrap"; - -import { useAvatar } from "@/services/user"; -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; - -import BlobImage from "../common/BlobImage"; -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; - -export type OrdinaryTimelineManageItem = "delete"; - -export type TimelineInfoCardProps = TimelineCardComponentProps< - OrdinaryTimelineManageItem ->; - -const TimelineInfoCard: React.FC = (props) => { - const { - timeline, - collapse, - onMember, - onManage, - syncStatus, - toggleCollapse, - } = props; - - const { t } = useTranslation(); - - const avatar = useAvatar(timeline?.owner?.username); - - return ( - -

- {timeline.name} -

-
- - {timeline.owner.nickname} - - @{timeline.owner.username} - -
-

{timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - -
- {onManage != null ? ( - - - {t("timeline.manage")} - - - onManage("property")}> - {t("timeline.manageItem.property")} - - - {t("timeline.manageItem.member")} - - - onManage("delete")} - > - {t("timeline.manageItem.delete")} - - - - ) : ( - - )} -
-
- ); -}; - -export default TimelineInfoCard; diff --git a/Timeline/ClientApp/src/app/views/timeline/TimelinePageUI.tsx b/Timeline/ClientApp/src/app/views/timeline/TimelinePageUI.tsx deleted file mode 100644 index 67ea699e..00000000 --- a/Timeline/ClientApp/src/app/views/timeline/TimelinePageUI.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -import TimelinePageTemplateUI, { - TimelinePageTemplateUIProps, -} from "../timeline-common/TimelinePageTemplateUI"; - -import TimelineInfoCard, { - OrdinaryTimelineManageItem, -} from "./TimelineInfoCard"; - -export type TimelinePageUIProps = Omit< - TimelinePageTemplateUIProps, - "CardComponent" ->; - -const TimelinePageUI: React.FC = (props) => { - return ; -}; - -export default TimelinePageUI; diff --git a/Timeline/ClientApp/src/app/views/timeline/index.tsx b/Timeline/ClientApp/src/app/views/timeline/index.tsx deleted file mode 100644 index 225a1a59..00000000 --- a/Timeline/ClientApp/src/app/views/timeline/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { useParams } from "react-router"; - -import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate"; - -import TimelinePageUI from "./TimelinePageUI"; -import { OrdinaryTimelineManageItem } from "./TimelineInfoCard"; -import TimelineDeleteDialog from "./TimelineDeleteDialog"; - -const TimelinePage: React.FC = (_) => { - const { name } = useParams<{ name: string }>(); - - const [dialog, setDialog] = React.useState( - null - ); - - let dialogElement: React.ReactElement | undefined; - if (dialog === "delete") { - dialogElement = ( - setDialog(null)} name={name} /> - ); - } - - return ( - <> - setDialog(item)} - notFoundI18nKey="timeline.timelineNotExist" - /> - {dialogElement} - - ); -}; - -export default TimelinePage; diff --git a/Timeline/ClientApp/src/app/views/timeline/timeline.sass b/Timeline/ClientApp/src/app/views/timeline/timeline.sass deleted file mode 100644 index e69de29b..00000000 diff --git a/Timeline/ClientApp/src/app/views/user/ChangeAvatarDialog.tsx b/Timeline/ClientApp/src/app/views/user/ChangeAvatarDialog.tsx deleted file mode 100644 index ffa2218b..00000000 --- a/Timeline/ClientApp/src/app/views/user/ChangeAvatarDialog.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useTranslation } from "react-i18next"; -import { AxiosError } from "axios"; -import { Modal, Row, Button } from "react-bootstrap"; - -import { UiLogicError } from "@/common"; - -import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; - -export interface ChangeAvatarDialogProps { - open: boolean; - close: () => void; - process: (blob: Blob) => Promise; -} - -const ChangeAvatarDialog: React.FC = (props) => { - const { t } = useTranslation(); - - const [file, setFile] = React.useState(null); - const [fileUrl, setFileUrl] = React.useState(null); - const [clip, setClip] = React.useState(null); - const [ - cropImgElement, - setCropImgElement, - ] = React.useState(null); - const [resultBlob, setResultBlob] = React.useState(null); - const [resultUrl, setResultUrl] = React.useState(null); - - const [state, setState] = React.useState< - | "select" - | "crop" - | "processcrop" - | "preview" - | "uploading" - | "success" - | "error" - >("select"); - - const [message, setMessage] = useState< - string | { type: "custom"; text: string } | null - >("userPage.dialogChangeAvatar.prompt.select"); - - const trueMessage = - message == null - ? null - : typeof message === "string" - ? t(message) - : message.text; - - const closeDialog = props.close; - - const close = React.useCallback((): void => { - if (!(state === "uploading")) { - closeDialog(); - } - }, [state, closeDialog]); - - useEffect(() => { - if (file != null) { - const url = URL.createObjectURL(file); - setClip(null); - setFileUrl(url); - setState("crop"); - return () => { - URL.revokeObjectURL(url); - }; - } else { - setFileUrl(null); - setState("select"); - } - }, [file]); - - React.useEffect(() => { - if (resultBlob != null) { - const url = URL.createObjectURL(resultBlob); - setResultUrl(url); - setState("preview"); - return () => { - URL.revokeObjectURL(url); - }; - } else { - setResultUrl(null); - } - }, [resultBlob]); - - const onSelectFile = React.useCallback( - (e: React.ChangeEvent): void => { - const files = e.target.files; - if (files == null || files.length === 0) { - setFile(null); - } else { - setFile(files[0]); - } - }, - [] - ); - - const onCropNext = React.useCallback(() => { - if ( - cropImgElement == null || - clip == null || - clip.width === 0 || - file == null - ) { - throw new UiLogicError(); - } - - setState("processcrop"); - void applyClipToImage(cropImgElement, clip, file.type).then((b) => { - setResultBlob(b); - }); - }, [cropImgElement, clip, file]); - - const onCropPrevious = React.useCallback(() => { - setFile(null); - setState("select"); - }, []); - - const onPreviewPrevious = React.useCallback(() => { - setResultBlob(null); - setState("crop"); - }, []); - - const process = props.process; - - const upload = React.useCallback(() => { - if (resultBlob == null) { - throw new UiLogicError(); - } - - setState("uploading"); - process(resultBlob).then( - () => { - setState("success"); - }, - (e: unknown) => { - setState("error"); - setMessage({ type: "custom", text: (e as AxiosError).message }); - } - ); - }, [resultBlob, process]); - - const createPreviewRow = (): React.ReactElement => { - if (resultUrl == null) { - throw new UiLogicError(); - } - return ( - - {t("userPage.dialogChangeAvatar.previewImgAlt")} - - ); - }; - - return ( - - - {t("userPage.dialogChangeAvatar.title")} - - {(() => { - if (state === "select") { - return ( - <> - - {t("userPage.dialogChangeAvatar.prompt.select")} - - - - - - - - - ); - } else if (state === "crop") { - if (fileUrl == null) { - throw new UiLogicError(); - } - return ( - <> - - - - - {t("userPage.dialogChangeAvatar.prompt.crop")} - - - - - - - - ); - } else if (state === "processcrop") { - return ( - <> - - - {t("userPage.dialogChangeAvatar.prompt.processingCrop")} - - - - - - - - ); - } else if (state === "preview") { - return ( - <> - - {createPreviewRow()} - {t("userPage.dialogChangeAvatar.prompt.preview")} - - - - - - - - ); - } else if (state === "uploading") { - return ( - <> - - {createPreviewRow()} - {t("userPage.dialogChangeAvatar.prompt.uploading")} - - - - ); - } else if (state === "success") { - return ( - <> - - - {t("operationDialog.success")} - - - - - - - ); - } else { - return ( - <> - - {createPreviewRow()} - {trueMessage} - - - - - - - ); - } - })()} - - ); -}; - -export default ChangeAvatarDialog; diff --git a/Timeline/ClientApp/src/app/views/user/ChangeNicknameDialog.tsx b/Timeline/ClientApp/src/app/views/user/ChangeNicknameDialog.tsx deleted file mode 100644 index 251b18c5..00000000 --- a/Timeline/ClientApp/src/app/views/user/ChangeNicknameDialog.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; - -import OperationDialog from "../common/OperationDialog"; - -export interface ChangeNicknameDialogProps { - open: boolean; - close: () => void; - onProcess: (newNickname: string) => Promise; -} - -const ChangeNicknameDialog: React.FC = (props) => { - return ( - { - return props.onProcess(newNickname as string); - }} - close={props.close} - /> - ); -}; - -export default ChangeNicknameDialog; diff --git a/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx b/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx deleted file mode 100644 index 888fb18a..00000000 --- a/Timeline/ClientApp/src/app/views/user/UserInfoCard.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Dropdown, Button } from "react-bootstrap"; - -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; -import { useAvatar } from "@/services/user"; - -import BlobImage from "../common/BlobImage"; -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; - -export type PersonalTimelineManageItem = "avatar" | "nickname"; - -export type UserInfoCardProps = TimelineCardComponentProps< - PersonalTimelineManageItem ->; - -const UserInfoCard: React.FC = (props) => { - const { - timeline, - collapse, - onMember, - onManage, - syncStatus, - toggleCollapse, - } = props; - const { t } = useTranslation(); - - const avatar = useAvatar(timeline?.owner?.username); - - return ( - -
- - {timeline.owner.nickname} - - @{timeline.owner.username} - -
-

{timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - -
- {onManage != null ? ( - - - {t("timeline.manage")} - - - onManage("nickname")}> - {t("timeline.manageItem.nickname")} - - onManage("avatar")}> - {t("timeline.manageItem.avatar")} - - onManage("property")}> - {t("timeline.manageItem.property")} - - - {t("timeline.manageItem.member")} - - - - ) : ( - - )} -
-
- ); -}; - -export default UserInfoCard; diff --git a/Timeline/ClientApp/src/app/views/user/UserPageUI.tsx b/Timeline/ClientApp/src/app/views/user/UserPageUI.tsx deleted file mode 100644 index d405399c..00000000 --- a/Timeline/ClientApp/src/app/views/user/UserPageUI.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -import TimelinePageTemplateUI, { - TimelinePageTemplateUIProps, -} from "../timeline-common/TimelinePageTemplateUI"; - -import UserInfoCard, { PersonalTimelineManageItem } from "./UserInfoCard"; - -export type UserPageUIProps = Omit< - TimelinePageTemplateUIProps, - "CardComponent" ->; - -const UserPageUI: React.FC = (props) => { - return ; -}; - -export default UserPageUI; diff --git a/Timeline/ClientApp/src/app/views/user/index.tsx b/Timeline/ClientApp/src/app/views/user/index.tsx deleted file mode 100644 index 7c0b1563..00000000 --- a/Timeline/ClientApp/src/app/views/user/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useState } from "react"; -import { useParams } from "react-router"; - -import { UiLogicError } from "@/common"; -import { useUser, userInfoService } from "@/services/user"; - -import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate"; - -import UserPageUI from "./UserPageUI"; -import { PersonalTimelineManageItem } from "./UserInfoCard"; -import ChangeNicknameDialog from "./ChangeNicknameDialog"; -import ChangeAvatarDialog from "./ChangeAvatarDialog"; - -const UserPage: React.FC = (_) => { - const { username } = useParams<{ username: string }>(); - - const user = useUser(); - - const [dialog, setDialog] = useState(null); - - let dialogElement: React.ReactElement | undefined; - - const closeDialogHandler = (): void => { - setDialog(null); - }; - - if (dialog === "nickname") { - if (user == null) { - throw new UiLogicError("Change nickname without login."); - } - - dialogElement = ( - - userInfoService.setNickname(username, newNickname) - } - /> - ); - } else if (dialog === "avatar") { - if (user == null) { - throw new UiLogicError("Change avatar without login."); - } - - dialogElement = ( - userInfoService.setAvatar(username, file)} - /> - ); - } - - const onManage = React.useCallback((item: PersonalTimelineManageItem) => { - setDialog(item); - }, []); - - return ( - <> - - {dialogElement} - - ); -}; - -export default UserPage; diff --git a/Timeline/ClientApp/src/app/views/user/user.sass b/Timeline/ClientApp/src/app/views/user/user.sass deleted file mode 100644 index 5b7fcae7..00000000 --- a/Timeline/ClientApp/src/app/views/user/user.sass +++ /dev/null @@ -1,7 +0,0 @@ -.change-avatar-cropper-row - max-height: 400px - -.change-avatar-img - min-width: 50% - max-width: 100% - max-height: 400px diff --git a/Timeline/ClientApp/src/sw/sw.ts b/Timeline/ClientApp/src/sw/sw.ts deleted file mode 100644 index d6202f36..00000000 --- a/Timeline/ClientApp/src/sw/sw.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { precacheAndRoute, matchPrecache } from "workbox-precaching"; -import { setDefaultHandler } from "workbox-routing"; -import { NetworkOnly } from "workbox-strategies"; - -declare let self: ServiceWorkerGlobalScope; - -self.addEventListener("message", (event) => { - if (event.data && (event.data as { type: string }).type === "SKIP_WAITING") { - void self.skipWaiting(); - } -}); - -precacheAndRoute(self.__WB_MANIFEST); - -const networkOnly = new NetworkOnly(); - -setDefaultHandler((options) => { - const { request, url } = options; - if (url && url.pathname.startsWith("/api/")) { - return networkOnly.handle(options); - } - - if (request instanceof Request && request.destination === "document") - return matchPrecache("/index.html").then((r) => - r == null ? Response.error() : r - ); - else return networkOnly.handle(options); -}); diff --git a/Timeline/ClientApp/src/sw/tsconfig.json b/Timeline/ClientApp/src/sw/tsconfig.json deleted file mode 100644 index aac99e59..00000000 --- a/Timeline/ClientApp/src/sw/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "lib": [ - "esnext", - "webworker" - ] - }, - "include": [ - "." - ] -} diff --git a/Timeline/ClientApp/src/tsconfig.json b/Timeline/ClientApp/src/tsconfig.json deleted file mode 100644 index 6937be63..00000000 --- a/Timeline/ClientApp/src/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "sourceMap": true, - "baseUrl": "./", - "paths": { - "@/*": [ - "app/*" - ] - } - } -} diff --git a/Timeline/ClientApp/webpack.common.js b/Timeline/ClientApp/webpack.common.js deleted file mode 100644 index 3779003e..00000000 --- a/Timeline/ClientApp/webpack.common.js +++ /dev/null @@ -1,86 +0,0 @@ -const path = require("path"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const postcssPresetEnv = require("postcss-preset-env"); -const Config = require("webpack-chain"); - -const config = new Config(); - -config.entry("index").add(path.resolve(__dirname, "src/app/index.tsx")); - -config.module - .rule("ts") - .test(/\.ts(x?)$/) - .exclude.add(/node_modules/) - .end() - .use("babel") - .loader("babel-loader") - .end() - .use("ts") - .loader("ts-loader") - .end(); - -config.module - .rule("js") - .test(/\.js(x?)$/) - .exclude.add(/node_modules/) - .end() - .use("babel") - .loader("babel-loader") - .end(); - -config.module - .rule("css") - .test(/\.css$/) - .use("css") - .loader("css-loader") - .end() - .use("postcss") - .loader("postcss-loader") - .end(); - -config.module - .rule("sass") - .test(/\.(scss|sass)$/) - .use("css") - .loader("css-loader") - .end() - .use("postcss") - .loader("postcss-loader") - .end() - .use("sass") - .loader("sass-loader") - .end(); - -config.module - .rule("file") - .test(/\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot)$/i) - .use("url") - .loader("url-loader") - .options({ - limit: 8192, - }); - -config.resolve.extensions - .add("*") - .add(".js") - .add(".jsx") - .add(".ts") - .add(".tsx") - .end(); - -config.resolve.alias.set("@", path.resolve(__dirname, "src/app")); - -config.output - .path(path.resolve(__dirname, "dist/")) - .filename("[name].[contenthash].js") - .chunkFilename("[name].[contenthash].js") - .publicPath("/"); - -config.plugin("html").use(HtmlWebpackPlugin, [ - { - template: "src/app/index.ejs", - title: "Timeline", - }, -]); - -module.exports = config; diff --git a/Timeline/ClientApp/webpack.config.dev.js b/Timeline/ClientApp/webpack.config.dev.js deleted file mode 100644 index c88e1aaf..00000000 --- a/Timeline/ClientApp/webpack.config.dev.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); - -const config = require("./webpack.common"); - -config.mode("development"); - -config.entry("index").add("react-hot-loader/patch"); - -config.module - .rule("ts") - .use("babel") - .options({ - plugins: ["react-hot-loader/babel"], - }); - -config.module - .rule("js") - .use("babel") - .options({ - plugins: ["react-hot-loader/babel"], - }); - -config.module - .rule("css") - .use("style") - .before("css") - .loader("style-loader") - .end(); - -config.module - .rule("sass") - .use("style") - .before("css") - .loader("style-loader") - .end(); - -config.devtool("eval-cheap-module-source-map"); - -config.resolve.alias.set("react-dom", "@hot-loader/react-dom"); - -config.devServer - .contentBase(path.resolve(__dirname, "public/")) - .host("0.0.0.0") - .port(3000) - .historyApiFallback(true) - .hotOnly(true) - .allowedHosts.add(".myide.io"); - -config.plugin("hot").use(webpack.HotModuleReplacementPlugin); - -module.exports = config.toConfig(); diff --git a/Timeline/ClientApp/webpack.config.prod.js b/Timeline/ClientApp/webpack.config.prod.js deleted file mode 100644 index 188cb940..00000000 --- a/Timeline/ClientApp/webpack.config.prod.js +++ /dev/null @@ -1,53 +0,0 @@ -const path = require("path"); -const { CleanWebpackPlugin } = require("clean-webpack-plugin"); -const CopyPlugin = require("copy-webpack-plugin"); -const WorkboxPlugin = require("workbox-webpack-plugin"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); - -const config = require("./webpack.common"); - -config.mode("production"); - -config - .entry("index") - .add(path.resolve(__dirname, "src/app/service-worker.tsx")); - -config.module - .rule("css") - .use("mini-css-extract") - .before("css") - .loader(MiniCssExtractPlugin.loader) - .end(); - -config.module - .rule("sass") - .use("mini-css-extract") - .before("css") - .loader(MiniCssExtractPlugin.loader) - .end(); - -config.devtool("source-map"); - -config.plugin("mini-css-extract").use(MiniCssExtractPlugin); - -config.plugin("clean").use(CleanWebpackPlugin); - -config.plugin("copy").use(CopyPlugin, [ - { - patterns: [ - { - from: path.resolve(__dirname, "public/"), - to: path.resolve(__dirname, "dist/"), - }, - ], - }, -]); - -config.plugin("workbox").use(WorkboxPlugin.InjectManifest, [ - { - swSrc: path.resolve(__dirname, "src/sw/sw.ts"), - maximumFileSizeToCacheInBytes: 15000000, - }, -]); - -module.exports = config.toConfig(); diff --git a/Timeline/Configs/ApplicationConfiguration.cs b/Timeline/Configs/ApplicationConfiguration.cs deleted file mode 100644 index df281adb..00000000 --- a/Timeline/Configs/ApplicationConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Timeline.Configs -{ - public static class ApplicationConfiguration - { - public const string WorkDirKey = "WorkDir"; - public const string DefaultWorkDir = "/timeline"; - public const string DatabaseFileName = "timeline.db"; - public const string DatabaseBackupDirectoryName = "backup"; - public const string DisableFrontEndKey = "DisableFrontEnd"; - public const string UseMockFrontEndKey = "UseMockFrontEnd"; - public const string UseProxyFrontEndKey = "UseProxyFrontEnd"; - } -} diff --git a/Timeline/Configs/JwtConfiguration.cs b/Timeline/Configs/JwtConfiguration.cs deleted file mode 100644 index af8052de..00000000 --- a/Timeline/Configs/JwtConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Timeline.Configs -{ - public class JwtConfiguration - { - public string Issuer { get; set; } = default!; - public string Audience { get; set; } = default!; - - /// - /// Set the default value of expire offset of jwt token. - /// Unit is second. Default is 3600 * 24 seconds, aka 1 day. - /// - public long DefaultExpireOffset { get; set; } = 3600 * 24; - } -} diff --git a/Timeline/Controllers/ControllerAuthExtensions.cs b/Timeline/Controllers/ControllerAuthExtensions.cs deleted file mode 100644 index 00a65454..00000000 --- a/Timeline/Controllers/ControllerAuthExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Security.Claims; -using Timeline.Auth; -using static Timeline.Resources.Controllers.ControllerAuthExtensions; - -namespace Timeline.Controllers -{ - public static class ControllerAuthExtensions - { - public static bool IsAdministrator(this ControllerBase controller) - { - return controller.User != null && controller.User.IsAdministrator(); - } - - public static long GetUserId(this ControllerBase controller) - { - var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier); - if (claim == null) - throw new InvalidOperationException(ExceptionNoUserIdentifierClaim); - - if (long.TryParse(claim.Value, out var value)) - return value; - - throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat); - } - - public static long? GetOptionalUserId(this ControllerBase controller) - { - var claim = controller.User.FindFirst(ClaimTypes.NameIdentifier); - if (claim == null) - return null; - - if (long.TryParse(claim.Value, out var value)) - return value; - - throw new InvalidOperationException(ExceptionUserIdentifierClaimBadFormat); - } - } -} diff --git a/Timeline/Controllers/Testing/TestingAuthController.cs b/Timeline/Controllers/Testing/TestingAuthController.cs deleted file mode 100644 index 4d3b3ec7..00000000 --- a/Timeline/Controllers/Testing/TestingAuthController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Timeline.Auth; - -namespace Timeline.Controllers.Testing -{ - [Route("testing/auth")] - [ApiController] - public class TestingAuthController : Controller - { - [HttpGet("[action]")] - [Authorize] - public ActionResult Authorize() - { - return Ok(); - } - - [HttpGet("[action]")] - [UserAuthorize] - public new ActionResult User() - { - return Ok(); - } - - [HttpGet("[action]")] - [AdminAuthorize] - public ActionResult Admin() - { - return Ok(); - } - } -} diff --git a/Timeline/Controllers/TimelineController.cs b/Timeline/Controllers/TimelineController.cs deleted file mode 100644 index 9a3147ea..00000000 --- a/Timeline/Controllers/TimelineController.cs +++ /dev/null @@ -1,491 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Timeline.Filters; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Models.Validation; -using Timeline.Services; -using Timeline.Services.Exceptions; - -namespace Timeline.Controllers -{ - /// - /// Operations about timeline. - /// - [ApiController] - [CatchTimelineNotExistException] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class TimelineController : Controller - { - private readonly ILogger _logger; - - private readonly IUserService _userService; - private readonly ITimelineService _service; - - private readonly IMapper _mapper; - - /// - /// - /// - public TimelineController(ILogger logger, IUserService userService, ITimelineService service, IMapper mapper) - { - _logger = logger; - _userService = userService; - _service = service; - _mapper = mapper; - } - - /// - /// List all timelines. - /// - /// A username. If set, only timelines related to the user will return. - /// Specify the relation type, may be 'own' or 'join'. If not set, both type will return. - /// "Private" or "Register" or "Public". If set, only timelines whose visibility is specified one will return. - /// The timeline list. - [HttpGet("timelines")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task>> TimelineList([FromQuery][Username] string? relate, [FromQuery][RegularExpression("(own)|(join)")] string? relateType, [FromQuery] string? visibility) - { - List? visibilityFilter = null; - if (visibility != null) - { - visibilityFilter = new List(); - var items = visibility.Split('|'); - foreach (var item in items) - { - if (item.Equals(nameof(TimelineVisibility.Private), StringComparison.OrdinalIgnoreCase)) - { - if (!visibilityFilter.Contains(TimelineVisibility.Private)) - visibilityFilter.Add(TimelineVisibility.Private); - } - else if (item.Equals(nameof(TimelineVisibility.Register), StringComparison.OrdinalIgnoreCase)) - { - if (!visibilityFilter.Contains(TimelineVisibility.Register)) - visibilityFilter.Add(TimelineVisibility.Register); - } - else if (item.Equals(nameof(TimelineVisibility.Public), StringComparison.OrdinalIgnoreCase)) - { - if (!visibilityFilter.Contains(TimelineVisibility.Public)) - visibilityFilter.Add(TimelineVisibility.Public); - } - else - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_QueryVisibilityUnknown, item)); - } - } - } - - TimelineUserRelationship? relationship = null; - if (relate != null) - { - try - { - var relatedUserId = await _userService.GetUserIdByUsername(relate); - - relationship = new TimelineUserRelationship(relateType switch - { - "own" => TimelineUserRelationshipType.Own, - "join" => TimelineUserRelationshipType.Join, - _ => TimelineUserRelationshipType.Default - }, relatedUserId); - } - catch (UserNotExistException) - { - return BadRequest(ErrorResponse.TimelineController.QueryRelateNotExist()); - } - } - - var timelines = await _service.GetTimelines(relationship, visibilityFilter); - var result = _mapper.Map>(timelines); - return result; - } - - /// - /// Get info of a timeline. - /// - /// The timeline name. - /// A unique id. If specified and if-modified-since is also specified, the timeline info will return when unique id is not the specified one even if it is not modified. - /// Same effect as If-Modified-Since header and take precedence than it. - /// If specified, will return 304 if not modified. - /// The timeline info. - [HttpGet("timelines/{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status304NotModified)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> TimelineGet([FromRoute][GeneralTimelineName] string name, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince) - { - DateTime? ifModifiedSince = null; - if (queryIfModifiedSince.HasValue) - { - ifModifiedSince = queryIfModifiedSince.Value; - } - else if (headerIfModifiedSince != null) - { - ifModifiedSince = headerIfModifiedSince.Value; - } - - bool returnNotModified = false; - - if (ifModifiedSince.HasValue) - { - var lastModified = await _service.GetTimelineLastModifiedTime(name); - if (lastModified < ifModifiedSince.Value) - { - if (checkUniqueId != null) - { - var uniqueId = await _service.GetTimelineUniqueId(name); - if (uniqueId == checkUniqueId) - { - returnNotModified = true; - } - } - else - { - returnNotModified = true; - } - } - } - - if (returnNotModified) - { - return StatusCode(StatusCodes.Status304NotModified); - } - else - { - var timeline = await _service.GetTimeline(name); - var result = _mapper.Map(timeline); - return result; - } - } - - /// - /// Get posts of a timeline. - /// - /// The name of the timeline. - /// If set, only posts modified since the time will return. - /// If set to true, deleted post will also return. - /// The post list. - [HttpGet("timelines/{name}/posts")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) - { - if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId())) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - List posts = await _service.GetPosts(name, modifiedSince, includeDeleted ?? false); - - var result = _mapper.Map>(posts); - return result; - } - - /// - /// Get the data of a post. Usually a image post. - /// - /// Timeline name. - /// The id of the post. - /// If-None-Match header. - /// The data. - [HttpGet("timelines/{name}/posts/{id}/data")] - [Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")] - [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) - { - _ = ifNoneMatch; - if (!this.IsAdministrator() && !await _service.HasReadPermission(name, this.GetOptionalUserId())) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - return await DataCacheHelper.GenerateActionResult(this, () => _service.GetPostDataETag(name, id), async () => - { - var data = await _service.GetPostData(name, id); - return data; - }); - } - catch (TimelinePostNotExistException) - { - return NotFound(ErrorResponse.TimelineController.PostNotExist()); - } - catch (TimelinePostNoDataException) - { - return BadRequest(ErrorResponse.TimelineController.PostNoData()); - } - } - - /// - /// Create a new post. - /// - /// Timeline name. - /// - /// Info of new post. - [HttpPost("timelines/{name}/posts")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePostCreateRequest body) - { - var id = this.GetUserId(); - if (!this.IsAdministrator() && !await _service.IsMemberOf(name, id)) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - var content = body.Content; - - TimelinePost post; - - if (content.Type == TimelinePostContentTypes.Text) - { - var text = content.Text; - if (text == null) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); - } - post = await _service.CreateTextPost(name, id, text, body.Time); - } - else if (content.Type == TimelinePostContentTypes.Image) - { - var base64Data = content.Data; - if (base64Data == null) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataRequired)); - } - byte[] data; - try - { - data = Convert.FromBase64String(base64Data); - } - catch (FormatException) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotBase64)); - } - - try - { - post = await _service.CreateImagePost(name, id, data, body.Time); - } - catch (ImageException) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotImage)); - } - } - else - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType)); - } - - var result = _mapper.Map(post); - return result; - } - - /// - /// Delete a post. - /// - /// Timeline name. - /// Post id. - /// Info of deletion. - [HttpDelete("timelines/{name}/posts/{id}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id) - { - if (!this.IsAdministrator() && !await _service.HasPostModifyPermission(name, id, this.GetUserId())) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - try - { - await _service.DeletePost(name, id); - return CommonDeleteResponse.Delete(); - } - catch (TimelinePostNotExistException) - { - return CommonDeleteResponse.NotExist(); - } - } - - /// - /// Change properties of a timeline. - /// - /// Timeline name. - /// - /// The new info. - [HttpPatch("timelines/{name}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePatchRequest body) - { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - await _service.ChangeProperty(name, _mapper.Map(body)); - var timeline = await _service.GetTimeline(name); - var result = _mapper.Map(timeline); - return result; - } - - /// - /// Add a member to timeline. - /// - /// Timeline name. - /// The new member's username. - [HttpPut("timelines/{name}/members/{member}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) - { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - await _service.ChangeMember(name, new List { member }, null); - return Ok(); - } - catch (UserNotExistException) - { - return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist()); - } - } - - /// - /// Remove a member from timeline. - /// - /// Timeline name. - /// The member's username. - [HttpDelete("timelines/{name}/members/{member}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member) - { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - await _service.ChangeMember(name, null, new List { member }); - return Ok(CommonDeleteResponse.Delete()); - } - catch (UserNotExistException) - { - return Ok(CommonDeleteResponse.NotExist()); - } - } - - /// - /// Create a timeline. - /// - /// - /// Info of new timeline. - [HttpPost("timelines")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> TimelineCreate([FromBody] TimelineCreateRequest body) - { - var userId = this.GetUserId(); - - try - { - var timeline = await _service.CreateTimeline(body.Name, userId); - var result = _mapper.Map(timeline); - return result; - } - catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline) - { - return BadRequest(ErrorResponse.TimelineController.NameConflict()); - } - } - - /// - /// Delete a timeline. - /// - /// Timeline name. - /// Info of deletion. - [HttpDelete("timelines/{name}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelineDelete([FromRoute][TimelineName] string name) - { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(name, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - await _service.DeleteTimeline(name); - return CommonDeleteResponse.Delete(); - } - catch (TimelineNotExistException) - { - return CommonDeleteResponse.NotExist(); - } - } - - [HttpPost("timelineop/changename")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelineOpChangeName([FromBody] TimelineChangeNameRequest body) - { - if (!this.IsAdministrator() && !(await _service.HasManagePermission(body.OldName, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - var timeline = await _service.ChangeTimelineName(body.OldName, body.NewName); - return Ok(_mapper.Map(timeline)); - } - catch (EntityAlreadyExistException) - { - return BadRequest(ErrorResponse.TimelineController.NameConflict()); - } - } - } -} diff --git a/Timeline/Controllers/TokenController.cs b/Timeline/Controllers/TokenController.cs deleted file mode 100644 index 8f2ca600..00000000 --- a/Timeline/Controllers/TokenController.cs +++ /dev/null @@ -1,142 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Globalization; -using System.Threading.Tasks; -using Timeline.Helpers; -using Timeline.Models.Http; -using Timeline.Services; -using Timeline.Services.Exceptions; -using static Timeline.Resources.Controllers.TokenController; - -namespace Timeline.Controllers -{ - /// - /// Operation about tokens. - /// - [Route("token")] - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class TokenController : Controller - { - private readonly IUserTokenManager _userTokenManager; - private readonly ILogger _logger; - private readonly IClock _clock; - - private readonly IMapper _mapper; - - /// - public TokenController(IUserTokenManager userTokenManager, ILogger logger, IClock clock, IMapper mapper) - { - _userTokenManager = userTokenManager; - _logger = logger; - _clock = clock; - _mapper = mapper; - } - - /// - /// Create a new token for a user. - /// - /// Result of token creation. - [HttpPost("create")] - [AllowAnonymous] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> Create([FromBody] CreateTokenRequest request) - { - void LogFailure(string reason, Exception? e = null) - { - _logger.LogInformation(e, Log.Format(LogCreateFailure, - ("Reason", reason), - ("Username", request.Username), - ("Password", request.Password), - ("Expire (in days)", request.Expire) - )); - } - - try - { - DateTime? expireTime = null; - if (request.Expire != null) - expireTime = _clock.GetCurrentTime().AddDays(request.Expire.Value); - - var result = await _userTokenManager.CreateToken(request.Username, request.Password, expireTime); - - _logger.LogInformation(Log.Format(LogCreateSuccess, - ("Username", request.Username), - ("Expire At", expireTime?.ToString(CultureInfo.CurrentCulture.DateTimeFormat) ?? "default") - )); - return Ok(new CreateTokenResponse - { - Token = result.Token, - User = _mapper.Map(result.User) - }); - } - catch (UserNotExistException e) - { - LogFailure(LogUserNotExist, e); - return BadRequest(ErrorResponse.TokenController.Create_BadCredential()); - } - catch (BadPasswordException e) - { - LogFailure(LogBadPassword, e); - return BadRequest(ErrorResponse.TokenController.Create_BadCredential()); - } - } - - /// - /// Verify a token. - /// - /// Result of token verification. - [HttpPost("verify")] - [AllowAnonymous] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> Verify([FromBody] VerifyTokenRequest request) - { - void LogFailure(string reason, Exception? e = null, params (string, object?)[] otherProperties) - { - var properties = new (string, object?)[2 + otherProperties.Length]; - properties[0] = ("Reason", reason); - properties[1] = ("Token", request.Token); - otherProperties.CopyTo(properties, 2); - _logger.LogInformation(e, Log.Format(LogVerifyFailure, properties)); - } - - try - { - var result = await _userTokenManager.VerifyToken(request.Token); - _logger.LogInformation(Log.Format(LogVerifySuccess, - ("Username", result.Username), ("Token", request.Token))); - return Ok(new VerifyTokenResponse - { - User = _mapper.Map(result) - }); - } - catch (UserTokenTimeExpireException e) - { - LogFailure(LogVerifyExpire, e, ("Expire Time", e.ExpireTime), ("Verify Time", e.VerifyTime)); - return BadRequest(ErrorResponse.TokenController.Verify_TimeExpired()); - } - catch (UserTokenBadVersionException e) - { - LogFailure(LogVerifyOldVersion, e, ("Token Version", e.TokenVersion), ("Required Version", e.RequiredVersion)); - return BadRequest(ErrorResponse.TokenController.Verify_OldVersion()); - - } - catch (UserTokenBadFormatException e) - { - LogFailure(LogVerifyBadFormat, e); - return BadRequest(ErrorResponse.TokenController.Verify_BadFormat()); - } - catch (UserNotExistException e) - { - LogFailure(LogVerifyUserNotExist, e); - return BadRequest(ErrorResponse.TokenController.Verify_UserNotExist()); - } - } - } -} diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs deleted file mode 100644 index bc4afa30..00000000 --- a/Timeline/Controllers/UserAvatarController.cs +++ /dev/null @@ -1,174 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using System; -using System.Threading.Tasks; -using Timeline.Auth; -using Timeline.Filters; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Models.Validation; -using Timeline.Services; -using Timeline.Services.Exceptions; -using static Timeline.Resources.Controllers.UserAvatarController; - -namespace Timeline.Controllers -{ - /// - /// Operations about user avatar. - /// - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class UserAvatarController : Controller - { - private readonly ILogger _logger; - - private readonly IUserService _userService; - private readonly IUserAvatarService _service; - - /// - /// - /// - public UserAvatarController(ILogger logger, IUserService userService, IUserAvatarService service) - { - _logger = logger; - _userService = userService; - _service = service; - } - - /// - /// Get avatar of a user. - /// - /// Username of the user to get avatar of. - /// If-None-Match header. - /// Avatar data. - [HttpGet("users/{username}/avatar")] - [Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")] - [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task Get([FromRoute][Username] string username, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) - { - _ = ifNoneMatch; - long id; - try - { - id = await _userService.GetUserIdByUsername(username); - } - catch (UserNotExistException e) - { - _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username))); - return NotFound(ErrorResponse.UserCommon.NotExist()); - } - - return await DataCacheHelper.GenerateActionResult(this, () => _service.GetAvatarETag(id), async () => - { - var avatar = await _service.GetAvatar(id); - return avatar.ToCacheableData(); - }); - } - - /// - /// Set avatar of a user. You have to be administrator to change other's. - /// - /// Username of the user to set avatar of. - /// The avatar data. - [HttpPut("users/{username}/avatar")] - [Authorize] - [Consumes("image/png", "image/jpeg", "image/gif", "image/webp")] - [MaxContentLength(1000 * 1000 * 10)] - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task Put([FromRoute][Username] string username, [FromBody] ByteData body) - { - if (!User.IsAdministrator() && User.Identity.Name != username) - { - _logger.LogInformation(Log.Format(LogPutForbid, - ("Operator Username", User.Identity.Name), ("Username To Put Avatar", username))); - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - long id; - try - { - id = await _userService.GetUserIdByUsername(username); - } - catch (UserNotExistException e) - { - _logger.LogInformation(e, Log.Format(LogPutUserNotExist, ("Username", username))); - return BadRequest(ErrorResponse.UserCommon.NotExist()); - } - - try - { - var etag = await _service.SetAvatar(id, new Avatar - { - Data = body.Data, - Type = body.ContentType - }); - - _logger.LogInformation(Log.Format(LogPutSuccess, - ("Username", username), ("Mime Type", Request.ContentType))); - - Response.Headers.Append("ETag", new EntityTagHeaderValue($"\"{etag}\"").ToString()); - - return Ok(); - } - catch (ImageException e) - { - _logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username))); - return BadRequest(e.Error switch - { - ImageException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(), - ImageException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(), - ImageException.ErrorReason.NotSquare => ErrorResponse.UserAvatar.BadFormat_BadSize(), - _ => - throw new Exception(ExceptionUnknownAvatarFormatError) - }); - } - } - - /// - /// Reset the avatar to the default one. You have to be administrator to reset other's. - /// - /// Username of the user. - /// Succeeded to reset. - /// Error code is 10010001 if user does not exist. - /// You have not logged in. - /// You are not administrator. - [HttpDelete("users/{username}/avatar")] - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [Authorize] - public async Task Delete([FromRoute][Username] string username) - { - if (!User.IsAdministrator() && User.Identity.Name != username) - { - _logger.LogInformation(Log.Format(LogDeleteForbid, - ("Operator Username", User.Identity.Name), ("Username To Delete Avatar", username))); - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - long id; - try - { - id = await _userService.GetUserIdByUsername(username); - } - catch (UserNotExistException e) - { - _logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username))); - return BadRequest(ErrorResponse.UserCommon.NotExist()); - } - - await _service.SetAvatar(id, null); - return Ok(); - } - } -} diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs deleted file mode 100644 index 02c09aab..00000000 --- a/Timeline/Controllers/UserController.cs +++ /dev/null @@ -1,195 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Auth; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Models.Http; -using Timeline.Models.Validation; -using Timeline.Services; -using Timeline.Services.Exceptions; -using static Timeline.Resources.Controllers.UserController; -using static Timeline.Resources.Messages; - -namespace Timeline.Controllers -{ - /// - /// Operations about users. - /// - [ApiController] - [ProducesErrorResponseType(typeof(CommonResponse))] - public class UserController : Controller - { - private readonly ILogger _logger; - private readonly IUserService _userService; - private readonly IUserDeleteService _userDeleteService; - private readonly IMapper _mapper; - - /// - public UserController(ILogger logger, IUserService userService, IUserDeleteService userDeleteService, IMapper mapper) - { - _logger = logger; - _userService = userService; - _userDeleteService = userDeleteService; - _mapper = mapper; - } - - private UserInfo ConvertToUserInfo(User user) => _mapper.Map(user); - - /// - /// Get all users. - /// - /// All user list. - [HttpGet("users")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> List() - { - var users = await _userService.GetUsers(); - var result = users.Select(u => ConvertToUserInfo(u)).ToArray(); - return Ok(result); - } - - /// - /// Get a user's info. - /// - /// Username of the user. - /// User info. - [HttpGet("users/{username}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Get([FromRoute][Username] string username) - { - try - { - var user = await _userService.GetUserByUsername(username); - return Ok(ConvertToUserInfo(user)); - } - catch (UserNotExistException e) - { - _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username))); - return NotFound(ErrorResponse.UserCommon.NotExist()); - } - } - - /// - /// Change a user's property. - /// - /// - /// Username of the user to change. - /// The new user info. - [HttpPatch("users/{username}"), Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Patch([FromBody] UserPatchRequest body, [FromRoute][Username] string username) - { - if (this.IsAdministrator()) - { - try - { - var user = await _userService.ModifyUser(username, _mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); - } - catch (UserNotExistException e) - { - _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username))); - return NotFound(ErrorResponse.UserCommon.NotExist()); - } - catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) - { - return BadRequest(ErrorResponse.UserController.UsernameConflict()); - } - } - else - { - if (User.Identity.Name != username) - return StatusCode(StatusCodes.Status403Forbidden, - ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf)); - - if (body.Username != null) - return StatusCode(StatusCodes.Status403Forbidden, - ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Username)); - - if (body.Password != null) - return StatusCode(StatusCodes.Status403Forbidden, - ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password)); - - if (body.Administrator != null) - return StatusCode(StatusCodes.Status403Forbidden, - ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Administrator)); - - var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); - } - } - - /// - /// Delete a user and all his related data. You have to be administrator. - /// - /// Username of the user to delete. - /// Info of deletion. - [HttpDelete("users/{username}"), AdminAuthorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> Delete([FromRoute][Username] string username) - { - var delete = await _userDeleteService.DeleteUser(username); - if (delete) - return Ok(CommonDeleteResponse.Delete()); - else - return Ok(CommonDeleteResponse.NotExist()); - } - - /// - /// Create a new user. You have to be administrator. - /// - /// The new user's info. - [HttpPost("userop/createuser"), AdminAuthorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> CreateUser([FromBody] CreateUserRequest body) - { - try - { - var user = await _userService.CreateUser(_mapper.Map(body)); - return Ok(ConvertToUserInfo(user)); - } - catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User) - { - return BadRequest(ErrorResponse.UserController.UsernameConflict()); - } - } - - /// - /// Change password with old password. - /// - [HttpPost("userop/changepassword"), Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task ChangePassword([FromBody] ChangePasswordRequest request) - { - try - { - await _userService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword); - return Ok(); - } - catch (BadPasswordException e) - { - _logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword, - ("Username", User.Identity.Name), ("Old Password", request.OldPassword))); - return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword()); - } - // User can't be non-existent or the token is bad. - } - } -} diff --git a/Timeline/Entities/DataEntity.cs b/Timeline/Entities/DataEntity.cs deleted file mode 100644 index b21e2dbf..00000000 --- a/Timeline/Entities/DataEntity.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - [Table("data")] - public class DataEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("tag"), Required] - public string Tag { get; set; } = default!; - - [Column("data"), Required] -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; set; } = default!; -#pragma warning restore CA1819 // Properties should not return arrays - - [Column("ref"), Required] - public int Ref { get; set; } - } -} diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs deleted file mode 100644 index ecadd703..00000000 --- a/Timeline/Entities/DatabaseContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Timeline.Entities -{ - public class DatabaseContext : DbContext - { - public DatabaseContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity().Property(e => e.Version).HasDefaultValue(0); - modelBuilder.Entity().HasIndex(e => e.Username).IsUnique(); - modelBuilder.Entity().Property(e => e.UniqueId).HasDefaultValueSql("lower(hex(randomblob(16)))"); - modelBuilder.Entity().Property(e => e.UsernameChangeTime).HasDefaultValueSql("datetime('now', 'utc')"); - modelBuilder.Entity().Property(e => e.CreateTime).HasDefaultValueSql("datetime('now', 'utc')"); - modelBuilder.Entity().Property(e => e.LastModified).HasDefaultValueSql("datetime('now', 'utc')"); - modelBuilder.Entity().HasIndex(e => e.Tag).IsUnique(); - modelBuilder.Entity().Property(e => e.UniqueId).HasDefaultValueSql("lower(hex(randomblob(16)))"); - - modelBuilder.ApplyUtcDateTimeConverter(); - } - - public DbSet Users { get; set; } = default!; - public DbSet UserAvatars { get; set; } = default!; - public DbSet Timelines { get; set; } = default!; - public DbSet TimelinePosts { get; set; } = default!; - public DbSet TimelineMembers { get; set; } = default!; - public DbSet JwtToken { get; set; } = default!; - public DbSet Data { get; set; } = default!; - } -} diff --git a/Timeline/Entities/JwtTokenEntity.cs b/Timeline/Entities/JwtTokenEntity.cs deleted file mode 100644 index 40cb230a..00000000 --- a/Timeline/Entities/JwtTokenEntity.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - [Table("jwt_token")] - public class JwtTokenEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Required, Column("key")] -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Key { get; set; } = default!; -#pragma warning restore CA1819 // Properties should not return arrays - } -} diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs deleted file mode 100644 index 3e592673..00000000 --- a/Timeline/Entities/TimelineEntity.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Timeline.Models; - -namespace Timeline.Entities -{ -#pragma warning disable CA2227 // Collection properties should be read only - // TODO: Create index for this table. - [Table("timelines")] - public class TimelineEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("unique_id"), Required] - public string UniqueId { get; set; } = default!; - - /// - /// If null, then this timeline is a personal timeline. - /// - [Column("name")] - public string? Name { get; set; } - - [Column("title")] - public string? Title { get; set; } - - [Column("name_last_modified")] - public DateTime NameLastModified { get; set; } - - [Column("description")] - public string? Description { get; set; } - - [Column("owner")] - public long OwnerId { get; set; } - - [ForeignKey(nameof(OwnerId))] - public UserEntity Owner { get; set; } = default!; - - [Column("visibility")] - public TimelineVisibility Visibility { get; set; } - - [Column("create_time")] - public DateTime CreateTime { get; set; } - - [Column("last_modified")] - public DateTime LastModified { get; set; } - - [Column("current_post_local_id")] - public long CurrentPostLocalId { get; set; } - - public List Members { get; set; } = default!; - - public List Posts { get; set; } = default!; - } -#pragma warning restore CA2227 // Collection properties should be read only -} diff --git a/Timeline/Entities/TimelineMemberEntity.cs b/Timeline/Entities/TimelineMemberEntity.cs deleted file mode 100644 index e76f2099..00000000 --- a/Timeline/Entities/TimelineMemberEntity.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - [Table("timeline_members")] - public class TimelineMemberEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("user")] - public long UserId { get; set; } - - [ForeignKey(nameof(UserId))] - public UserEntity User { get; set; } = default!; - - [Column("timeline")] - public long TimelineId { get; set; } - - [ForeignKey(nameof(TimelineId))] - public TimelineEntity Timeline { get; set; } = default!; - } -} diff --git a/Timeline/Entities/TimelinePostEntity.cs b/Timeline/Entities/TimelinePostEntity.cs deleted file mode 100644 index 07367fba..00000000 --- a/Timeline/Entities/TimelinePostEntity.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - [Table("timeline_posts")] - public class TimelinePostEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("local_id")] - public long LocalId { get; set; } - - [Column("timeline")] - public long TimelineId { get; set; } - - [ForeignKey(nameof(TimelineId))] - public TimelineEntity Timeline { get; set; } = default!; - - [Column("author")] - public long? AuthorId { get; set; } - - [ForeignKey(nameof(AuthorId))] - public UserEntity? Author { get; set; } = default!; - - [Column("content_type"), Required] - public string ContentType { get; set; } = default!; - - [Column("content")] - public string? Content { get; set; } - - [Column("extra_content")] - public string? ExtraContent { get; set; } - - [Column("time")] - public DateTime Time { get; set; } - - [Column("last_updated")] - public DateTime LastUpdated { get; set; } - } -} diff --git a/Timeline/Entities/UserAvatarEntity.cs b/Timeline/Entities/UserAvatarEntity.cs deleted file mode 100644 index 3c2720f7..00000000 --- a/Timeline/Entities/UserAvatarEntity.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "This is data base entity.")] - [Table("user_avatars")] - public class UserAvatarEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("data_tag")] - public string? DataTag { get; set; } - - [Column("type")] - public string? Type { get; set; } - - [Column("last_modified"), Required] - public DateTime LastModified { get; set; } - - [Column("user"), Required] - public long UserId { get; set; } - - [ForeignKey(nameof(UserId))] - public UserEntity User { get; set; } = default!; - } -} diff --git a/Timeline/Entities/UserEntity.cs b/Timeline/Entities/UserEntity.cs deleted file mode 100644 index 0cfaa335..00000000 --- a/Timeline/Entities/UserEntity.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Timeline.Entities -{ - public static class UserRoles - { - public const string Admin = "admin"; - public const string User = "user"; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is an entity class.")] - [Table("users")] - public class UserEntity - { - [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - [Column("unique_id"), Required] - public string UniqueId { get; set; } = default!; - - [Column("username"), Required] - public string Username { get; set; } = default!; - - [Column("username_change_time")] - public DateTime UsernameChangeTime { get; set; } - - [Column("password"), Required] - public string Password { get; set; } = default!; - - [Column("roles"), Required] - public string Roles { get; set; } = default!; - - [Column("version"), Required] - public long Version { get; set; } - - [Column("nickname")] - public string? Nickname { get; set; } - - [Column("create_time")] - public DateTime CreateTime { get; set; } - - [Column("last_modified")] - public DateTime LastModified { get; set; } - - public UserAvatarEntity? Avatar { get; set; } - - public List Timelines { get; set; } = default!; - - public List TimelinePosts { get; set; } = default!; - - public List TimelinesJoined { get; set; } = default!; - } -} diff --git a/Timeline/Entities/UtcDateAnnotation.cs b/Timeline/Entities/UtcDateAnnotation.cs deleted file mode 100644 index 6600e701..00000000 --- a/Timeline/Entities/UtcDateAnnotation.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System; - -namespace Timeline.Entities -{ - // Copied from https://github.com/dotnet/efcore/issues/4711#issuecomment-589842988 - public static class UtcDateAnnotation - { - private const string IsUtcAnnotation = "IsUtc"; - private static readonly ValueConverter UtcConverter = - new ValueConverter(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); - - public static PropertyBuilder IsUtc(this PropertyBuilder builder, bool isUtc = true) => - builder.HasAnnotation(IsUtcAnnotation, isUtc); - - public static bool IsUtc(this IMutableProperty property) => - ((bool?)property.FindAnnotation(IsUtcAnnotation)?.Value) ?? true; - - /// - /// Make sure this is called after configuring all your entities. - /// - public static void ApplyUtcDateTimeConverter(this ModelBuilder builder) - { - foreach (var entityType in builder.Model.GetEntityTypes()) - { - foreach (var property in entityType.GetProperties()) - { - if (!property.IsUtc()) - { - continue; - } - - if (property.ClrType == typeof(DateTime)) - { - property.SetValueConverter(UtcConverter); - } - } - } - } - } -} diff --git a/Timeline/Filters/Header.cs b/Timeline/Filters/Header.cs deleted file mode 100644 index cc5ddd9f..00000000 --- a/Timeline/Filters/Header.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Timeline.Models.Http; - -namespace Timeline.Filters -{ - /// - /// Restrict max content length. - /// - public class MaxContentLengthFilter : IResourceFilter - { - /// - /// - /// - /// Max length. - public MaxContentLengthFilter(long maxByteLength) - { - MaxByteLength = maxByteLength; - } - - /// - /// Max length. - /// - public long MaxByteLength { get; set; } - - /// - public void OnResourceExecuted(ResourceExecutedContext context) - { - } - - /// - public void OnResourceExecuting(ResourceExecutingContext context) - { - var contentLength = context.HttpContext.Request.ContentLength; - if (contentLength != null && contentLength > MaxByteLength) - { - context.Result = new BadRequestObjectResult(ErrorResponse.Common.Content.TooBig(MaxByteLength + "B")); - } - } - } - - /// - /// Restrict max content length. - /// - public class MaxContentLengthAttribute : TypeFilterAttribute - { - /// - /// - /// - /// Max length. - public MaxContentLengthAttribute(long maxByteLength) - : base(typeof(MaxContentLengthFilter)) - { - MaxByteLength = maxByteLength; - Arguments = new object[] { maxByteLength }; - } - - /// - /// Max length. - /// - public long MaxByteLength { get; } - } -} diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs deleted file mode 100644 index 6a730ee7..00000000 --- a/Timeline/Filters/Timeline.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Timeline.Models.Http; -using Timeline.Services.Exceptions; - -namespace Timeline.Filters -{ - public class CatchTimelineNotExistExceptionAttribute : ExceptionFilterAttribute - { - public override void OnException(ExceptionContext context) - { - if (context.Exception is TimelineNotExistException e) - { - if (e.InnerException is UserNotExistException) - { - if (HttpMethods.IsGet(context.HttpContext.Request.Method)) - context.Result = new NotFoundObjectResult(ErrorResponse.UserCommon.NotExist()); - else - context.Result = new BadRequestObjectResult(ErrorResponse.UserCommon.NotExist()); - } - else - { - if (HttpMethods.IsGet(context.HttpContext.Request.Method)) - context.Result = new NotFoundObjectResult(ErrorResponse.TimelineController.NotExist()); - else - context.Result = new BadRequestObjectResult(ErrorResponse.TimelineController.NotExist()); - } - } - } - } -} diff --git a/Timeline/Formatters/BytesInputFormatter.cs b/Timeline/Formatters/BytesInputFormatter.cs deleted file mode 100644 index ac6537c9..00000000 --- a/Timeline/Formatters/BytesInputFormatter.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using System; -using System.Threading.Tasks; -using Timeline.Models; - -namespace Timeline.Formatters -{ - /// - /// Formatter that reads body as bytes. - /// - public class BytesInputFormatter : InputFormatter - { - /// - /// - /// - public BytesInputFormatter() - { - SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png")); - SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg")); - SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/gif")); - SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/webp")); - } - - /// - public override bool CanRead(InputFormatterContext context) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - - if (context.ModelType == typeof(ByteData)) - return true; - - return false; - } - - /// - public override async Task ReadRequestBodyAsync(InputFormatterContext context) - { - var request = context.HttpContext.Request; - var contentLength = request.ContentLength; - - var logger = context.HttpContext.RequestServices.GetRequiredService>(); - - if (contentLength == null) - { - logger.LogInformation("Failed to read body as bytes. Content-Length is not set."); - return await InputFormatterResult.FailureAsync(); - } - - if (contentLength == 0) - { - logger.LogInformation("Failed to read body as bytes. Content-Length is 0."); - return await InputFormatterResult.FailureAsync(); - } - - var bodyStream = request.Body; - - var data = new byte[contentLength.Value]; - var bytesRead = await bodyStream.ReadAsync(data); - - if (bytesRead != contentLength) - { - logger.LogInformation("Failed to read body as bytes. Actual length of body is smaller than Content-Length."); - return await InputFormatterResult.FailureAsync(); - } - - var extraByte = new byte[1]; - if (await bodyStream.ReadAsync(extraByte) != 0) - { - logger.LogInformation("Failed to read body as bytes. Actual length of body is greater than Content-Length."); - return await InputFormatterResult.FailureAsync(); - } - - return await InputFormatterResult.SuccessAsync(new ByteData(data, request.ContentType)); - } - } -} diff --git a/Timeline/Formatters/StringInputFormatter.cs b/Timeline/Formatters/StringInputFormatter.cs deleted file mode 100644 index b1924268..00000000 --- a/Timeline/Formatters/StringInputFormatter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Net.Http.Headers; -using System.IO; -using System.Net.Mime; -using System.Text; -using System.Threading.Tasks; - -namespace Timeline.Formatters -{ - public class StringInputFormatter : TextInputFormatter - { - public StringInputFormatter() - { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Text.Plain)); - SupportedEncodings.Add(Encoding.UTF8); - } - - public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding) - { - var request = context.HttpContext.Request; - using var reader = new StreamReader(request.Body, effectiveEncoding); - var stringContent = await reader.ReadToEndAsync(); - return await InputFormatterResult.SuccessAsync(stringContent); - } - } -} diff --git a/Timeline/GlobalSuppressions.cs b/Timeline/GlobalSuppressions.cs deleted file mode 100644 index 2b0da576..00000000 --- a/Timeline/GlobalSuppressions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "This is not a UI application.")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is not bad.")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "No need to check the null because it's ASP.Net's duty.", Scope = "namespaceanddescendants", Target = "Timeline.Controllers")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Migrations code are auto generated.", Scope = "namespaceanddescendants", Target = "Timeline.Migrations")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Generated error response identifiers.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Generated error response.", Scope = "type", Target = "Timeline.Models.Http.ErrorResponse")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Adundant")] diff --git a/Timeline/Helpers/DataCacheHelper.cs b/Timeline/Helpers/DataCacheHelper.cs deleted file mode 100644 index 1ad69708..00000000 --- a/Timeline/Helpers/DataCacheHelper.cs +++ /dev/null @@ -1,125 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Models.Http; -using static Timeline.Resources.Helper.DataCacheHelper; - -namespace Timeline.Helpers -{ - public interface ICacheableData - { - string Type { get; } -#pragma warning disable CA1819 // Properties should not return arrays - byte[] Data { get; } -#pragma warning restore CA1819 // Properties should not return arrays - DateTime? LastModified { get; } - } - - public class CacheableData : ICacheableData - { - public CacheableData(string type, byte[] data, DateTime? lastModified) - { - Type = type; - Data = data; - LastModified = lastModified; - } - - public string Type { get; set; } -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays - public DateTime? LastModified { get; set; } - } - - public interface ICacheableDataProvider - { - Task GetDataETag(); - Task GetData(); - } - - public class DelegateCacheableDataProvider : ICacheableDataProvider - { - private readonly Func> _getDataETagDelegate; - private readonly Func> _getDataDelegate; - - public DelegateCacheableDataProvider(Func> getDataETagDelegate, Func> getDataDelegate) - { - _getDataETagDelegate = getDataETagDelegate; - _getDataDelegate = getDataDelegate; - } - - public Task GetData() - { - return _getDataDelegate(); - } - - public Task GetDataETag() - { - return _getDataETagDelegate(); - } - } - - public static class DataCacheHelper - { - public static async Task GenerateActionResult(Controller controller, ICacheableDataProvider provider, TimeSpan? maxAge = null) - { - const string CacheControlHeaderKey = "Cache-Control"; - const string IfNonMatchHeaderKey = "If-None-Match"; - const string ETagHeaderKey = "ETag"; - - string GenerateCacheControlHeaderValue() - { - var cacheControlHeader = new CacheControlHeaderValue() - { - NoCache = true, - NoStore = false, - MaxAge = maxAge ?? TimeSpan.FromDays(14), - Private = true, - MustRevalidate = true - }; - return cacheControlHeader.ToString(); - } - - var loggerFactory = controller.HttpContext.RequestServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger(typeof(DataCacheHelper)); - - var eTagValue = await provider.GetDataETag(); - eTagValue = '"' + eTagValue + '"'; - var eTag = new EntityTagHeaderValue(eTagValue); - - - if (controller.Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value)) - { - if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList)) - { - logger.LogInformation(Log.Format(LogBadIfNoneMatch, ("Header Value", value))); - return controller.BadRequest(ErrorResponse.Common.Header.IfNonMatch_BadFormat()); - } - - if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null) - { - logger.LogInformation(LogResultNotModified); - controller.Response.Headers.Add(ETagHeaderKey, eTagValue); - controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); - - return controller.StatusCode(StatusCodes.Status304NotModified, null); - } - } - - var data = await provider.GetData(); - logger.LogInformation(LogResultData); - controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); - return controller.File(data.Data, data.Type, data.LastModified, eTag); - } - - public static Task GenerateActionResult(Controller controller, Func> getDataETagDelegate, Func> getDataDelegate, TimeSpan? maxAge = null) - { - return GenerateActionResult(controller, new DelegateCacheableDataProvider(getDataETagDelegate, getDataDelegate), maxAge); - } - } -} diff --git a/Timeline/Helpers/DateTimeExtensions.cs b/Timeline/Helpers/DateTimeExtensions.cs deleted file mode 100644 index 374f3bc9..00000000 --- a/Timeline/Helpers/DateTimeExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Timeline.Helpers -{ - public static class DateTimeExtensions - { - public static DateTime MyToUtc(this DateTime dateTime) - { - if (dateTime.Kind == DateTimeKind.Utc) return dateTime; - if (dateTime.Kind == DateTimeKind.Local) return dateTime.ToUniversalTime(); - return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); - } - } -} diff --git a/Timeline/Helpers/InvalidModelResponseFactory.cs b/Timeline/Helpers/InvalidModelResponseFactory.cs deleted file mode 100644 index 9b253e7d..00000000 --- a/Timeline/Helpers/InvalidModelResponseFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Text; -using Timeline.Models.Http; - -namespace Timeline.Helpers -{ - public static class InvalidModelResponseFactory - { - public static IActionResult Factory(ActionContext context) - { - var modelState = context.ModelState; - - var messageBuilder = new StringBuilder(); - foreach (var model in modelState) - foreach (var error in model.Value.Errors) - { - messageBuilder.Append(model.Key); - messageBuilder.Append(" : "); - messageBuilder.AppendLine(error.ErrorMessage); - } - - return new BadRequestObjectResult(ErrorResponse.Common.CustomMessage_InvalidModel(messageBuilder.ToString())); - } - } -} diff --git a/Timeline/Helpers/LanguageHelper.cs b/Timeline/Helpers/LanguageHelper.cs deleted file mode 100644 index b0156b8b..00000000 --- a/Timeline/Helpers/LanguageHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Linq; - -namespace Timeline.Helpers -{ - public static class LanguageHelper - { - public static bool AreSame(this bool firstBool, params bool[] otherBools) - { - return otherBools.All(b => b == firstBool); - } - } -} diff --git a/Timeline/Helpers/Log.cs b/Timeline/Helpers/Log.cs deleted file mode 100644 index af0b7e13..00000000 --- a/Timeline/Helpers/Log.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text; - -namespace Timeline.Helpers -{ - public static class Log - { - public static string Format(string summary, params (string, object?)[] properties) - { - var builder = new StringBuilder(); - builder.Append(summary); - foreach (var property in properties) - { - var (key, value) = property; - builder.AppendLine(); - builder.Append(key); - builder.Append(" : "); - builder.Append(value); - } - return builder.ToString(); - } - } -} diff --git a/Timeline/Migrations/20200105150407_Initialize.Designer.cs b/Timeline/Migrations/20200105150407_Initialize.Designer.cs deleted file mode 100644 index 99e4eaac..00000000 --- a/Timeline/Migrations/20200105150407_Initialize.Designer.cs +++ /dev/null @@ -1,266 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200105150407_Initialize")] - partial class Initialize - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.0"); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("EncryptedPassword") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("RoleString") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatar", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("ETag") - .HasColumnName("etag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserDetail", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_details"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.User", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.User", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.User", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatar", b => - { - b.HasOne("Timeline.Entities.User", null) - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatar", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserDetail", b => - { - b.HasOne("Timeline.Entities.User", null) - .WithOne("Detail") - .HasForeignKey("Timeline.Entities.UserDetail", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200105150407_Initialize.cs b/Timeline/Migrations/20200105150407_Initialize.cs deleted file mode 100644 index 4e12ef83..00000000 --- a/Timeline/Migrations/20200105150407_Initialize.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class Initialize : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "users", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - name = table.Column(nullable: false), - password = table.Column(nullable: false), - roles = table.Column(nullable: false), - version = table.Column(nullable: false, defaultValue: 0L) - .Annotation("Sqlite:Autoincrement", true) - }, - constraints: table => - { - table.PrimaryKey("PK_users", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "timelines", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - name = table.Column(nullable: true), - description = table.Column(nullable: true), - owner = table.Column(nullable: false), - visibility = table.Column(nullable: false), - create_time = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_timelines", x => x.id); - table.ForeignKey( - name: "FK_timelines_users_owner", - column: x => x.owner, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "user_avatars", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - data = table.Column(nullable: true), - type = table.Column(nullable: true), - etag = table.Column(nullable: true), - last_modified = table.Column(nullable: false), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_user_avatars", x => x.id); - table.ForeignKey( - name: "FK_user_avatars_users_UserId", - column: x => x.UserId, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "user_details", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - nickname = table.Column(nullable: true), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_user_details", x => x.id); - table.ForeignKey( - name: "FK_user_details_users_UserId", - column: x => x.UserId, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "timeline_members", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - user = table.Column(nullable: false), - timeline = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_timeline_members", x => x.id); - table.ForeignKey( - name: "FK_timeline_members_timelines_timeline", - column: x => x.timeline, - principalTable: "timelines", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_timeline_members_users_user", - column: x => x.user, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "timeline_posts", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - timeline = table.Column(nullable: false), - author = table.Column(nullable: false), - content = table.Column(nullable: true), - time = table.Column(nullable: false), - last_updated = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_timeline_posts", x => x.id); - table.ForeignKey( - name: "FK_timeline_posts_users_author", - column: x => x.author, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_timeline_posts_timelines_timeline", - column: x => x.timeline, - principalTable: "timelines", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_timeline_members_timeline", - table: "timeline_members", - column: "timeline"); - - migrationBuilder.CreateIndex( - name: "IX_timeline_members_user", - table: "timeline_members", - column: "user"); - - migrationBuilder.CreateIndex( - name: "IX_timeline_posts_author", - table: "timeline_posts", - column: "author"); - - migrationBuilder.CreateIndex( - name: "IX_timeline_posts_timeline", - table: "timeline_posts", - column: "timeline"); - - migrationBuilder.CreateIndex( - name: "IX_timelines_owner", - table: "timelines", - column: "owner"); - - migrationBuilder.CreateIndex( - name: "IX_user_avatars_UserId", - table: "user_avatars", - column: "UserId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_user_details_UserId", - table: "user_details", - column: "UserId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_users_name", - table: "users", - column: "name", - unique: true); - - // Add a init user. Username is "administrator". Password is "crupest". - migrationBuilder.InsertData("users", new string[] { "name", "password", "roles" }, - new object[] { "administrator", "AQAAAAEAACcQAAAAENsspZrk8Wo+UuMyg6QuWJsNvRg6gVu4K/TumVod3h9GVLX9zDVuQQds3o7V8QWJ2w==", "user,admin" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "timeline_members"); - - migrationBuilder.DropTable( - name: "timeline_posts"); - - migrationBuilder.DropTable( - name: "user_avatars"); - - migrationBuilder.DropTable( - name: "user_details"); - - migrationBuilder.DropTable( - name: "timelines"); - - migrationBuilder.DropTable( - name: "users"); - } - } -} diff --git a/Timeline/Migrations/20200131100517_RefactorUser.Designer.cs b/Timeline/Migrations/20200131100517_RefactorUser.Designer.cs deleted file mode 100644 index 9b78eb15..00000000 --- a/Timeline/Migrations/20200131100517_RefactorUser.Designer.cs +++ /dev/null @@ -1,240 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200131100517_RefactorUser")] - partial class RefactorUser - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.1"); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("ETag") - .HasColumnName("etag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200131100517_RefactorUser.cs b/Timeline/Migrations/20200131100517_RefactorUser.cs deleted file mode 100644 index 8597ed50..00000000 --- a/Timeline/Migrations/20200131100517_RefactorUser.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class RefactorUser : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.RenameColumn(name: "name", table: "users", newName: "username"); - migrationBuilder.RenameIndex(name: "IX_users_name", table: "users", newName: "IX_users_username"); - - migrationBuilder.AddColumn( - name: "nickname", - table: "users", - nullable: true); - - migrationBuilder.Sql(@" -UPDATE users - SET nickname = ( - SELECT nickname - FROM user_details - WHERE user_details.UserId = users.id - ); - "); - - /* - migrationBuilder.RenameColumn(name: "UserId", table: "user_avatars", newName: "user"); - - migrationBuilder.DropForeignKey( - name: "FK_user_avatars_users_UserId", - table: "user_avatars"); - - migrationBuilder.AddForeignKey( - name: "FK_user_avatars_users_user", - table: "user_avatars", - column: "user", - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.RenameIndex( - name: "IX_user_avatars_UserId", - table: "user_avatars", - newName: "IX_user_avatars_user"); - */ - - migrationBuilder.Sql(@" -CREATE TABLE user_avatars_backup ( - id INTEGER NOT NULL - CONSTRAINT PK_user_avatars PRIMARY KEY AUTOINCREMENT, - data BLOB, - type TEXT, - etag TEXT, - last_modified TEXT NOT NULL, - user INTEGER NOT NULL, - CONSTRAINT FK_user_avatars_users_user FOREIGN KEY ( - user - ) - REFERENCES users (id) ON DELETE CASCADE -); - -INSERT INTO user_avatars_backup (id, data, type, etag, last_modified, user) - SELECT id, data, type, etag, last_modified, UserId FROM user_avatars; - -DROP TABLE user_avatars; - -ALTER TABLE user_avatars_backup - RENAME TO user_avatars; - -CREATE UNIQUE INDEX IX_user_avatars_user ON user_avatars (user); - "); - - // migrationBuilder.DropTable(name: "user_details"); - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" -CREATE TABLE user_avatars_backup ( - id INTEGER NOT NULL - CONSTRAINT PK_user_avatars PRIMARY KEY AUTOINCREMENT, - data BLOB, - type TEXT, - etag TEXT, - last_modified TEXT NOT NULL, - UserId INTEGER NOT NULL, - CONSTRAINT FK_user_avatars_users_UserId FOREIGN KEY ( - user - ) - REFERENCES users (id) ON DELETE CASCADE -); - -INSERT INTO user_avatars_backup (id, data, type, etag, last_modified, UserId) - SELECT id, data, type, etag, last_modified, user FROM user_avatars; - -DROP TABLE user_avatars; - -ALTER TABLE user_avatars_backup - RENAME TO user_avatars; - -CREATE UNIQUE INDEX IX_user_avatars_UserId ON user_avatars (UserId); - "); - - migrationBuilder.Sql(@" -CREATE TABLE users_backup ( - id INTEGER NOT NULL - CONSTRAINT PK_users PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - password TEXT NOT NULL, - roles TEXT NOT NULL, - version INTEGER NOT NULL - DEFAULT 0 -); - -INSERT INTO users_backup (id, name, password, roles, version) - SELECT id, username, password, roles, version FROM users; - -DROP TABLE users; - -ALTER TABLE users_backup - RENAME TO users; - -CREATE UNIQUE INDEX IX_users_name ON users (name); - "); - } - } -} diff --git a/Timeline/Migrations/20200221064341_AddJwtToken.Designer.cs b/Timeline/Migrations/20200221064341_AddJwtToken.Designer.cs deleted file mode 100644 index eb328b52..00000000 --- a/Timeline/Migrations/20200221064341_AddJwtToken.Designer.cs +++ /dev/null @@ -1,257 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200221064341_AddJwtToken")] - partial class AddJwtToken - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.2"); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("ETag") - .HasColumnName("etag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200221064341_AddJwtToken.cs b/Timeline/Migrations/20200221064341_AddJwtToken.cs deleted file mode 100644 index 628970c6..00000000 --- a/Timeline/Migrations/20200221064341_AddJwtToken.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Security.Cryptography; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public static class JwtTokenGenerateHelper - { - public static byte[] GenerateKey() - { - using var random = RandomNumberGenerator.Create(); - var key = new byte[16]; - random.GetBytes(key); - return key; - } - } - - public partial class AddJwtToken : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "jwt_token", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - key = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_jwt_token", x => x.id); - }); - - - migrationBuilder.InsertData("jwt_token", "key", JwtTokenGenerateHelper.GenerateKey()); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "jwt_token"); - } - } -} diff --git a/Timeline/Migrations/20200229103848_AddPostLocalId.Designer.cs b/Timeline/Migrations/20200229103848_AddPostLocalId.Designer.cs deleted file mode 100644 index cf6ae8a3..00000000 --- a/Timeline/Migrations/20200229103848_AddPostLocalId.Designer.cs +++ /dev/null @@ -1,265 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200229103848_AddPostLocalId")] - partial class AddPostLocalId - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.2"); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("ETag") - .HasColumnName("etag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200229103848_AddPostLocalId.cs b/Timeline/Migrations/20200229103848_AddPostLocalId.cs deleted file mode 100644 index 497b38a1..00000000 --- a/Timeline/Migrations/20200229103848_AddPostLocalId.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class AddPostLocalId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - migrationBuilder.AddColumn( - name: "current_post_local_id", - table: "timelines", - nullable: false, - defaultValue: 0L); - - migrationBuilder.AddColumn( - name: "local_id", - table: "timeline_posts", - nullable: false, - defaultValue: 0L); - - migrationBuilder.Sql(@" -UPDATE timeline_posts -SET local_id = (SELECT COUNT (*) - FROM timeline_posts AS p - WHERE p.timeline = timeline_posts.timeline - AND p.id <= timeline_posts.id); - -UPDATE timelines -SET current_post_local_id = (SELECT COUNT (*) - FROM timeline_posts AS p - WHERE p.timeline = timelines.id); - "); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Timeline/Migrations/20200306110049_AddDataTable.Designer.cs b/Timeline/Migrations/20200306110049_AddDataTable.Designer.cs deleted file mode 100644 index 336ffc18..00000000 --- a/Timeline/Migrations/20200306110049_AddDataTable.Designer.cs +++ /dev/null @@ -1,290 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200306110049_AddDataTable")] - partial class AddDataTable - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.2"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200306110049_AddDataTable.cs b/Timeline/Migrations/20200306110049_AddDataTable.cs deleted file mode 100644 index e33bf4c9..00000000 --- a/Timeline/Migrations/20200306110049_AddDataTable.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class AddDataTable : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "data", - columns: table => new - { - id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - tag = table.Column(nullable: false), - data = table.Column(nullable: false), - @ref = table.Column(name: "ref", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_data", x => x.id); - }); - - migrationBuilder.CreateIndex( - name: "IX_data_tag", - table: "data", - column: "tag", - unique: true); - - migrationBuilder.Sql(@" -ALTER TABLE user_avatars - RENAME TO user_avatars_backup; - -CREATE TABLE user_avatars ( - id INTEGER NOT NULL - CONSTRAINT PK_user_avatars PRIMARY KEY AUTOINCREMENT, - data_tag TEXT, - type TEXT, - last_modified TEXT NOT NULL, - user INTEGER NOT NULL, - CONSTRAINT FK_user_avatars_users_user FOREIGN KEY ( - user - ) - REFERENCES users (id) ON DELETE CASCADE -); - -INSERT INTO user_avatars (id, data_tag, type, last_modified, user) - SELECT id, etag, type, last_modified, user FROM user_avatars_backup; - -INSERT OR IGNORE INTO data (tag, data, ref) - SELECT etag, data, 0 FROM user_avatars_backup; - -UPDATE data -SET ref = (SELECT COUNT (*) - FROM user_avatars_backup AS a - WHERE a.etag == data.tag); - -DROP TABLE user_avatars_backup; - -CREATE UNIQUE INDEX IX_user_avatars_user ON user_avatars (user); - "); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "data"); - - migrationBuilder.DropColumn( - name: "data_tag", - table: "user_avatars"); - - migrationBuilder.AddColumn( - name: "data", - table: "user_avatars", - type: "BLOB", - nullable: true); - - migrationBuilder.AddColumn( - name: "etag", - table: "user_avatars", - type: "TEXT", - nullable: true); - } - } -} diff --git a/Timeline/Migrations/20200306111553_DropUserDetails.Designer.cs b/Timeline/Migrations/20200306111553_DropUserDetails.Designer.cs deleted file mode 100644 index f0c4dc08..00000000 --- a/Timeline/Migrations/20200306111553_DropUserDetails.Designer.cs +++ /dev/null @@ -1,290 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200306111553_DropUserDetails")] - partial class DropUserDetails - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.2"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200306111553_DropUserDetails.cs b/Timeline/Migrations/20200306111553_DropUserDetails.cs deleted file mode 100644 index 0a176461..00000000 --- a/Timeline/Migrations/20200306111553_DropUserDetails.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class DropUserDetails : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable(name: "user_details"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Timeline/Migrations/20200312112552_AddImagePost.Designer.cs b/Timeline/Migrations/20200312112552_AddImagePost.Designer.cs deleted file mode 100644 index bd75a916..00000000 --- a/Timeline/Migrations/20200312112552_AddImagePost.Designer.cs +++ /dev/null @@ -1,299 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200312112552_AddImagePost")] - partial class AddImagePost - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.2"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200312112552_AddImagePost.cs b/Timeline/Migrations/20200312112552_AddImagePost.cs deleted file mode 100644 index d5098ce0..00000000 --- a/Timeline/Migrations/20200312112552_AddImagePost.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Timeline.Models; - -namespace Timeline.Migrations -{ - public partial class AddImagePost : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "content_type", - table: "timeline_posts", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "extra_content", - table: "timeline_posts", - nullable: true); - - migrationBuilder.Sql($@" -UPDATE timeline_posts -SET content_type = '{TimelinePostContentTypes.Text}'; - "); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "content_type", - table: "timeline_posts"); - - migrationBuilder.DropColumn( - name: "extra_content", - table: "timeline_posts"); - } - } -} diff --git a/Timeline/Migrations/20200614061237_AddTimelineUniqueId.Designer.cs b/Timeline/Migrations/20200614061237_AddTimelineUniqueId.Designer.cs deleted file mode 100644 index adcc6308..00000000 --- a/Timeline/Migrations/20200614061237_AddTimelineUniqueId.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200614061237_AddTimelineUniqueId")] - partial class AddTimelineUniqueId - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.4"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200614061237_AddTimelineUniqueId.cs b/Timeline/Migrations/20200614061237_AddTimelineUniqueId.cs deleted file mode 100644 index 7abbed79..00000000 --- a/Timeline/Migrations/20200614061237_AddTimelineUniqueId.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class AddTimelineUniqueId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( -@" -PRAGMA foreign_keys=OFF; - -BEGIN TRANSACTION; - -CREATE TABLE new_timelines ( - id INTEGER NOT NULL CONSTRAINT PK_timelines PRIMARY KEY AUTOINCREMENT, - unique_id TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))), - name TEXT NULL, - description TEXT NULL, - owner INTEGER NOT NULL, - visibility INTEGER NOT NULL, - create_time TEXT NOT NULL, - current_post_local_id INTEGER NOT NULL DEFAULT 0, - CONSTRAINT FK_timelines_users_owner FOREIGN KEY (owner) REFERENCES users (id) ON DELETE CASCADE -); - -INSERT INTO new_timelines (id, name, description, owner, visibility, create_time, current_post_local_id) - SELECT id, name, description, owner, visibility, create_time, current_post_local_id FROM timelines; - -DROP TABLE timelines; - -ALTER TABLE new_timelines - RENAME TO timelines; - -CREATE INDEX IX_timelines_owner ON timelines (owner); - -PRAGMA foreign_key_check; - -COMMIT TRANSACTION; - -PRAGMA foreign_keys=ON; -" - , true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } -} diff --git a/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.Designer.cs b/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.Designer.cs deleted file mode 100644 index fd10dfa9..00000000 --- a/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.Designer.cs +++ /dev/null @@ -1,314 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200618064936_TimelineAddModifiedTime")] - partial class TimelineAddModifiedTime - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.5"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs b/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs deleted file mode 100644 index c277fe39..00000000 --- a/Timeline/Migrations/20200618064936_TimelineAddModifiedTime.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Timeline.Migrations -{ - public partial class TimelineAddModifiedTime : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - var currentTime = new DateTimeToStringConverter().ConvertToProvider(DateTime.Now); - - migrationBuilder.Sql( -@$" -PRAGMA foreign_keys=OFF; - -BEGIN TRANSACTION; - -CREATE TABLE new_timelines ( - id INTEGER NOT NULL CONSTRAINT PK_timelines PRIMARY KEY AUTOINCREMENT, - unique_id TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))), - name TEXT NULL, - name_last_modified TEXT NOT NULL, - description TEXT NULL, - owner INTEGER NOT NULL, - visibility INTEGER NOT NULL, - create_time TEXT NOT NULL, - last_modified TEXT NOT NULL, - current_post_local_id INTEGER NOT NULL DEFAULT 0, - CONSTRAINT FK_timelines_users_owner FOREIGN KEY (owner) REFERENCES users (id) ON DELETE CASCADE -); - -INSERT INTO new_timelines (id, unique_id, name, name_last_modified, description, owner, visibility, create_time, last_modified, current_post_local_id) - SELECT id, unique_id, name, '{currentTime}', description, owner, visibility, create_time, '{currentTime}', current_post_local_id FROM timelines; - -DROP TABLE timelines; - -ALTER TABLE new_timelines - RENAME TO timelines; - -CREATE INDEX IX_timelines_owner ON timelines (owner); - -PRAGMA foreign_key_check; - -COMMIT TRANSACTION; - -PRAGMA foreign_keys=ON; -" - , true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Timeline/Migrations/20200808071611_UserAddUniqueId.Designer.cs b/Timeline/Migrations/20200808071611_UserAddUniqueId.Designer.cs deleted file mode 100644 index fe2329e4..00000000 --- a/Timeline/Migrations/20200808071611_UserAddUniqueId.Designer.cs +++ /dev/null @@ -1,321 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200808071611_UserAddUniqueId")] - partial class UserAddUniqueId - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.5"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200808071611_UserAddUniqueId.cs b/Timeline/Migrations/20200808071611_UserAddUniqueId.cs deleted file mode 100644 index 651a2b05..00000000 --- a/Timeline/Migrations/20200808071611_UserAddUniqueId.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class UserAddUniqueId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( -@" -PRAGMA foreign_keys=OFF; - -BEGIN TRANSACTION; - -CREATE TABLE new_users ( - id INTEGER NOT NULL - CONSTRAINT PK_users PRIMARY KEY AUTOINCREMENT, - unique_id TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))), - username TEXT NOT NULL, - password TEXT NOT NULL, - roles TEXT NOT NULL, - version INTEGER NOT NULL - DEFAULT 0, - nickname TEXT -); - -INSERT INTO new_users (id, username, password, roles, version, nickname) - SELECT id, username, password, roles, version, nickname FROM users; - -DROP TABLE users; - -ALTER TABLE new_users - RENAME TO users; - -CREATE UNIQUE INDEX IX_users_username ON users ( - username -); - -PRAGMA foreign_key_check; - -COMMIT TRANSACTION; - -PRAGMA foreign_keys=ON; -" - , true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "unique_id", - table: "users"); - } - } -} diff --git a/Timeline/Migrations/20200810155908_AddTimesToUser.Designer.cs b/Timeline/Migrations/20200810155908_AddTimesToUser.Designer.cs deleted file mode 100644 index 71cc54dc..00000000 --- a/Timeline/Migrations/20200810155908_AddTimesToUser.Designer.cs +++ /dev/null @@ -1,339 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200810155908_AddTimesToUser")] - partial class AddTimesToUser - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.5"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .ValueGeneratedOnAdd() - .HasColumnName("create_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("LastModified") - .ValueGeneratedOnAdd() - .HasColumnName("last_modified") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("UsernameChangeTime") - .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200810155908_AddTimesToUser.cs b/Timeline/Migrations/20200810155908_AddTimesToUser.cs deleted file mode 100644 index 369f85e6..00000000 --- a/Timeline/Migrations/20200810155908_AddTimesToUser.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class AddTimesToUser : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( -@" -PRAGMA foreign_keys=OFF; - -BEGIN TRANSACTION; - -CREATE TABLE new_users ( - id INTEGER NOT NULL - CONSTRAINT PK_users PRIMARY KEY AUTOINCREMENT, - unique_id TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))), - username TEXT NOT NULL, - password TEXT NOT NULL, - roles TEXT NOT NULL, - version INTEGER NOT NULL - DEFAULT 0, - nickname TEXT, - create_time TEXT NOT NULL DEFAULT (datetime('now', 'utc')), - last_modified TEXT NOT NULL DEFAULT (datetime('now', 'utc')), - username_change_time TEXT NOT NULL DEFAULT (datetime('now', 'utc')) -); - -INSERT INTO new_users (id, unique_id, username, password, roles, version, nickname) - SELECT id, unique_id, username, password, roles, version, nickname FROM users; - -DROP TABLE users; - -ALTER TABLE new_users - RENAME TO users; - -CREATE UNIQUE INDEX IX_users_username ON users ( - username -); - -PRAGMA foreign_key_check; - -COMMIT TRANSACTION; - -PRAGMA foreign_keys=ON; -" -, true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "create_time", - table: "users"); - - migrationBuilder.DropColumn( - name: "last_modified", - table: "users"); - - migrationBuilder.DropColumn( - name: "username_change_time", - table: "users"); - } - } -} diff --git a/Timeline/Migrations/20200810170533_MakePostAuthorOptional.Designer.cs b/Timeline/Migrations/20200810170533_MakePostAuthorOptional.Designer.cs deleted file mode 100644 index 80598fdf..00000000 --- a/Timeline/Migrations/20200810170533_MakePostAuthorOptional.Designer.cs +++ /dev/null @@ -1,337 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200810170533_MakePostAuthorOptional")] - partial class MakePostAuthorOptional - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.5"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .ValueGeneratedOnAdd() - .HasColumnName("create_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("LastModified") - .ValueGeneratedOnAdd() - .HasColumnName("last_modified") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("UsernameChangeTime") - .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId"); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200810170533_MakePostAuthorOptional.cs b/Timeline/Migrations/20200810170533_MakePostAuthorOptional.cs deleted file mode 100644 index b0f0bca7..00000000 --- a/Timeline/Migrations/20200810170533_MakePostAuthorOptional.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class MakePostAuthorOptional : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" -PRAGMA foreign_keys = 0; - -BEGIN TRANSACTION; - -CREATE TABLE new_timeline_posts ( - id INTEGER NOT NULL - CONSTRAINT PK_timeline_posts PRIMARY KEY AUTOINCREMENT, - timeline INTEGER NOT NULL, - author INTEGER, - content TEXT, - time TEXT NOT NULL, - last_updated TEXT NOT NULL, - local_id INTEGER NOT NULL - DEFAULT 0, - content_type TEXT NOT NULL - DEFAULT '', - extra_content TEXT, - CONSTRAINT FK_timeline_posts_users_author FOREIGN KEY ( - author - ) - REFERENCES users (id), - CONSTRAINT FK_timeline_posts_timelines_timeline FOREIGN KEY ( - timeline - ) - REFERENCES timelines (id) ON DELETE CASCADE -); - -INSERT INTO new_timeline_posts SELECT * FROM timeline_posts; - -DROP TABLE timeline_posts; - -ALTER TABLE new_timeline_posts RENAME TO timeline_posts; - -CREATE INDEX IX_timeline_posts_author ON timeline_posts (author); - -CREATE INDEX IX_timeline_posts_timeline ON timeline_posts(timeline); - -PRAGMA foreign_key_check; - -COMMIT TRANSACTION; - -PRAGMA foreign_keys = 1; - ", true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_timeline_posts_users_author", - table: "timeline_posts"); - - migrationBuilder.AlterColumn( - name: "author", - table: "timeline_posts", - type: "INTEGER", - nullable: false, - oldClrType: typeof(long), - oldNullable: true); - - migrationBuilder.AddForeignKey( - name: "FK_timeline_posts_users_author", - table: "timeline_posts", - column: "author", - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.Designer.cs b/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.Designer.cs deleted file mode 100644 index 58238557..00000000 --- a/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.Designer.cs +++ /dev/null @@ -1,337 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200811080808_ChangeDateTimeOffsetToDateTime")] - partial class ChangeDateTimeOffsetToDateTime - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.5"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .ValueGeneratedOnAdd() - .HasColumnName("create_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("LastModified") - .ValueGeneratedOnAdd() - .HasColumnName("last_modified") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("UsernameChangeTime") - .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId"); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.cs b/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.cs deleted file mode 100644 index eb6b44f3..00000000 --- a/Timeline/Migrations/20200811080808_ChangeDateTimeOffsetToDateTime.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class ChangeDateTimeOffsetToDateTime : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs b/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs deleted file mode 100644 index f2279f3b..00000000 --- a/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs +++ /dev/null @@ -1,341 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20200826164553_TimelineAddTitle")] - partial class TimelineAddTitle - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.7"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Title") - .HasColumnName("title") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .ValueGeneratedOnAdd() - .HasColumnName("create_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("LastModified") - .ValueGeneratedOnAdd() - .HasColumnName("last_modified") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("UsernameChangeTime") - .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId"); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/Migrations/20200826164553_TimelineAddTitle.cs b/Timeline/Migrations/20200826164553_TimelineAddTitle.cs deleted file mode 100644 index 7e8c498b..00000000 --- a/Timeline/Migrations/20200826164553_TimelineAddTitle.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Timeline.Migrations -{ - public partial class TimelineAddTitle : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "title", - table: "timelines", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "title", - table: "timelines"); - } - } -} diff --git a/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/Timeline/Migrations/DatabaseContextModelSnapshot.cs deleted file mode 100644 index 65ae6c9a..00000000 --- a/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ /dev/null @@ -1,339 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Timeline.Entities; - -namespace Timeline.Migrations -{ - [DbContext(typeof(DatabaseContext))] - partial class DatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.7"); - - modelBuilder.Entity("Timeline.Entities.DataEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Data") - .IsRequired() - .HasColumnName("data") - .HasColumnType("BLOB"); - - b.Property("Ref") - .HasColumnName("ref") - .HasColumnType("INTEGER"); - - b.Property("Tag") - .IsRequired() - .HasColumnName("tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tag") - .IsUnique(); - - b.ToTable("data"); - }); - - modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnName("key") - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("jwt_token"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .HasColumnName("create_time") - .HasColumnType("TEXT"); - - b.Property("CurrentPostLocalId") - .HasColumnName("current_post_local_id") - .HasColumnType("INTEGER"); - - b.Property("Description") - .HasColumnName("description") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnName("name") - .HasColumnType("TEXT"); - - b.Property("NameLastModified") - .HasColumnName("name_last_modified") - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnName("owner") - .HasColumnType("INTEGER"); - - b.Property("Title") - .HasColumnName("title") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Visibility") - .HasColumnName("visibility") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("timelines"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TimelineId"); - - b.HasIndex("UserId"); - - b.ToTable("timeline_members"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("AuthorId") - .HasColumnName("author") - .HasColumnType("INTEGER"); - - b.Property("Content") - .HasColumnName("content") - .HasColumnType("TEXT"); - - b.Property("ContentType") - .IsRequired() - .HasColumnName("content_type") - .HasColumnType("TEXT"); - - b.Property("ExtraContent") - .HasColumnName("extra_content") - .HasColumnType("TEXT"); - - b.Property("LastUpdated") - .HasColumnName("last_updated") - .HasColumnType("TEXT"); - - b.Property("LocalId") - .HasColumnName("local_id") - .HasColumnType("INTEGER"); - - b.Property("Time") - .HasColumnName("time") - .HasColumnType("TEXT"); - - b.Property("TimelineId") - .HasColumnName("timeline") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("TimelineId"); - - b.ToTable("timeline_posts"); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("DataTag") - .HasColumnName("data_tag") - .HasColumnType("TEXT"); - - b.Property("LastModified") - .HasColumnName("last_modified") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnName("type") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnName("user") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("user_avatars"); - }); - - modelBuilder.Entity("Timeline.Entities.UserEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnName("id") - .HasColumnType("INTEGER"); - - b.Property("CreateTime") - .ValueGeneratedOnAdd() - .HasColumnName("create_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("LastModified") - .ValueGeneratedOnAdd() - .HasColumnName("last_modified") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Nickname") - .HasColumnName("nickname") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnName("password") - .HasColumnType("TEXT"); - - b.Property("Roles") - .IsRequired() - .HasColumnName("roles") - .HasColumnType("TEXT"); - - b.Property("UniqueId") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnName("unique_id") - .HasColumnType("TEXT") - .HasDefaultValueSql("lower(hex(randomblob(16)))"); - - b.Property("Username") - .IsRequired() - .HasColumnName("username") - .HasColumnType("TEXT"); - - b.Property("UsernameChangeTime") - .ValueGeneratedOnAdd() - .HasColumnName("username_change_time") - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now', 'utc')"); - - b.Property("Version") - .ValueGeneratedOnAdd() - .HasColumnName("version") - .HasColumnType("INTEGER") - .HasDefaultValue(0L); - - b.HasKey("Id"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users"); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Owner") - .WithMany("Timelines") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => - { - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Members") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithMany("TimelinesJoined") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "Author") - .WithMany("TimelinePosts") - .HasForeignKey("AuthorId"); - - b.HasOne("Timeline.Entities.TimelineEntity", "Timeline") - .WithMany("Posts") - .HasForeignKey("TimelineId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b => - { - b.HasOne("Timeline.Entities.UserEntity", "User") - .WithOne("Avatar") - .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Timeline/MockClientApp/index.html b/Timeline/MockClientApp/index.html deleted file mode 100644 index 03cf371e..00000000 --- a/Timeline/MockClientApp/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Mock Client App - - - This is a mock client app for testing. - - diff --git a/Timeline/Models/ByteData.cs b/Timeline/Models/ByteData.cs deleted file mode 100644 index 7b832eb5..00000000 --- a/Timeline/Models/ByteData.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NSwag.Annotations; - -namespace Timeline.Models -{ - /// - /// Model for reading http body as bytes. - /// - [OpenApiFile] - public class ByteData - { - /// - /// - /// The data. - /// The content type. - public ByteData(byte[] data, string contentType) - { - Data = data; - ContentType = contentType; - } - - /// - /// Data. - /// -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; } -#pragma warning restore CA1819 // Properties should not return arrays - - /// - /// Content type. - /// - public string ContentType { get; } - } -} diff --git a/Timeline/Models/Converters/JsonDateTimeConverter.cs b/Timeline/Models/Converters/JsonDateTimeConverter.cs deleted file mode 100644 index 94b5cab0..00000000 --- a/Timeline/Models/Converters/JsonDateTimeConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; -using Timeline.Helpers; - -namespace Timeline.Models.Converters -{ - public class JsonDateTimeConverter : JsonConverter - { - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - Debug.Assert(typeToConvert == typeof(DateTime)); - return DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.MyToUtc().ToString("s", CultureInfo.InvariantCulture) + "Z"); - } - } -} diff --git a/Timeline/Models/Converters/MyDateTimeConverter.cs b/Timeline/Models/Converters/MyDateTimeConverter.cs deleted file mode 100644 index f125cd5c..00000000 --- a/Timeline/Models/Converters/MyDateTimeConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; - -namespace Timeline.Models.Converters -{ - public class MyDateTimeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return base.CanConvertTo(context, destinationType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string text) - { - text = text.Trim(); - if (text.Length == 0) - { - return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); - } - - return DateTime.Parse(text, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - } - - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string) && value is DateTime) - { - DateTime dt = (DateTime)value; - if (dt == DateTime.MinValue) - { - return string.Empty; - } - - return dt.ToString("s", CultureInfo.InvariantCulture) + "Z"; - } - - return base.ConvertTo(context, culture, value, destinationType); - } - } -} diff --git a/Timeline/Models/Http/ActionContextAccessorExtensions.cs b/Timeline/Models/Http/ActionContextAccessorExtensions.cs deleted file mode 100644 index bcc55c5a..00000000 --- a/Timeline/Models/Http/ActionContextAccessorExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using System; - -namespace Timeline.Models.Http -{ - public static class ActionContextAccessorExtensions - { - public static ActionContext AssertActionContextForUrlFill(this IActionContextAccessor accessor) - { - return accessor.ActionContext ?? throw new InvalidOperationException(Resources.Models.Http.Exception.ActionContextNull); - } - } -} diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs deleted file mode 100644 index 5fa22c9e..00000000 --- a/Timeline/Models/Http/Common.cs +++ /dev/null @@ -1,120 +0,0 @@ -using static Timeline.Resources.Models.Http.Common; - -namespace Timeline.Models.Http -{ - public class CommonResponse - { - public CommonResponse() - { - - } - - public CommonResponse(int code, string message) - { - Code = code; - Message = message; - } - - public int Code { get; set; } - public string? Message { get; set; } - } - - public class CommonDataResponse : CommonResponse - { - public CommonDataResponse() - { - - } - - public CommonDataResponse(int code, string message, T data) - : base(code, message) - { - Data = data; - } - - public T Data { get; set; } = default!; - } - - public class CommonPutResponse : CommonDataResponse - { - public class ResponseData - { - public ResponseData() { } - - public ResponseData(bool create) - { - Create = create; - } - - public bool Create { get; set; } - } - - public CommonPutResponse() - { - - } - - public CommonPutResponse(int code, string message, bool create) - : base(code, message, new ResponseData(create)) - { - - } - - internal static CommonPutResponse Create() - { - return new CommonPutResponse(0, MessagePutCreate, true); - } - - internal static CommonPutResponse Modify() - { - return new CommonPutResponse(0, MessagePutModify, false); - } - } - - /// - /// Common response for delete method. - /// - public class CommonDeleteResponse : CommonDataResponse - { - /// - public class ResponseData - { - /// - public ResponseData() { } - - /// - public ResponseData(bool delete) - { - Delete = delete; - } - - /// - /// True if the entry is deleted. False if the entry does not exist. - /// - public bool Delete { get; set; } - } - - /// - public CommonDeleteResponse() - { - - } - - /// - public CommonDeleteResponse(int code, string message, bool delete) - : base(code, message, new ResponseData(delete)) - { - - } - - internal static CommonDeleteResponse Delete() - { - return new CommonDeleteResponse(0, MessageDeleteDelete, true); - } - - internal static CommonDeleteResponse NotExist() - { - return new CommonDeleteResponse(0, MessageDeleteNotExist, false); - } - } -} diff --git a/Timeline/Models/Http/ErrorResponse.cs b/Timeline/Models/Http/ErrorResponse.cs deleted file mode 100644 index ac86481f..00000000 --- a/Timeline/Models/Http/ErrorResponse.cs +++ /dev/null @@ -1,261 +0,0 @@ -using static Timeline.Resources.Messages; - -namespace Timeline.Models.Http -{ - public static class ErrorResponse - { - public static class Common - { - public static CommonResponse InvalidModel(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.InvalidModel, string.Format(Common_InvalidModel, formatArgs)); - } - - public static CommonResponse CustomMessage_InvalidModel(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.InvalidModel, string.Format(message, formatArgs)); - } - - public static CommonResponse Forbid(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Forbid, string.Format(Common_Forbid, formatArgs)); - } - - public static CommonResponse CustomMessage_Forbid(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Forbid, string.Format(message, formatArgs)); - } - - public static CommonResponse UnknownEndpoint(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.UnknownEndpoint, string.Format(Common_UnknownEndpoint, formatArgs)); - } - - public static CommonResponse CustomMessage_UnknownEndpoint(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.UnknownEndpoint, string.Format(message, formatArgs)); - } - - public static class Header - { - public static CommonResponse IfNonMatch_BadFormat(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, string.Format(Common_Header_IfNonMatch_BadFormat, formatArgs)); - } - - public static CommonResponse CustomMessage_IfNonMatch_BadFormat(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, string.Format(message, formatArgs)); - } - - } - - public static class Content - { - public static CommonResponse TooBig(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.TooBig, string.Format(Common_Content_TooBig, formatArgs)); - } - - public static CommonResponse CustomMessage_TooBig(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.Common.Content.TooBig, string.Format(message, formatArgs)); - } - - } - - } - - public static class UserCommon - { - public static CommonResponse NotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserCommon.NotExist, string.Format(UserCommon_NotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_NotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserCommon.NotExist, string.Format(message, formatArgs)); - } - - } - - public static class TokenController - { - public static CommonResponse Create_BadCredential(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Create_BadCredential, string.Format(TokenController_Create_BadCredential, formatArgs)); - } - - public static CommonResponse CustomMessage_Create_BadCredential(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Create_BadCredential, string.Format(message, formatArgs)); - } - - public static CommonResponse Verify_BadFormat(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_BadFormat, string.Format(TokenController_Verify_BadFormat, formatArgs)); - } - - public static CommonResponse CustomMessage_Verify_BadFormat(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_BadFormat, string.Format(message, formatArgs)); - } - - public static CommonResponse Verify_UserNotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_UserNotExist, string.Format(TokenController_Verify_UserNotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_Verify_UserNotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_UserNotExist, string.Format(message, formatArgs)); - } - - public static CommonResponse Verify_OldVersion(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_OldVersion, string.Format(TokenController_Verify_OldVersion, formatArgs)); - } - - public static CommonResponse CustomMessage_Verify_OldVersion(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_OldVersion, string.Format(message, formatArgs)); - } - - public static CommonResponse Verify_TimeExpired(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_TimeExpired, string.Format(TokenController_Verify_TimeExpired, formatArgs)); - } - - public static CommonResponse CustomMessage_Verify_TimeExpired(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TokenController.Verify_TimeExpired, string.Format(message, formatArgs)); - } - - } - - public static class UserController - { - public static CommonResponse UsernameConflict(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserController.UsernameConflict, string.Format(UserController_UsernameConflict, formatArgs)); - } - - public static CommonResponse CustomMessage_UsernameConflict(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserController.UsernameConflict, string.Format(message, formatArgs)); - } - - public static CommonResponse ChangePassword_BadOldPassword(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserController.ChangePassword_BadOldPassword, string.Format(UserController_ChangePassword_BadOldPassword, formatArgs)); - } - - public static CommonResponse CustomMessage_ChangePassword_BadOldPassword(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserController.ChangePassword_BadOldPassword, string.Format(message, formatArgs)); - } - - } - - public static class UserAvatar - { - public static CommonResponse BadFormat_CantDecode(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_CantDecode, string.Format(UserAvatar_BadFormat_CantDecode, formatArgs)); - } - - public static CommonResponse CustomMessage_BadFormat_CantDecode(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_CantDecode, string.Format(message, formatArgs)); - } - - public static CommonResponse BadFormat_UnmatchedFormat(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_UnmatchedFormat, string.Format(UserAvatar_BadFormat_UnmatchedFormat, formatArgs)); - } - - public static CommonResponse CustomMessage_BadFormat_UnmatchedFormat(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_UnmatchedFormat, string.Format(message, formatArgs)); - } - - public static CommonResponse BadFormat_BadSize(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_BadSize, string.Format(UserAvatar_BadFormat_BadSize, formatArgs)); - } - - public static CommonResponse CustomMessage_BadFormat_BadSize(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.UserAvatar.BadFormat_BadSize, string.Format(message, formatArgs)); - } - - } - - public static class TimelineController - { - public static CommonResponse NameConflict(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.NameConflict, string.Format(TimelineController_NameConflict, formatArgs)); - } - - public static CommonResponse CustomMessage_NameConflict(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.NameConflict, string.Format(message, formatArgs)); - } - - public static CommonResponse NotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(TimelineController_NotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_NotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(message, formatArgs)); - } - - public static CommonResponse MemberPut_NotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(TimelineController_MemberPut_NotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_MemberPut_NotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(message, formatArgs)); - } - - public static CommonResponse QueryRelateNotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(TimelineController_QueryRelateNotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_QueryRelateNotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(message, formatArgs)); - } - - public static CommonResponse PostNotExist(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.PostNotExist, string.Format(TimelineController_PostNotExist, formatArgs)); - } - - public static CommonResponse CustomMessage_PostNotExist(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.PostNotExist, string.Format(message, formatArgs)); - } - - public static CommonResponse PostNoData(params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.PostNoData, string.Format(TimelineController_PostNoData, formatArgs)); - } - - public static CommonResponse CustomMessage_PostNoData(string message, params object?[] formatArgs) - { - return new CommonResponse(ErrorCodes.TimelineController.PostNoData, string.Format(message, formatArgs)); - } - - } - - } - -} diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs deleted file mode 100644 index a81b33f5..00000000 --- a/Timeline/Models/Http/Timeline.cs +++ /dev/null @@ -1,219 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; -using System; -using System.Collections.Generic; -using Timeline.Controllers; - -namespace Timeline.Models.Http -{ - /// - /// Info of post content. - /// - public class TimelinePostContentInfo - { - /// - /// Type of the post content. - /// - public string Type { get; set; } = default!; - /// - /// If post is of text type. This is the text. - /// - public string? Text { get; set; } - /// - /// If post is of image type. This is the image url. - /// - public string? Url { get; set; } - /// - /// If post has data (currently it means it's a image post), this is the data etag. - /// - public string? ETag { get; set; } - } - - /// - /// Info of a post. - /// - public class TimelinePostInfo - { - /// - /// Post id. - /// - public long Id { get; set; } - /// - /// Content of the post. May be null if post is deleted. - /// - public TimelinePostContentInfo? Content { get; set; } - /// - /// True if post is deleted. - /// - public bool Deleted { get; set; } - /// - /// Post time. - /// - public DateTime Time { get; set; } - /// - /// The author. May be null if the user has been deleted. - /// - public UserInfo? Author { get; set; } = default!; - /// - /// Last updated time. - /// - public DateTime LastUpdated { get; set; } = default!; - } - - /// - /// Info of a timeline. - /// - public class TimelineInfo - { - /// - /// Unique id. - /// - public string UniqueId { get; set; } = default!; - /// - /// Title. - /// - public string Title { get; set; } = default!; - /// - /// Name of timeline. - /// - public string Name { get; set; } = default!; - /// - /// Last modified time of timeline name. - /// - public DateTime NameLastModifed { get; set; } = default!; - /// - /// Timeline description. - /// - public string Description { get; set; } = default!; - /// - /// Owner of the timeline. - /// - public UserInfo Owner { get; set; } = default!; - /// - /// Visibility of the timeline. - /// - public TimelineVisibility Visibility { get; set; } -#pragma warning disable CA2227 // Collection properties should be read only - /// - /// Members of timeline. - /// - public List Members { get; set; } = default!; -#pragma warning restore CA2227 // Collection properties should be read only - /// - /// Create time of timeline. - /// - public DateTime CreateTime { get; set; } = default!; - /// - /// Last modified time of timeline. - /// - public DateTime LastModified { get; set; } = default!; - -#pragma warning disable CA1707 // Identifiers should not contain underscores - /// - /// Related links. - /// - public TimelineInfoLinks _links { get; set; } = default!; -#pragma warning restore CA1707 // Identifiers should not contain underscores - } - - /// - /// Related links for timeline. - /// - public class TimelineInfoLinks - { - /// - /// Self. - /// - public string Self { get; set; } = default!; - /// - /// Posts url. - /// - public string Posts { get; set; } = default!; - } - - public class TimelineInfoLinksValueResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TimelineInfoLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public TimelineInfoLinks Resolve(Timeline source, TimelineInfo destination, TimelineInfoLinks destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - return new TimelineInfoLinks - { - Self = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }), - Posts = urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }) - }; - } - } - - public class TimelinePostContentResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TimelinePostContentResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public TimelinePostContentInfo? Resolve(TimelinePost source, TimelinePostInfo destination, TimelinePostContentInfo? destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - var sourceContent = source.Content; - - if (sourceContent == null) - { - return null; - } - - if (sourceContent is TextTimelinePostContent textContent) - { - return new TimelinePostContentInfo - { - Type = TimelinePostContentTypes.Text, - Text = textContent.Text - }; - } - else if (sourceContent is ImageTimelinePostContent imageContent) - { - return new TimelinePostContentInfo - { - Type = TimelinePostContentTypes.Image, - Url = urlHelper.ActionLink( - action: nameof(TimelineController.PostDataGet), - controller: nameof(TimelineController)[0..^nameof(Controller).Length], - values: new { Name = source.TimelineName, Id = source.Id }), - ETag = $"\"{imageContent.DataTag}\"" - }; - } - else - { - throw new InvalidOperationException(Resources.Models.Http.Exception.UnknownPostContentType); - } - } - } - - public class TimelineInfoAutoMapperProfile : Profile - { - public TimelineInfoAutoMapperProfile() - { - CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); - CreateMap().ForMember(p => p.Content, opt => opt.MapFrom()); - CreateMap(); - } - } -} diff --git a/Timeline/Models/Http/TimelineController.cs b/Timeline/Models/Http/TimelineController.cs deleted file mode 100644 index 7bd141ed..00000000 --- a/Timeline/Models/Http/TimelineController.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Timeline.Models.Validation; - -namespace Timeline.Models.Http -{ - /// - /// Content of post create request. - /// - public class TimelinePostCreateRequestContent - { - /// - /// Type of post content. - /// - [Required] - public string Type { get; set; } = default!; - /// - /// If post is of text type, this is the text. - /// - public string? Text { get; set; } - /// - /// If post is of image type, this is base64 of image data. - /// - public string? Data { get; set; } - } - - public class TimelinePostCreateRequest - { - /// - /// Content of the new post. - /// - [Required] - public TimelinePostCreateRequestContent Content { get; set; } = default!; - - /// - /// Time of the post. If not set, current time will be used. - /// - public DateTime? Time { get; set; } - } - - /// - /// Create timeline request model. - /// - public class TimelineCreateRequest - { - /// - /// Name of the new timeline. Must be a valid name. - /// - [Required] - [TimelineName] - public string Name { get; set; } = default!; - } - - /// - /// Patch timeline request model. - /// - public class TimelinePatchRequest - { - /// - /// New title. Null for not change. - /// - public string? Title { get; set; } - - /// - /// New description. Null for not change. - /// - public string? Description { get; set; } - - /// - /// New visibility. Null for not change. - /// - public TimelineVisibility? Visibility { get; set; } - } - - /// - /// Change timeline name request model. - /// - public class TimelineChangeNameRequest - { - /// - /// Old name of timeline. - /// - [Required] - [TimelineName] - public string OldName { get; set; } = default!; - /// - /// New name of timeline. - /// - [Required] - [TimelineName] - public string NewName { get; set; } = default!; - } -} diff --git a/Timeline/Models/Http/TokenController.cs b/Timeline/Models/Http/TokenController.cs deleted file mode 100644 index a42c44e5..00000000 --- a/Timeline/Models/Http/TokenController.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Timeline.Controllers; - -namespace Timeline.Models.Http -{ - /// - /// Request model for . - /// - public class CreateTokenRequest - { - /// - /// The username. - /// - public string Username { get; set; } = default!; - /// - /// The password. - /// - public string Password { get; set; } = default!; - /// - /// Optional token validation period. In days. If not specified, server will use a default one. - /// - [Range(1, 365)] - public int? Expire { get; set; } - } - - /// - /// Response model for . - /// - public class CreateTokenResponse - { - /// - /// The token created. - /// - public string Token { get; set; } = default!; - /// - /// The user owning the token. - /// - public UserInfo User { get; set; } = default!; - } - - /// - /// Request model for . - /// - public class VerifyTokenRequest - { - /// - /// The token to verify. - /// - public string Token { get; set; } = default!; - } - - /// - /// Response model for . - /// - public class VerifyTokenResponse - { - /// - /// The user owning the token. - /// - public UserInfo User { get; set; } = default!; - } -} diff --git a/Timeline/Models/Http/UserController.cs b/Timeline/Models/Http/UserController.cs deleted file mode 100644 index 6bc5a66e..00000000 --- a/Timeline/Models/Http/UserController.cs +++ /dev/null @@ -1,93 +0,0 @@ -using AutoMapper; -using System.ComponentModel.DataAnnotations; -using Timeline.Controllers; -using Timeline.Models.Validation; - -namespace Timeline.Models.Http -{ - /// - /// Request model for . - /// - public class UserPatchRequest - { - /// - /// New username. Null if not change. Need to be administrator. - /// - [Username] - public string? Username { get; set; } - - /// - /// New password. Null if not change. Need to be administrator. - /// - [MinLength(1)] - public string? Password { get; set; } - - /// - /// New nickname. Null if not change. Need to be administrator to change other's. - /// - [Nickname] - public string? Nickname { get; set; } - - /// - /// Whether to be administrator. Null if not change. Need to be administrator. - /// - public bool? Administrator { get; set; } - } - - /// - /// Request model for . - /// - public class CreateUserRequest - { - /// - /// Username of the new user. - /// - [Required, Username] - public string Username { get; set; } = default!; - - /// - /// Password of the new user. - /// - [Required, MinLength(1)] - public string Password { get; set; } = default!; - - /// - /// Whether the new user is administrator. - /// - [Required] - public bool? Administrator { get; set; } - - /// - /// Nickname of the new user. - /// - [Nickname] - public string? Nickname { get; set; } - } - - /// - /// Request model for . - /// - public class ChangePasswordRequest - { - /// - /// Old password. - /// - [Required(AllowEmptyStrings = false)] - public string OldPassword { get; set; } = default!; - - /// - /// New password. - /// - [Required(AllowEmptyStrings = false)] - public string NewPassword { get; set; } = default!; - } - - public class UserControllerAutoMapperProfile : Profile - { - public UserControllerAutoMapperProfile() - { - CreateMap(MemberList.Source); - CreateMap(MemberList.Source); - } - } -} diff --git a/Timeline/Models/Http/UserInfo.cs b/Timeline/Models/Http/UserInfo.cs deleted file mode 100644 index d92a12c4..00000000 --- a/Timeline/Models/Http/UserInfo.cs +++ /dev/null @@ -1,90 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; -using Timeline.Controllers; - -namespace Timeline.Models.Http -{ - /// - /// Info of a user. - /// - public class UserInfo - { - /// - /// Unique id. - /// - public string UniqueId { get; set; } = default!; - /// - /// Username. - /// - public string Username { get; set; } = default!; - /// - /// Nickname. - /// - public string Nickname { get; set; } = default!; - /// - /// True if the user is a administrator. - /// - public bool? Administrator { get; set; } = default!; -#pragma warning disable CA1707 // Identifiers should not contain underscores - /// - /// Related links. - /// - public UserInfoLinks _links { get; set; } = default!; -#pragma warning restore CA1707 // Identifiers should not contain underscores - } - - /// - /// Related links for user. - /// - public class UserInfoLinks - { - /// - /// Self. - /// - public string Self { get; set; } = default!; - /// - /// Avatar url. - /// - public string Avatar { get; set; } = default!; - /// - /// Personal timeline url. - /// - public string Timeline { get; set; } = default!; - } - - public class UserInfoLinksValueResolver : IValueResolver - { - private readonly IActionContextAccessor _actionContextAccessor; - private readonly IUrlHelperFactory _urlHelperFactory; - - public UserInfoLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory) - { - _actionContextAccessor = actionContextAccessor; - _urlHelperFactory = urlHelperFactory; - } - - public UserInfoLinks Resolve(User source, UserInfo destination, UserInfoLinks destMember, ResolutionContext context) - { - var actionContext = _actionContextAccessor.AssertActionContextForUrlFill(); - var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext); - - var result = new UserInfoLinks - { - Self = urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { destination.Username }), - Avatar = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { destination.Username }), - Timeline = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { Name = "@" + destination.Username }) - }; - return result; - } - } - - public class UserInfoAutoMapperProfile : Profile - { - public UserInfoAutoMapperProfile() - { - CreateMap().ForMember(u => u._links, opt => opt.MapFrom()); - } - } -} diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Timeline.cs deleted file mode 100644 index a5987577..00000000 --- a/Timeline/Models/Timeline.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Timeline.Models -{ - public enum TimelineVisibility - { - /// - /// All people including those without accounts. - /// - Public, - /// - /// Only people signed in. - /// - Register, - /// - /// Only member. - /// - Private - } - - public static class TimelinePostContentTypes - { - public const string Text = "text"; - public const string Image = "image"; - } - - public interface ITimelinePostContent - { - public string Type { get; } - } - - public class TextTimelinePostContent : ITimelinePostContent - { - public TextTimelinePostContent(string text) { Text = text; } - - public string Type { get; } = TimelinePostContentTypes.Text; - public string Text { get; set; } - } - - public class ImageTimelinePostContent : ITimelinePostContent - { - public ImageTimelinePostContent(string dataTag) { DataTag = dataTag; } - - public string Type { get; } = TimelinePostContentTypes.Image; - - /// - /// The tag of the data. The tag of the entry in DataManager. Also the etag (not quoted). - /// - public string DataTag { get; set; } - } - - public class TimelinePost - { - public TimelinePost(long id, ITimelinePostContent? content, DateTime time, User? author, DateTime lastUpdated, string timelineName) - { - Id = id; - Content = content; - Time = time; - Author = author; - LastUpdated = lastUpdated; - TimelineName = timelineName; - } - - public long Id { get; set; } - public ITimelinePostContent? Content { get; set; } - public bool Deleted => Content == null; - public DateTime Time { get; set; } - public User? Author { get; set; } - public DateTime LastUpdated { get; set; } - public string TimelineName { get; set; } - } - -#pragma warning disable CA1724 // Type names should not match namespaces - public class Timeline -#pragma warning restore CA1724 // Type names should not match namespaces - { - public string UniqueID { get; set; } = default!; - public string Name { get; set; } = default!; - public DateTime NameLastModified { get; set; } = default!; - public string Title { get; set; } = default!; - public string Description { get; set; } = default!; - public User Owner { get; set; } = default!; - public TimelineVisibility Visibility { get; set; } -#pragma warning disable CA2227 // Collection properties should be read only - public List Members { get; set; } = default!; -#pragma warning restore CA2227 // Collection properties should be read only - public DateTime CreateTime { get; set; } = default!; - public DateTime LastModified { get; set; } = default!; - } - - public class TimelineChangePropertyRequest - { - public string? Title { get; set; } - public string? Description { get; set; } - public TimelineVisibility? Visibility { get; set; } - } -} diff --git a/Timeline/Models/User.cs b/Timeline/Models/User.cs deleted file mode 100644 index f08a62db..00000000 --- a/Timeline/Models/User.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Timeline.Models -{ - public class User - { - public string? UniqueId { get; set; } - public string? Username { get; set; } - public string? Nickname { get; set; } - public bool? Administrator { get; set; } - - #region secret - public long? Id { get; set; } - public string? Password { get; set; } - public long? Version { get; set; } - public DateTime? UsernameChangeTime { get; set; } - public DateTime? CreateTime { get; set; } - public DateTime? LastModified { get; set; } - #endregion secret - } -} diff --git a/Timeline/Models/Validation/GeneralTimelineNameValidator.cs b/Timeline/Models/Validation/GeneralTimelineNameValidator.cs deleted file mode 100644 index e1c96fbd..00000000 --- a/Timeline/Models/Validation/GeneralTimelineNameValidator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Timeline.Models.Validation -{ - public class GeneralTimelineNameValidator : Validator - { - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); - - protected override (bool, string) DoValidate(string value) - { - if (value.StartsWith('@')) - { - return _usernameValidator.Validate(value.Substring(1)); - } - else - { - return _timelineNameValidator.Validate(value); - } - } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, - AllowMultiple = false)] - public class GeneralTimelineNameAttribute : ValidateWithAttribute - { - public GeneralTimelineNameAttribute() - : base(typeof(GeneralTimelineNameValidator)) - { - - } - } -} diff --git a/Timeline/Models/Validation/NameValidator.cs b/Timeline/Models/Validation/NameValidator.cs deleted file mode 100644 index b74c40b7..00000000 --- a/Timeline/Models/Validation/NameValidator.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Linq; -using System.Text.RegularExpressions; -using static Timeline.Resources.Models.Validation.NameValidator; - -namespace Timeline.Models.Validation -{ - public class NameValidator : Validator - { - private static Regex UniqueIdRegex { get; } = new Regex(@"^[a-zA-Z0-9]{32}$"); - - public const int MaxLength = 26; - - protected override (bool, string) DoValidate(string value) - { - if (value.Length == 0) - { - return (false, MessageEmptyString); - } - - if (value.Length > MaxLength) - { - return (false, MessageTooLong); - } - - foreach ((char c, int i) in value.Select((c, i) => (c, i))) - { - if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_')) - { - return (false, MessageInvalidChar); - } - } - - // Currently name can't be longer than 26. So this is not needed. But reserve it for future use. - if (UniqueIdRegex.IsMatch(value)) - { - return (false, MessageUnqiueId); - } - - return (true, GetSuccessMessage()); - } - } -} diff --git a/Timeline/Models/Validation/NicknameValidator.cs b/Timeline/Models/Validation/NicknameValidator.cs deleted file mode 100644 index 1d6ab163..00000000 --- a/Timeline/Models/Validation/NicknameValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using static Timeline.Resources.Models.Validation.NicknameValidator; - -namespace Timeline.Models.Validation -{ - public class NicknameValidator : Validator - { - protected override (bool, string) DoValidate(string value) - { - if (value.Length > 25) - return (false, MessageTooLong); - - return (true, GetSuccessMessage()); - } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] - public class NicknameAttribute : ValidateWithAttribute - { - public NicknameAttribute() : base(typeof(NicknameValidator)) - { - - } - } -} diff --git a/Timeline/Models/Validation/TimelineNameValidator.cs b/Timeline/Models/Validation/TimelineNameValidator.cs deleted file mode 100644 index f1ab54e8..00000000 --- a/Timeline/Models/Validation/TimelineNameValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Timeline.Models.Validation -{ - public class TimelineNameValidator : NameValidator - { - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, - AllowMultiple = false)] - public class TimelineNameAttribute : ValidateWithAttribute - { - public TimelineNameAttribute() - : base(typeof(TimelineNameValidator)) - { - - } - } -} diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs deleted file mode 100644 index 87bbf85f..00000000 --- a/Timeline/Models/Validation/UsernameValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Timeline.Models.Validation -{ - public class UsernameValidator : NameValidator - { - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, - AllowMultiple = false)] - public class UsernameAttribute : ValidateWithAttribute - { - public UsernameAttribute() - : base(typeof(UsernameValidator)) - { - - } - } -} diff --git a/Timeline/Models/Validation/Validator.cs b/Timeline/Models/Validation/Validator.cs deleted file mode 100644 index aef7891c..00000000 --- a/Timeline/Models/Validation/Validator.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using static Timeline.Resources.Models.Validation.Validator; - -namespace Timeline.Models.Validation -{ - /// - /// A validator to validate value. - /// - public interface IValidator - { - /// - /// Validate given value. - /// - /// The value to validate. - /// Validation success or not and message. - (bool, string) Validate(object? value); - } - - public static class ValidatorExtensions - { - public static bool Validate(this IValidator validator, object? value, out string message) - { - if (validator == null) - throw new ArgumentNullException(nameof(validator)); - - var (r, m) = validator.Validate(value); - message = m; - return r; - } - } - - /// - /// Convenient base class for validator. - /// - /// The type of accepted value. - /// - /// Subclass should override to do the real validation. - /// This class will check the nullity and type of value. - /// If value is null, it will pass or fail depending on . - /// If value is not null and not of type - /// it will fail and not call . - /// - /// is true by default. - /// - /// If you want some other behaviours, write the validator from scratch. - /// - public abstract class Validator : IValidator - { - protected bool PermitNull { get; set; } = true; - - public (bool, string) Validate(object? value) - { - if (value == null) - { - if (PermitNull) - return (true, GetSuccessMessage()); - else - return (false, ValidatorMessageNull); - } - - if (value is T v) - { - return DoValidate(v); - } - else - { - return (false, ValidatorMessageBadType); - } - } - - protected static string GetSuccessMessage() => ValidatorMessageSuccess; - - protected abstract (bool, string) DoValidate(T value); - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, - AllowMultiple = false)] - public class ValidateWithAttribute : ValidationAttribute - { - private readonly IValidator _validator; - - /// - /// Create with a given validator. - /// - /// The validator used to validate. - public ValidateWithAttribute(IValidator validator) - { - _validator = validator ?? throw new ArgumentNullException(nameof(validator)); - } - - /// - /// Create the validator with default constructor. - /// - /// The type of the validator. - public ValidateWithAttribute(Type validatorType) - { - if (validatorType == null) - throw new ArgumentNullException(nameof(validatorType)); - - if (!typeof(IValidator).IsAssignableFrom(validatorType)) - throw new ArgumentException(ValidateWithAttributeExceptionNotValidator, nameof(validatorType)); - - try - { - _validator = (Activator.CreateInstance(validatorType) as IValidator)!; - } - catch (Exception e) - { - throw new ArgumentException(ValidateWithAttributeExceptionCreateFail, e); - } - } - - protected override ValidationResult IsValid(object value, ValidationContext validationContext) - { - var (result, message) = _validator.Validate(value); - if (result) - { - return ValidationResult.Success; - } - else - { - return new ValidationResult(message); - } - } - } -} diff --git a/Timeline/Program.cs b/Timeline/Program.cs deleted file mode 100644 index 87e330a2..00000000 --- a/Timeline/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System.Resources; -using Timeline.Entities; -using Timeline.Services; - -[assembly: NeutralResourcesLanguage("en")] - -namespace Timeline -{ - public static class Program - { - public static void Main(string[] args) - { - var host = CreateWebHostBuilder(args).Build(); - - var env = host.Services.GetRequiredService(); - - var databaseBackupService = host.Services.GetRequiredService(); - databaseBackupService.BackupNow(); - - if (env.IsProduction()) - { - using (var scope = host.Services.CreateScope()) - { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - databaseContext.Database.Migrate(); - } - } - - host.Run(); - } - - public static IHostBuilder CreateWebHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/Timeline/Properties/launchSettings.json b/Timeline/Properties/launchSettings.json deleted file mode 100644 index de8186db..00000000 --- a/Timeline/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "profiles": { - "Development": { - "commandName": "Project", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_USEPROXYFRONTEND": "true", - "ASPNETCORE_WORKDIR": "D:\\timeline-development" - } - }, - "Development-Mock": { - "commandName": "Project", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_USEMOCKFRONTEND": "true", - "ASPNETCORE_WORKDIR": "D:\\timeline-development" - } - }, - "Staging": { - "commandName": "Project", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Staging", - "ASPNETCORE_WORKDIR": "D:\\timeline-development" - } - } - } -} diff --git a/Timeline/Resources/Authentication/AuthHandler.Designer.cs b/Timeline/Resources/Authentication/AuthHandler.Designer.cs deleted file mode 100644 index fd4540ea..00000000 --- a/Timeline/Resources/Authentication/AuthHandler.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Authentication { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class AuthHandler { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal AuthHandler() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Authentication.AuthHandler", typeof(AuthHandler).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Token is found in authorization header. Token is {0} .. - /// - internal static string LogTokenFoundInHeader { - get { - return ResourceManager.GetString("LogTokenFoundInHeader", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Token is found in query param with key "{0}". Token is {1} .. - /// - internal static string LogTokenFoundInQuery { - get { - return ResourceManager.GetString("LogTokenFoundInQuery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No jwt token is found.. - /// - internal static string LogTokenNotFound { - get { - return ResourceManager.GetString("LogTokenNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A jwt token validation failed.. - /// - internal static string LogTokenValidationFail { - get { - return ResourceManager.GetString("LogTokenValidationFail", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Authentication/AuthHandler.resx b/Timeline/Resources/Authentication/AuthHandler.resx deleted file mode 100644 index 4cddc8ce..00000000 --- a/Timeline/Resources/Authentication/AuthHandler.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Token is found in authorization header. Token is {0} . - - - Token is found in query param with key "{0}". Token is {1} . - - - No jwt token is found. - - - A jwt token validation failed. - - \ No newline at end of file diff --git a/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs b/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs deleted file mode 100644 index 70a1d605..00000000 --- a/Timeline/Resources/Controllers/ControllerAuthExtensions.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Controllers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class ControllerAuthExtensions { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ControllerAuthExtensions() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.ControllerAuthExtensions", typeof(ControllerAuthExtensions).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Failed to get user id because User has no NameIdentifier claim.. - /// - internal static string ExceptionNoUserIdentifierClaim { - get { - return ResourceManager.GetString("ExceptionNoUserIdentifierClaim", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to get user id because NameIdentifier claim is not a number.. - /// - internal static string ExceptionUserIdentifierClaimBadFormat { - get { - return ResourceManager.GetString("ExceptionUserIdentifierClaimBadFormat", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Controllers/ControllerAuthExtensions.resx b/Timeline/Resources/Controllers/ControllerAuthExtensions.resx deleted file mode 100644 index 03e6d95a..00000000 --- a/Timeline/Resources/Controllers/ControllerAuthExtensions.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Failed to get user id because User has no NameIdentifier claim. - - - Failed to get user id because NameIdentifier claim is not a number. - - \ No newline at end of file diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs deleted file mode 100644 index ae6414e6..00000000 --- a/Timeline/Resources/Controllers/TimelineController.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Controllers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class TimelineController { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TimelineController() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.TimelineController", typeof(TimelineController).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to An unknown timeline visibility value. Can't convert it.. - /// - internal static string ExceptionStringToVisibility { - get { - return ResourceManager.GetString("ExceptionStringToVisibility", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown.. - /// - internal static string LogUnknownTimelineMemberOperationUserException { - get { - return ResourceManager.GetString("LogUnknownTimelineMemberOperationUserException", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx deleted file mode 100644 index 4cf3d6fb..00000000 --- a/Timeline/Resources/Controllers/TimelineController.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An unknown timeline visibility value. Can't convert it. - - - An unknown TimelineMemberOperationUserException is thrown. Can't recognize its inner exception. It is rethrown. - - \ No newline at end of file diff --git a/Timeline/Resources/Controllers/TokenController.Designer.cs b/Timeline/Resources/Controllers/TokenController.Designer.cs deleted file mode 100644 index a7c2864b..00000000 --- a/Timeline/Resources/Controllers/TokenController.Designer.cs +++ /dev/null @@ -1,153 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Controllers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class TokenController { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TokenController() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.TokenController", typeof(TokenController).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The password is wrong.. - /// - internal static string LogBadPassword { - get { - return ResourceManager.GetString("LogBadPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user failed to create a token.. - /// - internal static string LogCreateFailure { - get { - return ResourceManager.GetString("LogCreateFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user succeeded to create a token.. - /// - internal static string LogCreateSuccess { - get { - return ResourceManager.GetString("LogCreateSuccess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The user does not exist.. - /// - internal static string LogUserNotExist { - get { - return ResourceManager.GetString("LogUserNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad format. It might not be created by the server.. - /// - internal static string LogVerifyBadFormat { - get { - return ResourceManager.GetString("LogVerifyBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is expired.. - /// - internal static string LogVerifyExpire { - get { - return ResourceManager.GetString("LogVerifyExpire", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A token failed to be verified.. - /// - internal static string LogVerifyFailure { - get { - return ResourceManager.GetString("LogVerifyFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Token has an old version. User might have update some info.. - /// - internal static string LogVerifyOldVersion { - get { - return ResourceManager.GetString("LogVerifyOldVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A token succeeded to be verified.. - /// - internal static string LogVerifySuccess { - get { - return ResourceManager.GetString("LogVerifySuccess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user.. - /// - internal static string LogVerifyUserNotExist { - get { - return ResourceManager.GetString("LogVerifyUserNotExist", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Controllers/TokenController.resx b/Timeline/Resources/Controllers/TokenController.resx deleted file mode 100644 index 683d6cc9..00000000 --- a/Timeline/Resources/Controllers/TokenController.resx +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The password is wrong. - - - A user failed to create a token. - - - A user succeeded to create a token. - - - The user does not exist. - - - The token is of bad format. It might not be created by the server. - - - The token is expired. - - - A token failed to be verified. - - - Token has an old version. User might have update some info. - - - A token succeeded to be verified. - - - User does not exist. Administrator might have deleted this user. - - \ No newline at end of file diff --git a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs deleted file mode 100644 index b0c35ff9..00000000 --- a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs +++ /dev/null @@ -1,144 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Controllers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserAvatarController { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserAvatarController() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.UserAvatarController", typeof(UserAvatarController).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Unknown AvatarDataException.ErrorReason value.. - /// - internal static string ExceptionUnknownAvatarFormatError { - get { - return ResourceManager.GetString("ExceptionUnknownAvatarFormatError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to delete a avatar of other user as a non-admin failed.. - /// - internal static string LogDeleteForbid { - get { - return ResourceManager.GetString("LogDeleteForbid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to delete a avatar of a non-existent user failed.. - /// - internal static string LogDeleteNotExist { - get { - return ResourceManager.GetString("LogDeleteNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Succeed to delete a avatar of a user.. - /// - internal static string LogDeleteSuccess { - get { - return ResourceManager.GetString("LogDeleteSuccess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to get a avatar of a non-existent user failed.. - /// - internal static string LogGetUserNotExist { - get { - return ResourceManager.GetString("LogGetUserNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to put a avatar of other user as a non-admin failed.. - /// - internal static string LogPutForbid { - get { - return ResourceManager.GetString("LogPutForbid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Succeed to put a avatar of a user.. - /// - internal static string LogPutSuccess { - get { - return ResourceManager.GetString("LogPutSuccess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to put a avatar of a bad format failed.. - /// - internal static string LogPutUserBadFormat { - get { - return ResourceManager.GetString("LogPutUserBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to put a avatar of a non-existent user failed.. - /// - internal static string LogPutUserNotExist { - get { - return ResourceManager.GetString("LogPutUserNotExist", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Controllers/UserAvatarController.resx b/Timeline/Resources/Controllers/UserAvatarController.resx deleted file mode 100644 index 864d96c0..00000000 --- a/Timeline/Resources/Controllers/UserAvatarController.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Unknown AvatarDataException.ErrorReason value. - - - Attempt to delete a avatar of other user as a non-admin failed. - - - Attempt to delete a avatar of a non-existent user failed. - - - Succeed to delete a avatar of a user. - - - Attempt to get a avatar of a non-existent user failed. - - - Attempt to put a avatar of other user as a non-admin failed. - - - Succeed to put a avatar of a user. - - - Attempt to put a avatar of a bad format failed. - - - Attempt to put a avatar of a non-existent user failed. - - \ No newline at end of file diff --git a/Timeline/Resources/Controllers/UserController.Designer.cs b/Timeline/Resources/Controllers/UserController.Designer.cs deleted file mode 100644 index c8067614..00000000 --- a/Timeline/Resources/Controllers/UserController.Designer.cs +++ /dev/null @@ -1,117 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Controllers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserController { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserController() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Controllers.UserController", typeof(UserController).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Unknown PutResult.. - /// - internal static string ExceptionUnknownPutResult { - get { - return ResourceManager.GetString("ExceptionUnknownPutResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to change password with wrong old password failed.. - /// - internal static string LogChangePasswordBadPassword { - get { - return ResourceManager.GetString("LogChangePasswordBadPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to change a user's username to a existent one failed.. - /// - internal static string LogChangeUsernameConflict { - get { - return ResourceManager.GetString("LogChangeUsernameConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to change a username of a user that does not exist failed.. - /// - internal static string LogChangeUsernameNotExist { - get { - return ResourceManager.GetString("LogChangeUsernameNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to retrieve info of a user that does not exist failed.. - /// - internal static string LogGetUserNotExist { - get { - return ResourceManager.GetString("LogGetUserNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to patch a user that does not exist failed.. - /// - internal static string LogPatchUserNotExist { - get { - return ResourceManager.GetString("LogPatchUserNotExist", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Controllers/UserController.resx b/Timeline/Resources/Controllers/UserController.resx deleted file mode 100644 index 0bdf4845..00000000 --- a/Timeline/Resources/Controllers/UserController.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Unknown PutResult. - - - Attempt to change password with wrong old password failed. - - - Attempt to change a user's username to a existent one failed. - - - Attempt to change a username of a user that does not exist failed. - - - Attempt to retrieve info of a user that does not exist failed. - - - Attempt to patch a user that does not exist failed. - - \ No newline at end of file diff --git a/Timeline/Resources/Entities.Designer.cs b/Timeline/Resources/Entities.Designer.cs deleted file mode 100644 index 5f286f23..00000000 --- a/Timeline/Resources/Entities.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Entities { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Entities() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Entities", typeof(Entities).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Only sqlite is supported.. - /// - internal static string ExceptionOnlySqliteSupported { - get { - return ResourceManager.GetString("ExceptionOnlySqliteSupported", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Entities.resx b/Timeline/Resources/Entities.resx deleted file mode 100644 index 1538b533..00000000 --- a/Timeline/Resources/Entities.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Only sqlite is supported. - - \ No newline at end of file diff --git a/Timeline/Resources/Filters.Designer.cs b/Timeline/Resources/Filters.Designer.cs deleted file mode 100644 index dedfe498..00000000 --- a/Timeline/Resources/Filters.Designer.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Filters { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Filters() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Filters", typeof(Filters).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, but there is no user. Try add AuthorizeAttribute.. - /// - internal static string LogSelfOrAdminNoUser { - get { - return ResourceManager.GetString("LogSelfOrAdminNoUser", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, but it does not have a model named username.. - /// - internal static string LogSelfOrAdminNoUsername { - get { - return ResourceManager.GetString("LogSelfOrAdminNoUsername", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You apply a SelfOrAdminAttribute on an action, found a model named username, but it is not string.. - /// - internal static string LogSelfOrAdminUsernameNotString { - get { - return ResourceManager.GetString("LogSelfOrAdminUsernameNotString", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Filters.resx b/Timeline/Resources/Filters.resx deleted file mode 100644 index 22620889..00000000 --- a/Timeline/Resources/Filters.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - You apply a SelfOrAdminAttribute on an action, but there is no user. Try add AuthorizeAttribute. - - - You apply a SelfOrAdminAttribute on an action, but it does not have a model named username. - - - You apply a SelfOrAdminAttribute on an action, found a model named username, but it is not string. - - \ No newline at end of file diff --git a/Timeline/Resources/Helper/DataCacheHelper.Designer.cs b/Timeline/Resources/Helper/DataCacheHelper.Designer.cs deleted file mode 100644 index acf56d13..00000000 --- a/Timeline/Resources/Helper/DataCacheHelper.Designer.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Helper { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class DataCacheHelper { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal DataCacheHelper() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Helper.DataCacheHelper", typeof(DataCacheHelper).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Header If-None-Match is of bad format.. - /// - internal static string LogBadIfNoneMatch { - get { - return ResourceManager.GetString("LogBadIfNoneMatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cache is invalid and data is returned.. - /// - internal static string LogResultData { - get { - return ResourceManager.GetString("LogResultData", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cache is valid and 304 Not Modified is returned.. - /// - internal static string LogResultNotModified { - get { - return ResourceManager.GetString("LogResultNotModified", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Helper/DataCacheHelper.resx b/Timeline/Resources/Helper/DataCacheHelper.resx deleted file mode 100644 index 515cfa9b..00000000 --- a/Timeline/Resources/Helper/DataCacheHelper.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Header If-None-Match is of bad format. - - - Cache is invalid and data is returned. - - - Cache is valid and 304 Not Modified is returned. - - \ No newline at end of file diff --git a/Timeline/Resources/Messages.Designer.cs b/Timeline/Resources/Messages.Designer.cs deleted file mode 100644 index bb654ce6..00000000 --- a/Timeline/Resources/Messages.Designer.cs +++ /dev/null @@ -1,396 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Body is too big. It can't be bigger than {0}.. - /// - internal static string Common_Content_TooBig { - get { - return ResourceManager.GetString("Common_Content_TooBig", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Actual body length is bigger than it in header.. - /// - internal static string Common_Content_UnmatchedLength_Bigger { - get { - return ResourceManager.GetString("Common_Content_UnmatchedLength_Bigger", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Actual body length is smaller than it in header.. - /// - internal static string Common_Content_UnmatchedLength_Smaller { - get { - return ResourceManager.GetString("Common_Content_UnmatchedLength_Smaller", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have no permission to do the operation.. - /// - internal static string Common_Forbid { - get { - return ResourceManager.GetString("Common_Forbid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You are not the resource owner.. - /// - internal static string Common_Forbid_NotSelf { - get { - return ResourceManager.GetString("Common_Forbid_NotSelf", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Header Content-Length is missing or of bad format.. - /// - internal static string Common_Header_ContentLength_Missing { - get { - return ResourceManager.GetString("Common_Header_ContentLength_Missing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Header Content-Length must not be 0.. - /// - internal static string Common_Header_ContentLength_Zero { - get { - return ResourceManager.GetString("Common_Header_ContentLength_Zero", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Header Content-Type is missing.. - /// - internal static string Common_Header_ContentType_Missing { - get { - return ResourceManager.GetString("Common_Header_ContentType_Missing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Header If-Non-Match is of bad format.. - /// - internal static string Common_Header_IfNonMatch_BadFormat { - get { - return ResourceManager.GetString("Common_Header_IfNonMatch_BadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Model is of bad format.. - /// - internal static string Common_InvalidModel { - get { - return ResourceManager.GetString("Common_InvalidModel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The api endpoint you request is unknown. You might get the wrong api entry.. - /// - internal static string Common_UnknownEndpoint { - get { - return ResourceManager.GetString("Common_UnknownEndpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown type of post content.. - /// - internal static string TimelineController_ContentUnknownType { - get { - return ResourceManager.GetString("TimelineController_ContentUnknownType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data field is not a valid base64 string in image content.. - /// - internal static string TimelineController_ImageContentDataNotBase64 { - get { - return ResourceManager.GetString("TimelineController_ImageContentDataNotBase64", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data field is not a valid image after base64 decoding in image content.. - /// - internal static string TimelineController_ImageContentDataNotImage { - get { - return ResourceManager.GetString("TimelineController_ImageContentDataNotImage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data field is required for image content.. - /// - internal static string TimelineController_ImageContentDataRequired { - get { - return ResourceManager.GetString("TimelineController_ImageContentDataRequired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The user to set as member does not exist.. - /// - internal static string TimelineController_MemberPut_NotExist { - get { - return ResourceManager.GetString("TimelineController_MemberPut_NotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A timeline with given name already exists.. - /// - internal static string TimelineController_NameConflict { - get { - return ResourceManager.GetString("TimelineController_NameConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The timeline with given name does not exist.. - /// - internal static string TimelineController_NotExist { - get { - return ResourceManager.GetString("TimelineController_NotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The post of that type has no data.. - /// - internal static string TimelineController_PostNoData { - get { - return ResourceManager.GetString("TimelineController_PostNoData", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The post to operate on does not exist.. - /// - internal static string TimelineController_PostNotExist { - get { - return ResourceManager.GetString("TimelineController_PostNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The user specified by query param "relate" does not exist.. - /// - internal static string TimelineController_QueryRelateNotExist { - get { - return ResourceManager.GetString("TimelineController_QueryRelateNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is an unkown visibility in the query parameter 'visibility'. . - /// - internal static string TimelineController_QueryVisibilityUnknown { - get { - return ResourceManager.GetString("TimelineController_QueryVisibilityUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Text field is required for text content.. - /// - internal static string TimelineController_TextContentTextRequired { - get { - return ResourceManager.GetString("TimelineController_TextContentTextRequired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username or password is invalid.. - /// - internal static string TokenController_Create_BadCredential { - get { - return ResourceManager.GetString("TokenController_Create_BadCredential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad format. It might not be created by the server.. - /// - internal static string TokenController_Verify_BadFormat { - get { - return ResourceManager.GetString("TokenController_Verify_BadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Token has an old version. User might have update some info.. - /// - internal static string TokenController_Verify_OldVersion { - get { - return ResourceManager.GetString("TokenController_Verify_OldVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is expired.. - /// - internal static string TokenController_Verify_TimeExpired { - get { - return ResourceManager.GetString("TokenController_Verify_TimeExpired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User does not exist. Administrator might have deleted this user.. - /// - internal static string TokenController_Verify_UserNotExist { - get { - return ResourceManager.GetString("TokenController_Verify_UserNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image is not a square.. - /// - internal static string UserAvatar_BadFormat_BadSize { - get { - return ResourceManager.GetString("UserAvatar_BadFormat_BadSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image decode failed.. - /// - internal static string UserAvatar_BadFormat_CantDecode { - get { - return ResourceManager.GetString("UserAvatar_BadFormat_CantDecode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image format does not match the one in header.. - /// - internal static string UserAvatar_BadFormat_UnmatchedFormat { - get { - return ResourceManager.GetString("UserAvatar_BadFormat_UnmatchedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The user to operate on does not exist.. - /// - internal static string UserCommon_NotExist { - get { - return ResourceManager.GetString("UserCommon_NotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Old password is wrong.. - /// - internal static string UserController_ChangePassword_BadOldPassword { - get { - return ResourceManager.GetString("UserController_ChangePassword_BadOldPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You can't set permission unless you are administrator.. - /// - internal static string UserController_Patch_Forbid_Administrator { - get { - return ResourceManager.GetString("UserController_Patch_Forbid_Administrator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You can't set password unless you are administrator. If you want to change password, use /userop/changepassword .. - /// - internal static string UserController_Patch_Forbid_Password { - get { - return ResourceManager.GetString("UserController_Patch_Forbid_Password", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You can't set username unless you are administrator.. - /// - internal static string UserController_Patch_Forbid_Username { - get { - return ResourceManager.GetString("UserController_Patch_Forbid_Username", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user with given username already exists.. - /// - internal static string UserController_UsernameConflict { - get { - return ResourceManager.GetString("UserController_UsernameConflict", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Messages.resx b/Timeline/Resources/Messages.resx deleted file mode 100644 index 2bbf494e..00000000 --- a/Timeline/Resources/Messages.resx +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Body is too big. It can't be bigger than {0}. - - - Actual body length is bigger than it in header. - - - Actual body length is smaller than it in header. - - - You have no permission to do the operation. - - - You are not the resource owner. - - - Header Content-Length is missing or of bad format. - - - Header Content-Length must not be 0. - - - Header Content-Type is missing. - - - Header If-Non-Match is of bad format. - - - Model is of bad format. - - - The api endpoint you request is unknown. You might get the wrong api entry. - - - Unknown type of post content. - - - Data field is not a valid base64 string in image content. - - - Data field is not a valid image after base64 decoding in image content. - - - Data field is required for image content. - - - The user to set as member does not exist. - - - A timeline with given name already exists. - - - The timeline with given name does not exist. - - - The post of that type has no data. - - - The post to operate on does not exist. - - - The user specified by query param "relate" does not exist. - - - '{0}' is an unkown visibility in the query parameter 'visibility'. - - - Text field is required for text content. - - - Username or password is invalid. - - - The token is of bad format. It might not be created by the server. - - - Token has an old version. User might have update some info. - - - The token is expired. - - - User does not exist. Administrator might have deleted this user. - - - Image is not a square. - - - Image decode failed. - - - Image format does not match the one in header. - - - The user to operate on does not exist. - - - Old password is wrong. - - - You can't set permission unless you are administrator. - - - You can't set password unless you are administrator. If you want to change password, use /userop/changepassword . - - - You can't set username unless you are administrator. - - - A user with given username already exists. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Http/Common.Designer.cs b/Timeline/Resources/Models/Http/Common.Designer.cs deleted file mode 100644 index 5165463e..00000000 --- a/Timeline/Resources/Models/Http/Common.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Http { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Common { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Common() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Http.Common", typeof(Common).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to An existent item is deleted.. - /// - internal static string MessageDeleteDelete { - get { - return ResourceManager.GetString("MessageDeleteDelete", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The item does not exist, so nothing is changed.. - /// - internal static string MessageDeleteNotExist { - get { - return ResourceManager.GetString("MessageDeleteNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A new item is created.. - /// - internal static string MessagePutCreate { - get { - return ResourceManager.GetString("MessagePutCreate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An existent item is modified.. - /// - internal static string MessagePutModify { - get { - return ResourceManager.GetString("MessagePutModify", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Http/Common.resx b/Timeline/Resources/Models/Http/Common.resx deleted file mode 100644 index 85ec4d32..00000000 --- a/Timeline/Resources/Models/Http/Common.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An existent item is deleted. - - - The item does not exist, so nothing is changed. - - - A new item is created. - - - An existent item is modified. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Http/Exception.Designer.cs b/Timeline/Resources/Models/Http/Exception.Designer.cs deleted file mode 100644 index 19f42793..00000000 --- a/Timeline/Resources/Models/Http/Exception.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Http { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Exception { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Exception() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Http.Exception", typeof(Exception).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to No action context currently, can't fill urls in value resolver.. - /// - internal static string ActionContextNull { - get { - return ResourceManager.GetString("ActionContextNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown post content type.. - /// - internal static string UnknownPostContentType { - get { - return ResourceManager.GetString("UnknownPostContentType", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Http/Exception.resx b/Timeline/Resources/Models/Http/Exception.resx deleted file mode 100644 index 3f7bddb6..00000000 --- a/Timeline/Resources/Models/Http/Exception.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - No action context currently, can't fill urls in value resolver. - - - Unknown post content type. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/NameValidator.Designer.cs b/Timeline/Resources/Models/Validation/NameValidator.Designer.cs deleted file mode 100644 index 3050049e..00000000 --- a/Timeline/Resources/Models/Validation/NameValidator.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Validation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class NameValidator { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal NameValidator() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.NameValidator", typeof(NameValidator).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to An empty string is not allowed.. - /// - internal static string MessageEmptyString { - get { - return ResourceManager.GetString("MessageEmptyString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid character, only alphabet, digit, underscore and hyphen are allowed.. - /// - internal static string MessageInvalidChar { - get { - return ResourceManager.GetString("MessageInvalidChar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Too long, more than 26 characters is not premitted.. - /// - internal static string MessageTooLong { - get { - return ResourceManager.GetString("MessageTooLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Name can't be of the same format of unique id.. - /// - internal static string MessageUnqiueId { - get { - return ResourceManager.GetString("MessageUnqiueId", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Validation/NameValidator.resx b/Timeline/Resources/Models/Validation/NameValidator.resx deleted file mode 100644 index 5e7e1745..00000000 --- a/Timeline/Resources/Models/Validation/NameValidator.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An empty string is not allowed. - - - Invalid character, only alphabet, digit, underscore and hyphen are allowed. - - - Too long, more than 26 characters is not premitted. - - - Name can't be of the same format of unique id. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs b/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs deleted file mode 100644 index 522f305a..00000000 --- a/Timeline/Resources/Models/Validation/NicknameValidator.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Validation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class NicknameValidator { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal NicknameValidator() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.NicknameValidator", typeof(NicknameValidator).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Nickname is too long.. - /// - internal static string MessageTooLong { - get { - return ResourceManager.GetString("MessageTooLong", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Validation/NicknameValidator.resx b/Timeline/Resources/Models/Validation/NicknameValidator.resx deleted file mode 100644 index b191b505..00000000 --- a/Timeline/Resources/Models/Validation/NicknameValidator.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Nickname is too long. - - \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/Validator.Designer.cs b/Timeline/Resources/Models/Validation/Validator.Designer.cs deleted file mode 100644 index 74d4c169..00000000 --- a/Timeline/Resources/Models/Validation/Validator.Designer.cs +++ /dev/null @@ -1,108 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Validation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Validator { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Validator() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.Validator", typeof(Validator).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Failed to create a validator instance from default constructor. See inner exception.. - /// - internal static string ValidateWithAttributeExceptionCreateFail { - get { - return ResourceManager.GetString("ValidateWithAttributeExceptionCreateFail", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Given type is not assignable to IValidator.. - /// - internal static string ValidateWithAttributeExceptionNotValidator { - get { - return ResourceManager.GetString("ValidateWithAttributeExceptionNotValidator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value is not of type {0}.. - /// - internal static string ValidatorMessageBadType { - get { - return ResourceManager.GetString("ValidatorMessageBadType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value can't be null.. - /// - internal static string ValidatorMessageNull { - get { - return ResourceManager.GetString("ValidatorMessageNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Validation succeeded.. - /// - internal static string ValidatorMessageSuccess { - get { - return ResourceManager.GetString("ValidatorMessageSuccess", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Validation/Validator.resx b/Timeline/Resources/Models/Validation/Validator.resx deleted file mode 100644 index 8317e3eb..00000000 --- a/Timeline/Resources/Models/Validation/Validator.resx +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Failed to create a validator instance from default constructor. See inner exception. - - - Given type is not assignable to IValidator. - - - Value is not of type {0}. - - - Value can't be null. - - - Validation succeeded. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/DataManager.Designer.cs b/Timeline/Resources/Services/DataManager.Designer.cs deleted file mode 100644 index 0872059a..00000000 --- a/Timeline/Resources/Services/DataManager.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class DataManager { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal DataManager() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.DataManager", typeof(DataManager).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Entry with given tag does not exist.. - /// - internal static string ExceptionEntryNotExist { - get { - return ResourceManager.GetString("ExceptionEntryNotExist", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/DataManager.resx b/Timeline/Resources/Services/DataManager.resx deleted file mode 100644 index 688e0e96..00000000 --- a/Timeline/Resources/Services/DataManager.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Entry with given tag does not exist. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs deleted file mode 100644 index 21ca7b86..00000000 --- a/Timeline/Resources/Services/Exception.Designer.cs +++ /dev/null @@ -1,234 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Exception { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Exception() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.Exception", typeof(Exception).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The password is wrong.. - /// - internal static string BadPasswordException { - get { - return ResourceManager.GetString("BadPasswordException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server.. - /// - internal static string HashedPasswordBadFromatException { - get { - return ResourceManager.GetString("HashedPasswordBadFromatException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not of valid base64 format. See inner exception.. - /// - internal static string HashedPasswordBadFromatExceptionNotBase64 { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotBase64", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Decoded hashed password is of length 0.. - /// - internal static string HashedPasswordBadFromatExceptionNotLength0 { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotLength0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to See inner exception.. - /// - internal static string HashedPasswordBadFromatExceptionNotOthers { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotOthers", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Salt length < 128 bits.. - /// - internal static string HashedPasswordBadFromatExceptionNotSaltTooShort { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSaltTooShort", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Subkey length < 128 bits.. - /// - internal static string HashedPasswordBadFromatExceptionNotSubkeyTooShort { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSubkeyTooShort", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown format marker.. - /// - internal static string HashedPasswordBadFromatExceptionNotUnknownMarker { - get { - return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotUnknownMarker", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token didn't pass verification because {0}.. - /// - internal static string JwtUserTokenBadFormatException { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to id claim is not a number. - /// - internal static string JwtUserTokenBadFormatExceptionIdBadFormat { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionIdBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to id claim does not exist. - /// - internal static string JwtUserTokenBadFormatExceptionIdMissing { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionIdMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to other error, see inner exception for information. - /// - internal static string JwtUserTokenBadFormatExceptionOthers { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionOthers", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to unknown error. - /// - internal static string JwtUserTokenBadFormatExceptionUnknown { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to version claim is not a number.. - /// - internal static string JwtUserTokenBadFormatExceptionVersionBadFormat { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionVersionBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to version claim does not exist.. - /// - internal static string JwtUserTokenBadFormatExceptionVersionMissing { - get { - return ResourceManager.GetString("JwtUserTokenBadFormatExceptionVersionMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password is of bad format.. - /// - internal static string PasswordBadFormatException { - get { - return ResourceManager.GetString("PasswordBadFormatException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad format, which means it may not be created by the server.. - /// - internal static string UserTokenBadFormatException { - get { - return ResourceManager.GetString("UserTokenBadFormatException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is of bad version.. - /// - internal static string UserTokenBadVersionException { - get { - return ResourceManager.GetString("UserTokenBadVersionException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The token is expired because its expiration time has passed.. - /// - internal static string UserTokenTimeExpireException { - get { - return ResourceManager.GetString("UserTokenTimeExpireException", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx deleted file mode 100644 index c31ed7c7..00000000 --- a/Timeline/Resources/Services/Exception.resx +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The password is wrong. - - - The hashes password is of bad format. It might not be created by server. - - - Not of valid base64 format. See inner exception. - - - Decoded hashed password is of length 0. - - - See inner exception. - - - Salt length < 128 bits. - - - Subkey length < 128 bits. - - - Unknown format marker. - - - The token didn't pass verification because {0}. - - - id claim is not a number - - - id claim does not exist - - - other error, see inner exception for information - - - unknown error - - - version claim is not a number. - - - version claim does not exist. - - - Password is of bad format. - - - The token is of bad format, which means it may not be created by the server. - - - The token is of bad version. - - - The token is expired because its expiration time has passed. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/Exceptions.Designer.cs b/Timeline/Resources/Services/Exceptions.Designer.cs deleted file mode 100644 index 1dbe11c9..00000000 --- a/Timeline/Resources/Services/Exceptions.Designer.cs +++ /dev/null @@ -1,189 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Exceptions { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Exceptions() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.Exceptions", typeof(Exceptions).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to A entity of type "{0}" already exists.. - /// - internal static string EntityAlreadyExistError { - get { - return ResourceManager.GetString("EntityAlreadyExistError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The entity already exists.. - /// - internal static string EntityAlreadyExistErrorDefault { - get { - return ResourceManager.GetString("EntityAlreadyExistErrorDefault", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The required entity of type "{0}" does not exist.. - /// - internal static string EntityNotExistError { - get { - return ResourceManager.GetString("EntityNotExistError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The required entity does not exist.. - /// - internal static string EntityNotExistErrorDefault { - get { - return ResourceManager.GetString("EntityNotExistErrorDefault", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image is in valid because {0}.. - /// - internal static string ImageException { - get { - return ResourceManager.GetString("ImageException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to image is not of required size. - /// - internal static string ImageExceptionBadSize { - get { - return ResourceManager.GetString("ImageExceptionBadSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to failed to decode image, see inner exception. - /// - internal static string ImageExceptionCantDecode { - get { - return ResourceManager.GetString("ImageExceptionCantDecode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to unknown error. - /// - internal static string ImageExceptionUnknownError { - get { - return ResourceManager.GetString("ImageExceptionUnknownError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to image's actual mime type is not the specified one. - /// - internal static string ImageExceptionUnmatchedFormat { - get { - return ResourceManager.GetString("ImageExceptionUnmatchedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The timeline has no data.. - /// - internal static string TimelineNoDataException { - get { - return ResourceManager.GetString("TimelineNoDataException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request timeline name is "{0}". If this is a personal timeline whose name starts with '@', it means the user does not exist and inner exception should be a UserNotExistException.. - /// - internal static string TimelineNotExistException { - get { - return ResourceManager.GetString("TimelineNotExistException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request timeline name is "{0}". Request timeline post id is "{1}".. - /// - internal static string TimelinePostNotExistException { - get { - return ResourceManager.GetString("TimelinePostNotExistException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request timeline name is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted.. - /// - internal static string TimelinePostNotExistExceptionDeleted { - get { - return ResourceManager.GetString("TimelinePostNotExistExceptionDeleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request username is "{0}". Request id is "{1}".. - /// - internal static string UserNotExistException { - get { - return ResourceManager.GetString("UserNotExistException", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/Exceptions.resx b/Timeline/Resources/Services/Exceptions.resx deleted file mode 100644 index e9595caa..00000000 --- a/Timeline/Resources/Services/Exceptions.resx +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The required entity of type "{0}" does not exist. - - - The entity already exists. - - - A entity of type "{0}" already exists. - - - The required entity does not exist. - - - Request timeline name is "{0}". If this is a personal timeline whose name starts with '@', it means the user does not exist and inner exception should be a UserNotExistException. - - - Request timeline name is "{0}". Request timeline post id is "{1}". - - - Request username is "{0}". Request id is "{1}". - - - The timeline has no data. - - - Image is in valid because {0}. - - - image is not of required size - - - failed to decode image, see inner exception - - - unknown error - - - image's actual mime type is not the specified one - - - Request timeline name is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/TimelineService.Designer.cs b/Timeline/Resources/Services/TimelineService.Designer.cs deleted file mode 100644 index e16c1337..00000000 --- a/Timeline/Resources/Services/TimelineService.Designer.cs +++ /dev/null @@ -1,144 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class TimelineService { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TimelineService() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.TimelineService", typeof(TimelineService).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The number {0} username is invalid.. - /// - internal static string ExceptionChangeMemberUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionChangeMemberUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown post content type "{0}" is saved in database.. - /// - internal static string ExceptionDatabaseUnknownContentType { - get { - return ResourceManager.GetString("ExceptionDatabaseUnknownContentType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The owner username of personal timeline is of bad format.. - /// - internal static string ExceptionFindTimelineUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionFindTimelineUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The data entry of the tag of the image post does not exist.. - /// - internal static string ExceptionGetDataDataEntryNotExist { - get { - return ResourceManager.GetString("ExceptionGetDataDataEntryNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Can't get data of a non-image post.. - /// - internal static string ExceptionGetDataNonImagePost { - get { - return ResourceManager.GetString("ExceptionGetDataNonImagePost", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The post has been deleted because content of entity is null.. - /// - internal static string ExceptionPostDeleted { - get { - return ResourceManager.GetString("ExceptionPostDeleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The timeline name is of bad format.. - /// - internal static string ExceptionTimelineNameBadFormat { - get { - return ResourceManager.GetString("ExceptionTimelineNameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The timeline with given name already exists.. - /// - internal static string ExceptionTimelineNameConflict { - get { - return ResourceManager.GetString("ExceptionTimelineNameConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image format type of the post does not exist in column "extra_content". Normally this couldn't be possible because it should be saved when post was created. However, we now re-detect the format and save it.. - /// - internal static string LogGetDataNoFormat { - get { - return ResourceManager.GetString("LogGetDataNoFormat", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/TimelineService.resx b/Timeline/Resources/Services/TimelineService.resx deleted file mode 100644 index 9314f51b..00000000 --- a/Timeline/Resources/Services/TimelineService.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The number {0} username is invalid. - - - Unknown post content type "{0}" is saved in database. - - - The owner username of personal timeline is of bad format. - - - The data entry of the tag of the image post does not exist. - - - Can't get data of a non-image post. - - - The timeline name is of bad format. - - - The timeline with given name already exists. - - - Image format type of the post does not exist in column "extra_content". Normally this couldn't be possible because it should be saved when post was created. However, we now re-detect the format and save it. - - - The post has been deleted because content of entity is null. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/UserAvatarService.Designer.cs b/Timeline/Resources/Services/UserAvatarService.Designer.cs deleted file mode 100644 index c72d4215..00000000 --- a/Timeline/Resources/Services/UserAvatarService.Designer.cs +++ /dev/null @@ -1,108 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserAvatarService { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserAvatarService() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserAvatarService", typeof(UserAvatarService).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Data of avatar is null.. - /// - internal static string ExceptionAvatarDataNull { - get { - return ResourceManager.GetString("ExceptionAvatarDataNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type of avatar is null or empty.. - /// - internal static string ExceptionAvatarTypeNullOrEmpty { - get { - return ResourceManager.GetString("ExceptionAvatarTypeNullOrEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Database corupted! One of type and data of a avatar is null but the other is not.. - /// - internal static string ExceptionDatabaseCorruptedDataAndTypeNotSame { - get { - return ResourceManager.GetString("ExceptionDatabaseCorruptedDataAndTypeNotSame", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Created an entry in user_avatars.. - /// - internal static string LogCreateEntity { - get { - return ResourceManager.GetString("LogCreateEntity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Updated an entry in user_avatars.. - /// - internal static string LogUpdateEntity { - get { - return ResourceManager.GetString("LogUpdateEntity", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/UserAvatarService.resx b/Timeline/Resources/Services/UserAvatarService.resx deleted file mode 100644 index da9d7203..00000000 --- a/Timeline/Resources/Services/UserAvatarService.resx +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Data of avatar is null. - - - Type of avatar is null or empty. - - - Database corupted! One of type and data of a avatar is null but the other is not. - - - Created an entry in user_avatars. - - - Updated an entry in user_avatars. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/UserService.Designer.cs b/Timeline/Resources/Services/UserService.Designer.cs deleted file mode 100644 index cdf7f390..00000000 --- a/Timeline/Resources/Services/UserService.Designer.cs +++ /dev/null @@ -1,162 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserService { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserService() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserService", typeof(UserService).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to New username is of bad format.. - /// - internal static string ExceptionNewUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionNewUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Nickname is of bad format, because {}.. - /// - internal static string ExceptionNicknameBadFormat { - get { - return ResourceManager.GetString("ExceptionNicknameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Old username is of bad format.. - /// - internal static string ExceptionOldUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionOldUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password can't be empty.. - /// - internal static string ExceptionPasswordEmpty { - get { - return ResourceManager.GetString("ExceptionPasswordEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password can't be null.. - /// - internal static string ExceptionPasswordNull { - get { - return ResourceManager.GetString("ExceptionPasswordNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username is of bad format, because {}.. - /// - internal static string ExceptionUsernameBadFormat { - get { - return ResourceManager.GetString("ExceptionUsernameBadFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user with given username already exists.. - /// - internal static string ExceptionUsernameConflict { - get { - return ResourceManager.GetString("ExceptionUsernameConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username can't be null.. - /// - internal static string ExceptionUsernameNull { - get { - return ResourceManager.GetString("ExceptionUsernameNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A new user entry is added to the database.. - /// - internal static string LogDatabaseCreate { - get { - return ResourceManager.GetString("LogDatabaseCreate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user entry is removed from the database.. - /// - internal static string LogDatabaseRemove { - get { - return ResourceManager.GetString("LogDatabaseRemove", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user entry is updated to the database.. - /// - internal static string LogDatabaseUpdate { - get { - return ResourceManager.GetString("LogDatabaseUpdate", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/UserService.resx b/Timeline/Resources/Services/UserService.resx deleted file mode 100644 index 09bd4abb..00000000 --- a/Timeline/Resources/Services/UserService.resx +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - New username is of bad format. - - - Nickname is of bad format, because {}. - - - Old username is of bad format. - - - Password can't be empty. - - - Password can't be null. - - - Username is of bad format, because {}. - - - A user with given username already exists. - - - Username can't be null. - - - A new user entry is added to the database. - - - A user entry is removed from the database. - - - A user entry is updated to the database. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/UserTokenService.Designer.cs b/Timeline/Resources/Services/UserTokenService.Designer.cs deleted file mode 100644 index 3c3c7e41..00000000 --- a/Timeline/Resources/Services/UserTokenService.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Services { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UserTokenService { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UserTokenService() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Services.UserTokenService", typeof(UserTokenService).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Jwt token key is not set in database. Maybe you forget to migrate the database.. - /// - internal static string JwtKeyNotExist { - get { - return ResourceManager.GetString("JwtKeyNotExist", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Services/UserTokenService.resx b/Timeline/Resources/Services/UserTokenService.resx deleted file mode 100644 index 1ce78427..00000000 --- a/Timeline/Resources/Services/UserTokenService.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Jwt token key is not set in database. Maybe you forget to migrate the database. - - \ No newline at end of file diff --git a/Timeline/Routes/ApiRoutePrefixConvention.cs b/Timeline/Routes/ApiRoutePrefixConvention.cs deleted file mode 100644 index ca38a0d9..00000000 --- a/Timeline/Routes/ApiRoutePrefixConvention.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; -using System.Linq; - -namespace Timeline.Routes -{ - public static class MvcOptionsExtensions - { - public static void UseApiRoutePrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute) - { - opts.Conventions.Add(new ApiRoutePrefixConvention(routeAttribute)); - } - - public static void UseApiRoutePrefix(this MvcOptions opts, string prefix) - { - opts.UseApiRoutePrefix(new RouteAttribute(prefix)); - } - } - - public class ApiRoutePrefixConvention : IApplicationModelConvention - { - private readonly AttributeRouteModel _routePrefix; - - public ApiRoutePrefixConvention(IRouteTemplateProvider route) - { - _routePrefix = new AttributeRouteModel(route); - } - - public void Apply(ApplicationModel application) - { - foreach (var selector in application.Controllers.Where(c => c.Filters.Any(f => f is IApiBehaviorMetadata)).SelectMany(c => c.Selectors)) - { - if (selector.AttributeRouteModel != null) - { - selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel); - } - else - { - selector.AttributeRouteModel = _routePrefix; - } - } - } - } -} diff --git a/Timeline/Routes/UnknownEndpointMiddleware.cs b/Timeline/Routes/UnknownEndpointMiddleware.cs deleted file mode 100644 index 25ec563c..00000000 --- a/Timeline/Routes/UnknownEndpointMiddleware.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using System; -using System.Net.Mime; -using System.Text.Json; -using Timeline.Models.Http; - -namespace Timeline.Routes -{ - public static class UnknownEndpointMiddleware - { - public static void Attach(IApplicationBuilder app) - { - app.Use(async (context, next) => - { - if (context.GetEndpoint() != null) - { - await next(); - return; - } - - if (context.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)) - { - context.Response.StatusCode = StatusCodes.Status400BadRequest; - context.Response.ContentType = MediaTypeNames.Application.Json; - - var body = JsonSerializer.SerializeToUtf8Bytes(ErrorResponse.Common.UnknownEndpoint(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - - context.Response.ContentLength = body.Length; - await context.Response.Body.WriteAsync(body); - await context.Response.CompleteAsync(); - return; - } - - await next(); - }); - } - } -} diff --git a/Timeline/Services/BadPasswordException.cs b/Timeline/Services/BadPasswordException.cs deleted file mode 100644 index f609371d..00000000 --- a/Timeline/Services/BadPasswordException.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Timeline.Helpers; - -namespace Timeline.Services -{ - [Serializable] - public class BadPasswordException : Exception - { - public BadPasswordException() : base(Resources.Services.Exception.BadPasswordException) { } - public BadPasswordException(string message, Exception inner) : base(message, inner) { } - - public BadPasswordException(string badPassword) - : base(Log.Format(Resources.Services.Exception.BadPasswordException, ("Bad Password", badPassword))) - { - Password = badPassword; - } - - protected BadPasswordException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - /// - /// The wrong password. - /// - public string? Password { get; set; } - } -} diff --git a/Timeline/Services/Clock.cs b/Timeline/Services/Clock.cs deleted file mode 100644 index 4395edcd..00000000 --- a/Timeline/Services/Clock.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Timeline.Services -{ - /// - /// Convenient for unit test. - /// - public interface IClock - { - /// - /// Get current time. - /// - /// Current time. - DateTime GetCurrentTime(); - } - - public class Clock : IClock - { - public Clock() - { - - } - - public DateTime GetCurrentTime() - { - return DateTime.UtcNow; - } - } -} diff --git a/Timeline/Services/DataManager.cs b/Timeline/Services/DataManager.cs deleted file mode 100644 index d447b0d5..00000000 --- a/Timeline/Services/DataManager.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; - -namespace Timeline.Services -{ - /// - /// A data manager controlling data. - /// - /// - /// Identical data will be saved as one copy and return the same tag. - /// Every data has a ref count. When data is retained, ref count increase. - /// When data is freed, ref count decease. If ref count is decreased - /// to 0, the data entry will be destroyed and no longer occupy space. - /// - public interface IDataManager - { - /// - /// Saves the data to a new entry if it does not exist, - /// increases its ref count and returns a tag to the entry. - /// - /// The data. Can't be null. - /// The tag of the created entry. - /// Thrown when is null. - public Task RetainEntry(byte[] data); - - /// - /// Decrease the the ref count of the entry. - /// Remove it if ref count is zero. - /// - /// The tag of the entry. - /// Thrown when is null. - /// - /// It's no-op if entry with tag does not exist. - /// - public Task FreeEntry(string tag); - - /// - /// Retrieve the entry with given tag. - /// - /// The tag of the entry. - /// The data of the entry. - /// Thrown when is null. - /// Thrown when entry with given tag does not exist. - public Task GetEntry(string tag); - } - - public class DataManager : IDataManager - { - private readonly DatabaseContext _database; - private readonly IETagGenerator _eTagGenerator; - - public DataManager(DatabaseContext database, IETagGenerator eTagGenerator) - { - _database = database; - _eTagGenerator = eTagGenerator; - } - - public async Task RetainEntry(byte[] data) - { - if (data == null) - throw new ArgumentNullException(nameof(data)); - - var tag = await _eTagGenerator.Generate(data); - - var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(); - - if (entity == null) - { - entity = new DataEntity - { - Tag = tag, - Data = data, - Ref = 1 - }; - _database.Data.Add(entity); - } - else - { - entity.Ref += 1; - } - await _database.SaveChangesAsync(); - return tag; - } - - public async Task FreeEntry(string tag) - { - if (tag == null) - throw new ArgumentNullException(nameof(tag)); - - var entity = await _database.Data.Where(d => d.Tag == tag).SingleOrDefaultAsync(); - - if (entity != null) - { - if (entity.Ref == 1) - { - _database.Data.Remove(entity); - } - else - { - entity.Ref -= 1; - } - await _database.SaveChangesAsync(); - } - } - - public async Task GetEntry(string tag) - { - if (tag == null) - throw new ArgumentNullException(nameof(tag)); - - var entity = await _database.Data.Where(d => d.Tag == tag).Select(d => new { d.Data }).SingleOrDefaultAsync(); - - if (entity == null) - throw new InvalidOperationException(Resources.Services.DataManager.ExceptionEntryNotExist); - - return entity.Data; - } - } -} diff --git a/Timeline/Services/DatabaseBackupService.cs b/Timeline/Services/DatabaseBackupService.cs deleted file mode 100644 index a76b2a0d..00000000 --- a/Timeline/Services/DatabaseBackupService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Globalization; -using System.IO; - -namespace Timeline.Services -{ - public interface IDatabaseBackupService - { - void BackupNow(); - } - - public class DatabaseBackupService : IDatabaseBackupService - { - private readonly IPathProvider _pathProvider; - private readonly IClock _clock; - - public DatabaseBackupService(IPathProvider pathProvider, IClock clock) - { - _pathProvider = pathProvider; - _clock = clock; - } - - public void BackupNow() - { - var databasePath = _pathProvider.GetDatabaseFilePath(); - if (File.Exists(databasePath)) - { - var backupDirPath = _pathProvider.GetDatabaseBackupDirectory(); - Directory.CreateDirectory(backupDirPath); - var fileName = _clock.GetCurrentTime().ToString("yyyy-MM-ddTHH-mm-ss", CultureInfo.InvariantCulture); - var path = Path.Combine(backupDirPath, fileName); - File.Copy(databasePath, path); - } - } - } -} diff --git a/Timeline/Services/DatabaseCorruptedException.cs b/Timeline/Services/DatabaseCorruptedException.cs deleted file mode 100644 index 9988e0ad..00000000 --- a/Timeline/Services/DatabaseCorruptedException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Timeline.Services -{ - [Serializable] - public class DatabaseCorruptedException : Exception - { - public DatabaseCorruptedException() { } - public DatabaseCorruptedException(string message) : base(message) { } - public DatabaseCorruptedException(string message, Exception inner) : base(message, inner) { } - protected DatabaseCorruptedException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/Timeline/Services/ETagGenerator.cs b/Timeline/Services/ETagGenerator.cs deleted file mode 100644 index 4493e903..00000000 --- a/Timeline/Services/ETagGenerator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Security.Cryptography; -using System.Threading.Tasks; - -namespace Timeline.Services -{ - public interface IETagGenerator - { - /// - /// Generate a etag for given source. - /// - /// The source data. - /// The generated etag. - /// Thrown if is null. - Task Generate(byte[] source); - } - - public sealed class ETagGenerator : IETagGenerator, IDisposable - { - private readonly SHA1 _sha1; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "Sha1 is enough ??? I don't know.")] - public ETagGenerator() - { - _sha1 = SHA1.Create(); - } - - public Task Generate(byte[] source) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - - return Task.Run(() => Convert.ToBase64String(_sha1.ComputeHash(source))); - } - - private bool _disposed; // To detect redundant calls - - public void Dispose() - { - if (_disposed) return; - _sha1.Dispose(); - _disposed = true; - } - } -} diff --git a/Timeline/Services/EntityNames.cs b/Timeline/Services/EntityNames.cs deleted file mode 100644 index 0ce1de3b..00000000 --- a/Timeline/Services/EntityNames.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Timeline.Services -{ - public static class EntityNames - { - public const string User = "User"; - public const string Timeline = "Timeline"; - public const string TimelinePost = "TimelinePost"; - } -} diff --git a/Timeline/Services/Exceptions/EntityAlreadyExistError.cs b/Timeline/Services/Exceptions/EntityAlreadyExistError.cs deleted file mode 100644 index 7db2e860..00000000 --- a/Timeline/Services/Exceptions/EntityAlreadyExistError.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Globalization; -using System.Text; - -namespace Timeline.Services.Exceptions -{ - /// - /// Thrown when an entity is already exists. - /// - /// - /// For example, want to create a timeline but a timeline with the same name already exists. - /// - [Serializable] - public class EntityAlreadyExistException : Exception - { - private readonly string? _entityName; - - public EntityAlreadyExistException() : this(null, null, null, null) { } - - public EntityAlreadyExistException(string? entityName) : this(entityName, null) { } - - public EntityAlreadyExistException(string? entityName, Exception? inner) : this(entityName, null, null, null, inner) { } - - public EntityAlreadyExistException(string? entityName, object? entity = null) : this(entityName, null, entity, null, null) { } - public EntityAlreadyExistException(Type? entityType, object? entity = null) : this(null, entityType, entity, null, null) { } - public EntityAlreadyExistException(string? entityName, Type? entityType, object? entity = null, string? message = null, Exception? inner = null) : base(MakeMessage(entityName, entityType, message), inner) - { - _entityName = entityName; - EntityType = entityType; - Entity = entity; - } - - private static string MakeMessage(string? entityName, Type? entityType, string? message) - { - string? name = entityName ?? (entityType?.Name); - - var result = new StringBuilder(); - - if (name == null) - result.Append(Resources.Services.Exceptions.EntityAlreadyExistErrorDefault); - else - result.AppendFormat(CultureInfo.InvariantCulture, Resources.Services.Exceptions.EntityAlreadyExistError, name); - - if (message != null) - { - result.Append(' '); - result.Append(message); - } - - return result.ToString(); - } - - protected EntityAlreadyExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? EntityName => _entityName ?? (EntityType?.Name); - - public Type? EntityType { get; } - - public object? Entity { get; } - } -} diff --git a/Timeline/Services/Exceptions/EntityNotExistError.cs b/Timeline/Services/Exceptions/EntityNotExistError.cs deleted file mode 100644 index e79496d3..00000000 --- a/Timeline/Services/Exceptions/EntityNotExistError.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Globalization; -using System.Text; - -namespace Timeline.Services.Exceptions -{ - /// - /// Thrown when you want to get an entity that does not exist. - /// - /// - /// For example, you want to get a timeline with given name but it does not exist. - /// - [Serializable] - public class EntityNotExistException : Exception - { - public EntityNotExistException() : this(null, null, null, null) { } - public EntityNotExistException(string? entityName) : this(entityName, null, null, null) { } - public EntityNotExistException(Type? entityType) : this(null, entityType, null, null) { } - public EntityNotExistException(string? entityName, Exception? inner) : this(entityName, null, null, inner) { } - public EntityNotExistException(Type? entityType, Exception? inner) : this(null, entityType, null, inner) { } - public EntityNotExistException(string? entityName, Type? entityType, string? message = null, Exception? inner = null) : base(MakeMessage(entityName, entityType, message), inner) - { - EntityName = entityName; - EntityType = entityType; - } - - private static string MakeMessage(string? entityName, Type? entityType, string? message) - { - string? name = entityName ?? (entityType?.Name); - - var result = new StringBuilder(); - - if (name == null) - result.Append(Resources.Services.Exceptions.EntityNotExistErrorDefault); - else - result.AppendFormat(CultureInfo.InvariantCulture, Resources.Services.Exceptions.EntityNotExistError, name); - - if (message != null) - { - result.Append(' '); - result.Append(message); - } - - return result.ToString(); - } - - protected EntityNotExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? EntityName { get; } - - public Type? EntityType { get; } - } -} diff --git a/Timeline/Services/Exceptions/ExceptionMessageHelper.cs b/Timeline/Services/Exceptions/ExceptionMessageHelper.cs deleted file mode 100644 index be3c42a4..00000000 --- a/Timeline/Services/Exceptions/ExceptionMessageHelper.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Timeline.Services.Exceptions -{ - public static class ExceptionMessageHelper - { - public static string AppendAdditionalMessage(this string origin, string? message) - { - if (message == null) - return origin; - else - return origin + " " + message; - } - } -} diff --git a/Timeline/Services/Exceptions/ImageException.cs b/Timeline/Services/Exceptions/ImageException.cs deleted file mode 100644 index 20dd48ae..00000000 --- a/Timeline/Services/Exceptions/ImageException.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services.Exceptions -{ - [Serializable] - public class ImageException : Exception - { - public enum ErrorReason - { - /// - /// Decoding image failed. - /// - CantDecode, - /// - /// Decoding succeeded but the real type is not the specified type. - /// - UnmatchedFormat, - /// - /// Image is not of required size. - /// - NotSquare, - /// - /// Other unknown errer. - /// - Unknown - } - - public ImageException() : this(null) { } - public ImageException(string? message) : this(message, null) { } - public ImageException(string? message, Exception? inner) : this(ErrorReason.Unknown, null, null, null, message, inner) { } - - public ImageException(ErrorReason error, byte[]? data, string? requestType = null, string? realType = null, string? message = null, Exception? inner = null) : base(MakeMessage(error).AppendAdditionalMessage(message), inner) { Error = error; ImageData = data; RequestType = requestType; RealType = realType; } - - protected ImageException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - private static string MakeMessage(ErrorReason? reason) => - string.Format(CultureInfo.InvariantCulture, Resources.Services.Exceptions.ImageException, reason switch - { - ErrorReason.CantDecode => Resources.Services.Exceptions.ImageExceptionCantDecode, - ErrorReason.UnmatchedFormat => Resources.Services.Exceptions.ImageExceptionUnmatchedFormat, - ErrorReason.NotSquare => Resources.Services.Exceptions.ImageExceptionBadSize, - _ => Resources.Services.Exceptions.ImageExceptionUnknownError - }); - - public ErrorReason Error { get; } -#pragma warning disable CA1819 // Properties should not return arrays - public byte[]? ImageData { get; } -#pragma warning restore CA1819 // Properties should not return arrays - public string? RequestType { get; } - - // This field will be null if decoding failed. - public string? RealType { get; } - } -} diff --git a/Timeline/Services/Exceptions/TimelineNotExistException.cs b/Timeline/Services/Exceptions/TimelineNotExistException.cs deleted file mode 100644 index 70970b24..00000000 --- a/Timeline/Services/Exceptions/TimelineNotExistException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services.Exceptions -{ - [Serializable] - public class TimelineNotExistException : EntityNotExistException - { - public TimelineNotExistException() : this(null, null) { } - public TimelineNotExistException(string? timelineName) : this(timelineName, null) { } - public TimelineNotExistException(string? timelineName, Exception? inner) : this(timelineName, null, inner) { } - public TimelineNotExistException(string? timelineName, string? message, Exception? inner = null) - : base(EntityNames.Timeline, null, string.Format(CultureInfo.InvariantCulture, Resources.Services.Exceptions.TimelineNotExistException, timelineName ?? "").AppendAdditionalMessage(message), inner) { TimelineName = timelineName; } - - protected TimelineNotExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? TimelineName { get; set; } - } -} diff --git a/Timeline/Services/Exceptions/TimelinePostNoDataException.cs b/Timeline/Services/Exceptions/TimelinePostNoDataException.cs deleted file mode 100644 index c4b6bf62..00000000 --- a/Timeline/Services/Exceptions/TimelinePostNoDataException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Timeline.Services.Exceptions -{ - [Serializable] - public class TimelinePostNoDataException : Exception - { - public TimelinePostNoDataException() : this(null, null) { } - public TimelinePostNoDataException(string? message) : this(message, null) { } - public TimelinePostNoDataException(string? message, Exception? inner) : base(Resources.Services.Exceptions.TimelineNoDataException.AppendAdditionalMessage(message), inner) { } - protected TimelinePostNoDataException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/Timeline/Services/Exceptions/TimelinePostNotExistException.cs b/Timeline/Services/Exceptions/TimelinePostNotExistException.cs deleted file mode 100644 index f95dd410..00000000 --- a/Timeline/Services/Exceptions/TimelinePostNotExistException.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services.Exceptions -{ - [Serializable] - public class TimelinePostNotExistException : EntityNotExistException - { - public TimelinePostNotExistException() : this(null, null, false, null, null) { } - [Obsolete("This has no meaning.")] - public TimelinePostNotExistException(string? message) : this(message, null) { } - [Obsolete("This has no meaning.")] - public TimelinePostNotExistException(string? message, Exception? inner) : this(null, null, false, message, inner) { } - protected TimelinePostNotExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public TimelinePostNotExistException(string? timelineName, long? id, bool isDelete, string? message = null, Exception? inner = null) : base(EntityNames.TimelinePost, null, MakeMessage(timelineName, id, isDelete).AppendAdditionalMessage(message), inner) { TimelineName = timelineName; Id = id; IsDelete = isDelete; } - - private static string MakeMessage(string? timelineName, long? id, bool isDelete) - { - return string.Format(CultureInfo.InvariantCulture, isDelete ? Resources.Services.Exceptions.TimelinePostNotExistExceptionDeleted : Resources.Services.Exceptions.TimelinePostNotExistException, timelineName ?? "", id); - } - - public string? TimelineName { get; set; } - public long? Id { get; set; } - - /// - /// True if the post is deleted. False if the post does not exist at all. - /// - public bool IsDelete { get; set; } - } -} diff --git a/Timeline/Services/Exceptions/UserNotExistException.cs b/Timeline/Services/Exceptions/UserNotExistException.cs deleted file mode 100644 index 7ef714df..00000000 --- a/Timeline/Services/Exceptions/UserNotExistException.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; - -namespace Timeline.Services.Exceptions -{ - /// - /// The user requested does not exist. - /// - [Serializable] - public class UserNotExistException : EntityNotExistException - { - public UserNotExistException() : this(null, null, null, null) { } - public UserNotExistException(string? username, Exception? inner) : this(username, null, null, inner) { } - - public UserNotExistException(string? username) : this(username, null, null, null) { } - - public UserNotExistException(long id) : this(null, id, null, null) { } - - public UserNotExistException(string? username, long? id, string? message, Exception? inner) : base(EntityNames.User, null, - string.Format(CultureInfo.InvariantCulture, Resources.Services.Exceptions.UserNotExistException, username ?? "", id).AppendAdditionalMessage(message), inner) - { - Username = username; - Id = id; - } - - protected UserNotExistException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - /// - /// The username of the user that does not exist. - /// - public string? Username { get; set; } - - /// - /// The id of the user that does not exist. - /// - public long? Id { get; set; } - } -} diff --git a/Timeline/Services/ImageValidator.cs b/Timeline/Services/ImageValidator.cs deleted file mode 100644 index 59424a7c..00000000 --- a/Timeline/Services/ImageValidator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats; -using System; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Services.Exceptions; - -namespace Timeline.Services -{ - public interface IImageValidator - { - /// - /// Validate a image data. - /// - /// The data of the image. Can't be null. - /// If not null, the real image format will be check against the requested format and throw if not match. If null, then do not check. - /// If true, image must be square. - /// The format. - /// Thrown when is null. - /// Thrown when image data can't be decoded or real type does not match request type or image is not square when required. - Task Validate(byte[] data, string? requestType = null, bool square = false); - } - - public class ImageValidator : IImageValidator - { - public ImageValidator() - { - } - - public async Task Validate(byte[] data, string? requestType = null, bool square = false) - { - if (data == null) - throw new ArgumentNullException(nameof(data)); - - var format = await Task.Run(() => - { - try - { - using var image = Image.Load(data, out IImageFormat format); - if (requestType != null && !format.MimeTypes.Contains(requestType)) - throw new ImageException(ImageException.ErrorReason.UnmatchedFormat, data, requestType, format.DefaultMimeType); - if (square && image.Width != image.Height) - throw new ImageException(ImageException.ErrorReason.NotSquare, data, requestType, format.DefaultMimeType); - return format; - } - catch (UnknownImageFormatException e) - { - throw new ImageException(ImageException.ErrorReason.CantDecode, data, requestType, null, null, e); - } - }); - return format; - } - } -} diff --git a/Timeline/Services/JwtUserTokenBadFormatException.cs b/Timeline/Services/JwtUserTokenBadFormatException.cs deleted file mode 100644 index c528c3e3..00000000 --- a/Timeline/Services/JwtUserTokenBadFormatException.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Globalization; -using static Timeline.Resources.Services.Exception; - -namespace Timeline.Services -{ - [Serializable] - public class JwtUserTokenBadFormatException : UserTokenBadFormatException - { - public enum ErrorKind - { - NoIdClaim, - IdClaimBadFormat, - NoVersionClaim, - VersionClaimBadFormat, - Other - } - - public JwtUserTokenBadFormatException() : this("", ErrorKind.Other) { } - public JwtUserTokenBadFormatException(string message) : base(message) { } - public JwtUserTokenBadFormatException(string message, Exception inner) : base(message, inner) { } - - public JwtUserTokenBadFormatException(string token, ErrorKind type) : base(token, GetErrorMessage(type)) { ErrorType = type; } - public JwtUserTokenBadFormatException(string token, ErrorKind type, Exception inner) : base(token, GetErrorMessage(type), inner) { ErrorType = type; } - public JwtUserTokenBadFormatException(string token, ErrorKind type, string message, Exception inner) : base(token, message, inner) { ErrorType = type; } - protected JwtUserTokenBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public ErrorKind ErrorType { get; set; } - - private static string GetErrorMessage(ErrorKind type) - { - var reason = type switch - { - ErrorKind.NoIdClaim => JwtUserTokenBadFormatExceptionIdMissing, - ErrorKind.IdClaimBadFormat => JwtUserTokenBadFormatExceptionIdBadFormat, - ErrorKind.NoVersionClaim => JwtUserTokenBadFormatExceptionVersionMissing, - ErrorKind.VersionClaimBadFormat => JwtUserTokenBadFormatExceptionVersionBadFormat, - ErrorKind.Other => JwtUserTokenBadFormatExceptionOthers, - _ => JwtUserTokenBadFormatExceptionUnknown - }; - - return string.Format(CultureInfo.CurrentCulture, - Resources.Services.Exception.JwtUserTokenBadFormatException, reason); - } - } -} diff --git a/Timeline/Services/PasswordBadFormatException.cs b/Timeline/Services/PasswordBadFormatException.cs deleted file mode 100644 index 2029ebb4..00000000 --- a/Timeline/Services/PasswordBadFormatException.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Timeline.Services -{ - - [Serializable] - public class PasswordBadFormatException : Exception - { - public PasswordBadFormatException() : base(Resources.Services.Exception.PasswordBadFormatException) { } - public PasswordBadFormatException(string message) : base(message) { } - public PasswordBadFormatException(string message, Exception inner) : base(message, inner) { } - - public PasswordBadFormatException(string password, string validationMessage) : this() - { - Password = password; - ValidationMessage = validationMessage; - } - - protected PasswordBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string Password { get; set; } = ""; - - public string ValidationMessage { get; set; } = ""; - } -} diff --git a/Timeline/Services/PasswordService.cs b/Timeline/Services/PasswordService.cs deleted file mode 100644 index 8114a520..00000000 --- a/Timeline/Services/PasswordService.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Microsoft.AspNetCore.Cryptography.KeyDerivation; -using System; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; - -namespace Timeline.Services -{ - /// - /// Hashed password is of bad format. - /// - /// - [Serializable] - public class HashedPasswordBadFromatException : Exception - { - private static string MakeMessage(string reason) - { - return Resources.Services.Exception.HashedPasswordBadFromatException + " Reason: " + reason; - } - - public HashedPasswordBadFromatException() : base(Resources.Services.Exception.HashedPasswordBadFromatException) { } - - public HashedPasswordBadFromatException(string message) : base(message) { } - public HashedPasswordBadFromatException(string message, Exception inner) : base(message, inner) { } - - public HashedPasswordBadFromatException(string hashedPassword, string reason) : base(MakeMessage(reason)) { HashedPassword = hashedPassword; } - public HashedPasswordBadFromatException(string hashedPassword, string reason, Exception inner) : base(MakeMessage(reason), inner) { HashedPassword = hashedPassword; } - protected HashedPasswordBadFromatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string? HashedPassword { get; set; } - } - - public interface IPasswordService - { - /// - /// Hash a password. - /// - /// The password to hash. - /// A hashed representation of the supplied . - /// Thrown when is null. - string HashPassword(string password); - - /// - /// Verify whether the password fits into the hashed one. - /// - /// Usually you only need to check the returned bool value. - /// Catching usually is not necessary. - /// Because if your program logic is right and always call - /// and in pair, this exception will never be thrown. - /// A thrown one usually means the data you saved is corupted, which is a critical problem. - /// - /// The hashed password. - /// The password supplied for comparison. - /// True indicating password is right. Otherwise false. - /// Thrown when or is null. - /// Thrown when the hashed password is of bad format. - bool VerifyPassword(string hashedPassword, string providedPassword); - } - - /// - /// Copied from https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/PasswordHasher.cs - /// Remove V2 format and unnecessary format version check. - /// Remove configuration options. - /// Remove user related parts. - /// Change the exceptions. - /// - public class PasswordService : IPasswordService - { - /* ======================= - * HASHED PASSWORD FORMATS - * ======================= - * - * Version 3: - * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } - * (All UInt32s are stored big-endian.) - */ - - private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); - - public PasswordService() - { - } - - // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static bool ByteArraysEqual(byte[] a, byte[] b) - { - if (a == null && b == null) - { - return true; - } - if (a == null || b == null || a.Length != b.Length) - { - return false; - } - var areSame = true; - for (var i = 0; i < a.Length; i++) - { - areSame &= (a[i] == b[i]); - } - return areSame; - } - - public string HashPassword(string password) - { - if (password == null) - throw new ArgumentNullException(nameof(password)); - return Convert.ToBase64String(HashPasswordV3(password, _rng)); - } - - private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng) - { - return HashPasswordV3(password, rng, - prf: KeyDerivationPrf.HMACSHA256, - iterCount: 10000, - saltSize: 128 / 8, - numBytesRequested: 256 / 8); - } - - private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested) - { - // Produce a version 3 (see comment above) text hash. - byte[] salt = new byte[saltSize]; - rng.GetBytes(salt); - byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested); - - var outputBytes = new byte[13 + salt.Length + subkey.Length]; - outputBytes[0] = 0x01; // format marker - WriteNetworkByteOrder(outputBytes, 1, (uint)prf); - WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount); - WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize); - Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); - Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length); - return outputBytes; - } - - public bool VerifyPassword(string hashedPassword, string providedPassword) - { - if (hashedPassword == null) - throw new ArgumentNullException(nameof(hashedPassword)); - if (providedPassword == null) - throw new ArgumentNullException(nameof(providedPassword)); - - byte[] decodedHashedPassword; - try - { - decodedHashedPassword = Convert.FromBase64String(hashedPassword); - } - catch (FormatException e) - { - throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotBase64, e); - } - - // read the format marker from the hashed password - if (decodedHashedPassword.Length == 0) - { - throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotLength0); - } - - return (decodedHashedPassword[0]) switch - { - 0x01 => VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, hashedPassword), - _ => throw new HashedPasswordBadFromatException(hashedPassword, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotUnknownMarker), - }; - } - - private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, string hashedPasswordString) - { - try - { - // Read header information - KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 1); - int iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5); - int saltLength = (int)ReadNetworkByteOrder(hashedPassword, 9); - - // Read the salt: must be >= 128 bits - if (saltLength < 128 / 8) - { - throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotSaltTooShort); - } - byte[] salt = new byte[saltLength]; - Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length); - - // Read the subkey (the rest of the payload): must be >= 128 bits - int subkeyLength = hashedPassword.Length - 13 - salt.Length; - if (subkeyLength < 128 / 8) - { - throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotSubkeyTooShort); - } - byte[] expectedSubkey = new byte[subkeyLength]; - Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); - - // Hash the incoming password and verify it - byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength); - return ByteArraysEqual(actualSubkey, expectedSubkey); - } - catch (Exception e) - { - // This should never occur except in the case of a malformed payload, where - // we might go off the end of the array. Regardless, a malformed payload - // implies verification failed. - throw new HashedPasswordBadFromatException(hashedPasswordString, Resources.Services.Exception.HashedPasswordBadFromatExceptionNotOthers, e); - } - } - - private static uint ReadNetworkByteOrder(byte[] buffer, int offset) - { - return ((uint)(buffer[offset + 0]) << 24) - | ((uint)(buffer[offset + 1]) << 16) - | ((uint)(buffer[offset + 2]) << 8) - | ((uint)(buffer[offset + 3])); - } - - private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value) - { - buffer[offset + 0] = (byte)(value >> 24); - buffer[offset + 1] = (byte)(value >> 16); - buffer[offset + 2] = (byte)(value >> 8); - buffer[offset + 3] = (byte)(value >> 0); - } - } -} diff --git a/Timeline/Services/PathProvider.cs b/Timeline/Services/PathProvider.cs deleted file mode 100644 index 1baba5c0..00000000 --- a/Timeline/Services/PathProvider.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.Extensions.Configuration; -using System.IO; -using Timeline.Configs; - -namespace Timeline.Services -{ - public interface IPathProvider - { - public string GetWorkingDirectory(); - public string GetDatabaseFilePath(); - public string GetDatabaseBackupDirectory(); - } - - public class PathProvider : IPathProvider - { - private readonly IConfiguration _configuration; - - private readonly string _workingDirectory; - - - public PathProvider(IConfiguration configuration) - { - _configuration = configuration; - _workingDirectory = configuration.GetValue(ApplicationConfiguration.WorkDirKey) ?? ApplicationConfiguration.DefaultWorkDir; - } - - public string GetWorkingDirectory() - { - return _workingDirectory; - } - - public string GetDatabaseFilePath() - { - return Path.Combine(_workingDirectory, ApplicationConfiguration.DatabaseFileName); - } - - public string GetDatabaseBackupDirectory() - { - return Path.Combine(_workingDirectory, ApplicationConfiguration.DatabaseBackupDirectoryName); - } - } -} diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs deleted file mode 100644 index 4bcae596..00000000 --- a/Timeline/Services/TimelineService.cs +++ /dev/null @@ -1,1166 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using SixLabors.ImageSharp; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Models.Validation; -using Timeline.Services.Exceptions; -using static Timeline.Resources.Services.TimelineService; - -namespace Timeline.Services -{ - public static class TimelineHelper - { - public static string ExtractTimelineName(string name, out bool isPersonal) - { - if (name.StartsWith("@", StringComparison.OrdinalIgnoreCase)) - { - isPersonal = true; - return name.Substring(1); - } - else - { - isPersonal = false; - return name; - } - } - } - - public enum TimelineUserRelationshipType - { - Own = 0b1, - Join = 0b10, - Default = Own | Join - } - - public class TimelineUserRelationship - { - public TimelineUserRelationship(TimelineUserRelationshipType type, long userId) - { - Type = type; - UserId = userId; - } - - public TimelineUserRelationshipType Type { get; set; } - public long UserId { get; set; } - } - - public class PostData : ICacheableData - { -#pragma warning disable CA1819 // Properties should not return arrays - public byte[] Data { get; set; } = default!; -#pragma warning restore CA1819 // Properties should not return arrays - public string Type { get; set; } = default!; - public string ETag { get; set; } = default!; - public DateTime? LastModified { get; set; } // TODO: Why nullable? - } - - /// - /// This define the interface of both personal timeline and ordinary timeline. - /// - public interface ITimelineService - { - /// - /// Get the timeline last modified time (not include name change). - /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimelineLastModifiedTime(string timelineName); - - /// - /// Get the timeline unique id. - /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimelineUniqueId(string timelineName); - - /// - /// Get the timeline info. - /// - /// The name of the timeline. - /// The timeline info. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task GetTimeline(string timelineName); - - /// - /// Set the properties of a timeline. - /// - /// The name of the timeline. - /// The new properties. Null member means not to change. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties); - - /// - /// Get all the posts in the timeline. - /// - /// The name of the timeline. - /// The time that posts have been modified since. - /// Whether include deleted posts. - /// A list of all posts. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - Task> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false); - - /// - /// Get the etag of data of a post. - /// - /// The name of the timeline of the post. - /// The id of the post. - /// The etag of the data. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when post of does not exist or has been deleted. - /// Thrown when post has no data. - /// - Task GetPostDataETag(string timelineName, long postId); - - /// - /// Get the data of a post. - /// - /// The name of the timeline of the post. - /// The id of the post. - /// The etag of the data. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when post of does not exist or has been deleted. - /// Thrown when post has no data. - /// - Task GetPostData(string timelineName, long postId); - - /// - /// Create a new text post in timeline. - /// - /// The name of the timeline to create post against. - /// The author's user id. - /// The content text. - /// The time of the post. If null, then current time is used. - /// The info of the created post. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown if user of does not exist. - Task CreateTextPost(string timelineName, long authorId, string text, DateTime? time); - - /// - /// Create a new image post in timeline. - /// - /// The name of the timeline to create post against. - /// The author's user id. - /// The image data. - /// The time of the post. If null, then use current time. - /// The info of the created post. - /// Thrown when or is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown if user of does not exist. - /// Thrown if data is not a image. Validated by . - Task CreateImagePost(string timelineName, long authorId, byte[] imageData, DateTime? time); - - /// - /// Delete a post. - /// - /// The name of the timeline to delete post against. - /// The id of the post to delete. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when the post with given id does not exist or is deleted already. - /// - /// First use to check the permission. - /// - Task DeletePost(string timelineName, long postId); - - /// - /// Delete all posts of the given user. Used when delete a user. - /// - /// The id of the user. - Task DeleteAllPostsOfUser(long userId); - - /// - /// Change member of timeline. - /// - /// The name of the timeline. - /// A list of usernames of members to add. May be null. - /// A list of usernames of members to remove. May be null. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when names in or is not a valid username. - /// Thrown when one of the user to change does not exist. - /// - /// Operating on a username that is of bad format or does not exist always throws. - /// Add a user that already is a member has no effects. - /// Remove a user that is not a member also has not effects. - /// Add and remove an identical user results in no effects. - /// More than one same usernames are regarded as one. - /// - Task ChangeMember(string timelineName, IList? membersToAdd, IList? membersToRemove); - - /// - /// Check whether a user can manage(change timeline info, member, ...) a timeline. - /// - /// The name of the timeline. - /// The id of the user to check on. - /// True if the user can manage the timeline, otherwise false. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// - /// This method does not check whether visitor is administrator. - /// Return false if user with user id does not exist. - /// - Task HasManagePermission(string timelineName, long userId); - - /// - /// Verify whether a visitor has the permission to read a timeline. - /// - /// The name of the timeline. - /// The id of the user to check on. Null means visitor without account. - /// True if can read, false if can't read. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// - /// This method does not check whether visitor is administrator. - /// Return false if user with visitor id does not exist. - /// - Task HasReadPermission(string timelineName, long? visitorId); - - /// - /// Verify whether a user has the permission to modify a post. - /// - /// The name of the timeline. - /// The id of the post. - /// The id of the user to check on. - /// True if you want it to throw . Default false. - /// True if can modify, false if can't modify. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// Thrown when the post with given id does not exist or is deleted already and is true. - /// - /// Unless is true, this method should return true if the post does not exist. - /// If the post is deleted, its author info still exists, so it is checked as the post is not deleted unless is true. - /// This method does not check whether the user is administrator. - /// It only checks whether he is the author of the post or the owner of the timeline. - /// Return false when user with modifier id does not exist. - /// - Task HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false); - - /// - /// Verify whether a user is member of a timeline. - /// - /// The name of the timeline. - /// The id of user to check on. - /// True if it is a member, false if not. - /// Thrown when is null. - /// Throw when is of bad format. - /// - /// Thrown when timeline with name does not exist. - /// If it is a personal timeline, then inner exception is . - /// - /// - /// Timeline owner is also considered as a member. - /// Return false when user with user id does not exist. - /// - Task IsMemberOf(string timelineName, long userId); - - /// - /// Get all timelines including personal and ordinary timelines. - /// - /// Filter timelines related (own or is a member) to specific user. - /// Filter timelines with given visibility. If null or empty, all visibilities are returned. Duplicate value are ignored. - /// The list of timelines. - /// - /// If user with related user id does not exist, empty list will be returned. - /// - Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null); - - /// - /// Create a timeline. - /// - /// The name of the timeline. - /// The id of owner of the timeline. - /// The info of the new timeline. - /// Thrown when is null. - /// Thrown when timeline name is invalid. - /// Thrown when the timeline already exists. - /// Thrown when the owner user does not exist. - Task CreateTimeline(string timelineName, long ownerId); - - /// - /// Delete a timeline. - /// - /// The name of the timeline to delete. - /// Thrown when is null. - /// Thrown when timeline name is invalid. - /// Thrown when the timeline does not exist. - Task DeleteTimeline(string timelineName); - - /// - /// Change name of a timeline. - /// - /// The old timeline name. - /// The new timeline name. - /// The new timeline info. - /// Thrown when or is null. - /// Thrown when or is of invalid format. - /// Thrown when timeline does not exist. - /// Thrown when a timeline with new name already exists. - /// - /// You can only change name of general timeline. - /// - Task ChangeTimelineName(string oldTimelineName, string newTimelineName); - } - - public class TimelineService : ITimelineService - { - public TimelineService(ILogger logger, DatabaseContext database, IDataManager dataManager, IUserService userService, IImageValidator imageValidator, IClock clock) - { - _logger = logger; - _database = database; - _dataManager = dataManager; - _userService = userService; - _imageValidator = imageValidator; - _clock = clock; - } - - private readonly ILogger _logger; - - private readonly DatabaseContext _database; - - private readonly IDataManager _dataManager; - - private readonly IUserService _userService; - - private readonly IImageValidator _imageValidator; - - private readonly IClock _clock; - - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - - private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); - - private void ValidateTimelineName(string name, string paramName) - { - if (!_timelineNameValidator.Validate(name, out var message)) - { - throw new ArgumentException(ExceptionTimelineNameBadFormat.AppendAdditionalMessage(message), paramName); - } - } - - /// Remember to include Members when query. - private async Task MapTimelineFromEntity(TimelineEntity entity) - { - var owner = await _userService.GetUserById(entity.OwnerId); - - var members = new List(); - foreach (var memberEntity in entity.Members) - { - members.Add(await _userService.GetUserById(memberEntity.UserId)); - } - - var name = entity.Name ?? ("@" + owner.Username); - - return new Models.Timeline - { - UniqueID = entity.UniqueId, - Name = name, - NameLastModified = entity.NameLastModified, - Title = string.IsNullOrEmpty(entity.Title) ? name : entity.Title, - Description = entity.Description ?? "", - Owner = owner, - Visibility = entity.Visibility, - Members = members, - CreateTime = entity.CreateTime, - LastModified = entity.LastModified - }; - } - - private async Task MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName) - { - User? author = entity.AuthorId.HasValue ? await _userService.GetUserById(entity.AuthorId.Value) : null; - - ITimelinePostContent? content = null; - - if (entity.Content != null) - { - var type = entity.ContentType; - - content = type switch - { - TimelinePostContentTypes.Text => new TextTimelinePostContent(entity.Content), - TimelinePostContentTypes.Image => new ImageTimelinePostContent(entity.Content), - _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, ExceptionDatabaseUnknownContentType, type)) - }; - } - - return new TimelinePost( - id: entity.LocalId, - author: author, - content: content, - time: entity.Time, - lastUpdated: entity.LastUpdated, - timelineName: timelineName - ); - } - - private TimelineEntity CreateNewTimelineEntity(string? name, long ownerId) - { - var currentTime = _clock.GetCurrentTime(); - - return new TimelineEntity - { - Name = name, - NameLastModified = currentTime, - OwnerId = ownerId, - Visibility = TimelineVisibility.Register, - CreateTime = currentTime, - LastModified = currentTime, - CurrentPostLocalId = 0, - Members = new List() - }; - } - - - - // Get timeline id by name. If it is a personal timeline and it does not exist, it will be created. - // - // This method will check the name format and if it is invalid, ArgumentException is thrown. - // - // For personal timeline, if the user does not exist, TimelineNotExistException will be thrown with UserNotExistException as inner exception. - // For ordinary timeline, if the timeline does not exist, TimelineNotExistException will be thrown. - // - // It follows all timeline-related function common interface contracts. - private async Task FindTimelineId(string timelineName) - { - timelineName = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal); - - if (isPersonal) - { - long userId; - try - { - userId = await _userService.GetUserIdByUsername(timelineName); - } - catch (ArgumentException e) - { - throw new ArgumentException(ExceptionFindTimelineUsernameBadFormat, nameof(timelineName), e); - } - catch (UserNotExistException e) - { - throw new TimelineNotExistException(timelineName, e); - } - - var timelineEntity = await _database.Timelines.Where(t => t.OwnerId == userId && t.Name == null).Select(t => new { t.Id }).SingleOrDefaultAsync(); - - if (timelineEntity != null) - { - return timelineEntity.Id; - } - else - { - var newTimelineEntity = CreateNewTimelineEntity(null, userId); - _database.Timelines.Add(newTimelineEntity); - await _database.SaveChangesAsync(); - - return newTimelineEntity.Id; - } - } - else - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - ValidateTimelineName(timelineName, nameof(timelineName)); - - var timelineEntity = await _database.Timelines.Where(t => t.Name == timelineName).Select(t => new { t.Id }).SingleOrDefaultAsync(); - - if (timelineEntity == null) - { - throw new TimelineNotExistException(timelineName); - } - else - { - return timelineEntity.Id; - } - } - } - - public async Task GetTimelineLastModifiedTime(string timelineName) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.LastModified }).SingleAsync(); - - return timelineEntity.LastModified; - } - - public async Task GetTimelineUniqueId(string timelineName) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.UniqueId }).SingleAsync(); - - return timelineEntity.UniqueId; - } - - public async Task GetTimeline(string timelineName) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Include(t => t.Members).SingleAsync(); - - return await MapTimelineFromEntity(timelineEntity); - } - - public async Task> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false) - { - modifiedSince = modifiedSince?.MyToUtc(); - - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - IQueryable query = _database.TimelinePosts.Where(p => p.TimelineId == timelineId); - - if (!includeDeleted) - { - query = query.Where(p => p.Content != null); - } - - if (modifiedSince.HasValue) - { - query = query.Include(p => p.Author).Where(p => p.LastUpdated >= modifiedSince || (p.Author != null && p.Author.UsernameChangeTime >= modifiedSince)); - } - - query = query.OrderBy(p => p.Time); - - var postEntities = await query.ToListAsync(); - - var posts = new List(); - foreach (var entity in postEntities) - { - posts.Add(await MapTimelinePostFromEntity(entity, timelineName)); - } - return posts; - } - - public async Task GetPostDataETag(string timelineName, long postId) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); - - if (postEntity == null) - throw new TimelinePostNotExistException(timelineName, postId, false); - - if (postEntity.Content == null) - throw new TimelinePostNotExistException(timelineName, postId, true); - - if (postEntity.ContentType != TimelinePostContentTypes.Image) - throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost); - - var tag = postEntity.Content; - - return tag; - } - - public async Task GetPostData(string timelineName, long postId) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync(); - - if (postEntity == null) - throw new TimelinePostNotExistException(timelineName, postId, false); - - if (postEntity.Content == null) - throw new TimelinePostNotExistException(timelineName, postId, true); - - if (postEntity.ContentType != TimelinePostContentTypes.Image) - throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost); - - var tag = postEntity.Content; - - byte[] data; - - try - { - data = await _dataManager.GetEntry(tag); - } - catch (InvalidOperationException e) - { - throw new DatabaseCorruptedException(ExceptionGetDataDataEntryNotExist, e); - } - - if (postEntity.ExtraContent == null) - { - _logger.LogWarning(LogGetDataNoFormat); - var format = Image.DetectFormat(data); - postEntity.ExtraContent = format.DefaultMimeType; - await _database.SaveChangesAsync(); - } - - return new PostData - { - Data = data, - Type = postEntity.ExtraContent, - ETag = tag, - LastModified = postEntity.LastUpdated - }; - } - - public async Task CreateTextPost(string timelineName, long authorId, string text, DateTime? time) - { - time = time?.MyToUtc(); - - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (text == null) - throw new ArgumentNullException(nameof(text)); - - var timelineId = await FindTimelineId(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var author = await _userService.GetUserById(authorId); - - var currentTime = _clock.GetCurrentTime(); - var finalTime = time ?? currentTime; - - timelineEntity.CurrentPostLocalId += 1; - - var postEntity = new TimelinePostEntity - { - LocalId = timelineEntity.CurrentPostLocalId, - ContentType = TimelinePostContentTypes.Text, - Content = text, - AuthorId = authorId, - TimelineId = timelineId, - Time = finalTime, - LastUpdated = currentTime - }; - _database.TimelinePosts.Add(postEntity); - await _database.SaveChangesAsync(); - - - return new TimelinePost( - id: postEntity.LocalId, - content: new TextTimelinePostContent(text), - time: finalTime, - author: author, - lastUpdated: currentTime, - timelineName: timelineName - ); - } - - public async Task CreateImagePost(string timelineName, long authorId, byte[] data, DateTime? time) - { - time = time?.MyToUtc(); - - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (data == null) - throw new ArgumentNullException(nameof(data)); - - var timelineId = await FindTimelineId(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var author = await _userService.GetUserById(authorId); - - var imageFormat = await _imageValidator.Validate(data); - - var imageFormatText = imageFormat.DefaultMimeType; - - var tag = await _dataManager.RetainEntry(data); - - var currentTime = _clock.GetCurrentTime(); - var finalTime = time ?? currentTime; - - timelineEntity.CurrentPostLocalId += 1; - - var postEntity = new TimelinePostEntity - { - LocalId = timelineEntity.CurrentPostLocalId, - ContentType = TimelinePostContentTypes.Image, - Content = tag, - ExtraContent = imageFormatText, - AuthorId = authorId, - TimelineId = timelineId, - Time = finalTime, - LastUpdated = currentTime - }; - _database.TimelinePosts.Add(postEntity); - await _database.SaveChangesAsync(); - - return new TimelinePost( - id: postEntity.LocalId, - content: new ImageTimelinePostContent(tag), - time: finalTime, - author: author, - lastUpdated: currentTime, - timelineName: timelineName - ); - } - - public async Task DeletePost(string timelineName, long id) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == id).SingleOrDefaultAsync(); - - if (post == null) - throw new TimelinePostNotExistException(timelineName, id, false); - - if (post.Content == null) - throw new TimelinePostNotExistException(timelineName, id, true); - - string? dataTag = null; - - if (post.ContentType == TimelinePostContentTypes.Image) - { - dataTag = post.Content; - } - - post.Content = null; - post.LastUpdated = _clock.GetCurrentTime(); - - await _database.SaveChangesAsync(); - - if (dataTag != null) - { - await _dataManager.FreeEntry(dataTag); - } - } - - public async Task DeleteAllPostsOfUser(long userId) - { - var posts = await _database.TimelinePosts.Where(p => p.AuthorId == userId).ToListAsync(); - - var now = _clock.GetCurrentTime(); - - var dataTags = new List(); - - foreach (var post in posts) - { - if (post.Content != null) - { - if (post.ContentType == TimelinePostContentTypes.Image) - { - dataTags.Add(post.Content); - } - post.Content = null; - } - post.LastUpdated = now; - } - - await _database.SaveChangesAsync(); - - foreach (var dataTag in dataTags) - { - await _dataManager.FreeEntry(dataTag); - } - } - - public async Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - if (newProperties == null) - throw new ArgumentNullException(nameof(newProperties)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var changed = false; - - if (newProperties.Title != null) - { - changed = true; - timelineEntity.Title = newProperties.Title; - } - - if (newProperties.Description != null) - { - changed = true; - timelineEntity.Description = newProperties.Description; - } - - if (newProperties.Visibility.HasValue) - { - changed = true; - timelineEntity.Visibility = newProperties.Visibility.Value; - } - - if (changed) - { - var currentTime = _clock.GetCurrentTime(); - timelineEntity.LastModified = currentTime; - } - - await _database.SaveChangesAsync(); - } - - public async Task ChangeMember(string timelineName, IList? add, IList? remove) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - List? RemoveDuplicateAndCheckFormat(IList? list, string paramName) - { - if (list != null) - { - List result = new List(); - var count = list.Count; - for (var index = 0; index < count; index++) - { - var username = list[index]; - if (result.Contains(username)) - { - continue; - } - var (validationResult, message) = _usernameValidator.Validate(username); - if (!validationResult) - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionChangeMemberUsernameBadFormat, index), nameof(paramName)); - result.Add(username); - } - return result; - } - else - { - return null; - } - } - var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, nameof(add)); - var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, nameof(remove)); - - // remove those both in add and remove - if (simplifiedAdd != null && simplifiedRemove != null) - { - var usersToClean = simplifiedRemove.Where(u => simplifiedAdd.Contains(u)).ToList(); - foreach (var u in usersToClean) - { - simplifiedAdd.Remove(u); - simplifiedRemove.Remove(u); - } - - if (simplifiedAdd.Count == 0) - simplifiedAdd = null; - - if (simplifiedRemove.Count == 0) - simplifiedRemove = null; - } - - if (simplifiedAdd == null && simplifiedRemove == null) - return; - - var timelineId = await FindTimelineId(timelineName); - - async Task?> CheckExistenceAndGetId(List? list) - { - if (list == null) - return null; - - List result = new List(); - foreach (var username in list) - { - result.Add(await _userService.GetUserIdByUsername(username)); - } - return result; - } - var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd); - var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove); - - if (userIdsAdd != null) - { - var membersToAdd = userIdsAdd.Select(id => new TimelineMemberEntity { UserId = id, TimelineId = timelineId }).ToList(); - _database.TimelineMembers.AddRange(membersToAdd); - } - - if (userIdsRemove != null) - { - var membersToRemove = await _database.TimelineMembers.Where(m => m.TimelineId == timelineId && userIdsRemove.Contains(m.UserId)).ToListAsync(); - _database.TimelineMembers.RemoveRange(membersToRemove); - } - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - timelineEntity.LastModified = _clock.GetCurrentTime(); - - await _database.SaveChangesAsync(); - } - - public async Task HasManagePermission(string timelineName, long userId) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); - - return userId == timelineEntity.OwnerId; - } - - public async Task HasReadPermission(string timelineName, long? visitorId) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleAsync(); - - if (timelineEntity.Visibility == TimelineVisibility.Public) - return true; - - if (timelineEntity.Visibility == TimelineVisibility.Register && visitorId != null) - return true; - - if (visitorId == null) - { - return false; - } - else - { - var memberEntity = await _database.TimelineMembers.Where(m => m.UserId == visitorId && m.TimelineId == timelineId).SingleOrDefaultAsync(); - return memberEntity != null; - } - } - - public async Task HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); - - var postEntity = await _database.TimelinePosts.Where(p => p.Id == postId).Select(p => new { p.Content, p.AuthorId }).SingleOrDefaultAsync(); - - if (postEntity == null) - { - if (throwOnPostNotExist) - throw new TimelinePostNotExistException(timelineName, postId, false); - else - return true; - } - - if (postEntity.Content == null && throwOnPostNotExist) - { - throw new TimelinePostNotExistException(timelineName, postId, true); - } - - return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId; - } - - public async Task IsMemberOf(string timelineName, long userId) - { - if (timelineName == null) - throw new ArgumentNullException(nameof(timelineName)); - - var timelineId = await FindTimelineId(timelineName); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync(); - - if (userId == timelineEntity.OwnerId) - return true; - - return await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId); - } - - public async Task> GetTimelines(TimelineUserRelationship? relate = null, List? visibility = null) - { - List entities; - - IQueryable ApplyTimelineVisibilityFilter(IQueryable query) - { - if (visibility != null && visibility.Count != 0) - { - return query.Where(t => visibility.Contains(t.Visibility)); - } - return query; - } - - bool allVisibilities = visibility == null || visibility.Count == 0; - - if (relate == null) - { - entities = await ApplyTimelineVisibilityFilter(_database.Timelines).Include(t => t.Members).ToListAsync(); - } - else - { - entities = new List(); - - if ((relate.Type & TimelineUserRelationshipType.Own) != 0) - { - entities.AddRange(await ApplyTimelineVisibilityFilter(_database.Timelines.Where(t => t.OwnerId == relate.UserId)).Include(t => t.Members).ToListAsync()); - } - - if ((relate.Type & TimelineUserRelationshipType.Join) != 0) - { - entities.AddRange(await ApplyTimelineVisibilityFilter(_database.TimelineMembers.Where(m => m.UserId == relate.UserId).Include(m => m.Timeline).ThenInclude(t => t.Members).Select(m => m.Timeline)).ToListAsync()); - } - } - - var result = new List(); - - foreach (var entity in entities) - { - result.Add(await MapTimelineFromEntity(entity)); - } - - return result; - } - - public async Task CreateTimeline(string name, long owner) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - ValidateTimelineName(name, nameof(name)); - - var user = await _userService.GetUserById(owner); - - var conflict = await _database.Timelines.AnyAsync(t => t.Name == name); - - if (conflict) - throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); - - var newEntity = CreateNewTimelineEntity(name, user.Id!.Value); - - _database.Timelines.Add(newEntity); - await _database.SaveChangesAsync(); - - return await MapTimelineFromEntity(newEntity); - } - - public async Task DeleteTimeline(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - ValidateTimelineName(name, nameof(name)); - - var entity = await _database.Timelines.Where(t => t.Name == name).SingleOrDefaultAsync(); - - if (entity == null) - throw new TimelineNotExistException(name); - - _database.Timelines.Remove(entity); - await _database.SaveChangesAsync(); - } - - public async Task ChangeTimelineName(string oldTimelineName, string newTimelineName) - { - if (oldTimelineName == null) - throw new ArgumentNullException(nameof(oldTimelineName)); - if (newTimelineName == null) - throw new ArgumentNullException(nameof(newTimelineName)); - - ValidateTimelineName(oldTimelineName, nameof(oldTimelineName)); - ValidateTimelineName(newTimelineName, nameof(newTimelineName)); - - var entity = await _database.Timelines.Include(t => t.Members).Where(t => t.Name == oldTimelineName).SingleOrDefaultAsync(); - - if (entity == null) - throw new TimelineNotExistException(oldTimelineName); - - if (oldTimelineName == newTimelineName) - return await MapTimelineFromEntity(entity); - - var conflict = await _database.Timelines.AnyAsync(t => t.Name == newTimelineName); - - if (conflict) - throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); - - var now = _clock.GetCurrentTime(); - - entity.Name = newTimelineName; - entity.NameLastModified = now; - entity.LastModified = now; - - await _database.SaveChangesAsync(); - - return await MapTimelineFromEntity(entity); - } - } -} diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs deleted file mode 100644 index b41c45fd..00000000 --- a/Timeline/Services/UserAvatarService.cs +++ /dev/null @@ -1,265 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Services.Exceptions; - -namespace Timeline.Services -{ - public class Avatar - { - public string Type { get; set; } = default!; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "DTO Object")] - public byte[] Data { get; set; } = default!; - } - - public class AvatarInfo - { - public Avatar Avatar { get; set; } = default!; - public DateTime LastModified { get; set; } - - public CacheableData ToCacheableData() - { - return new CacheableData(Avatar.Type, Avatar.Data, LastModified); - } - } - - /// - /// Provider for default user avatar. - /// - /// - /// Mainly for unit tests. - /// - public interface IDefaultUserAvatarProvider - { - /// - /// Get the etag of default avatar. - /// - /// - Task GetDefaultAvatarETag(); - - /// - /// Get the default avatar. - /// - Task GetDefaultAvatar(); - } - - public interface IUserAvatarService - { - /// - /// Get the etag of a user's avatar. Warning: This method does not check the user existence. - /// - /// The id of the user to get avatar etag of. - /// The etag. - Task GetAvatarETag(long id); - - /// - /// Get avatar of a user. If the user has no avatar set, a default one is returned. Warning: This method does not check the user existence. - /// - /// The id of the user to get avatar of. - /// The avatar info. - Task GetAvatar(long id); - - /// - /// Set avatar for a user. Warning: This method does not check the user existence. - /// - /// The id of the user to set avatar for. - /// The avatar. Can be null to delete the saved avatar. - /// The etag of the avatar. - /// Thrown if any field in is null when is not null. - /// Thrown if avatar is of bad format. - Task SetAvatar(long id, Avatar? avatar); - } - - // TODO! : Make this configurable. - public class DefaultUserAvatarProvider : IDefaultUserAvatarProvider - { - private readonly IETagGenerator _eTagGenerator; - - private readonly string _avatarPath; - - private byte[] _cacheData = default!; - private DateTime _cacheLastModified; - private string _cacheETag = default!; - - public DefaultUserAvatarProvider(IWebHostEnvironment environment, IETagGenerator eTagGenerator) - { - _avatarPath = Path.Combine(environment.ContentRootPath, "default-avatar.png"); - _eTagGenerator = eTagGenerator; - } - - private async Task CheckAndInit() - { - var path = _avatarPath; - if (_cacheData == null || File.GetLastWriteTime(path) > _cacheLastModified) - { - _cacheData = await File.ReadAllBytesAsync(path); - _cacheLastModified = File.GetLastWriteTime(path); - _cacheETag = await _eTagGenerator.Generate(_cacheData); - } - } - - public async Task GetDefaultAvatarETag() - { - await CheckAndInit(); - return _cacheETag; - } - - public async Task GetDefaultAvatar() - { - await CheckAndInit(); - return new AvatarInfo - { - Avatar = new Avatar - { - Type = "image/png", - Data = _cacheData - }, - LastModified = _cacheLastModified - }; - } - } - - public class UserAvatarService : IUserAvatarService - { - - private readonly ILogger _logger; - - private readonly DatabaseContext _database; - - private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider; - - private readonly IImageValidator _imageValidator; - - private readonly IDataManager _dataManager; - - private readonly IClock _clock; - - public UserAvatarService( - ILogger logger, - DatabaseContext database, - IDefaultUserAvatarProvider defaultUserAvatarProvider, - IImageValidator imageValidator, - IDataManager dataManager, - IClock clock) - { - _logger = logger; - _database = database; - _defaultUserAvatarProvider = defaultUserAvatarProvider; - _imageValidator = imageValidator; - _dataManager = dataManager; - _clock = clock; - } - - public async Task GetAvatarETag(long id) - { - var eTag = (await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.DataTag }).SingleOrDefaultAsync())?.DataTag; - if (eTag == null) - return await _defaultUserAvatarProvider.GetDefaultAvatarETag(); - else - return eTag; - } - - public async Task GetAvatar(long id) - { - var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).Select(a => new { a.Type, a.DataTag, a.LastModified }).SingleOrDefaultAsync(); - - if (avatarEntity != null) - { - if (!LanguageHelper.AreSame(avatarEntity.DataTag == null, avatarEntity.Type == null)) - { - var message = Resources.Services.UserAvatarService.ExceptionDatabaseCorruptedDataAndTypeNotSame; - _logger.LogCritical(message); - throw new DatabaseCorruptedException(message); - } - - - if (avatarEntity.DataTag != null) - { - var data = await _dataManager.GetEntry(avatarEntity.DataTag); - return new AvatarInfo - { - Avatar = new Avatar - { - Type = avatarEntity.Type!, - Data = data - }, - LastModified = avatarEntity.LastModified - }; - } - } - var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar(); - if (avatarEntity != null) - defaultAvatar.LastModified = defaultAvatar.LastModified > avatarEntity.LastModified ? defaultAvatar.LastModified : avatarEntity.LastModified; - return defaultAvatar; - } - - public async Task SetAvatar(long id, Avatar? avatar) - { - if (avatar != null) - { - if (avatar.Data == null) - throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarDataNull, nameof(avatar)); - if (string.IsNullOrEmpty(avatar.Type)) - throw new ArgumentException(Resources.Services.UserAvatarService.ExceptionAvatarTypeNullOrEmpty, nameof(avatar)); - } - - var avatarEntity = await _database.UserAvatars.Where(a => a.UserId == id).SingleOrDefaultAsync(); - - if (avatar == null) - { - if (avatarEntity != null && avatarEntity.DataTag != null) - { - await _dataManager.FreeEntry(avatarEntity.DataTag); - avatarEntity.DataTag = null; - avatarEntity.Type = null; - avatarEntity.LastModified = _clock.GetCurrentTime(); - await _database.SaveChangesAsync(); - _logger.LogInformation(Resources.Services.UserAvatarService.LogUpdateEntity); - } - return await _defaultUserAvatarProvider.GetDefaultAvatarETag(); - } - else - { - await _imageValidator.Validate(avatar.Data, avatar.Type, true); - var tag = await _dataManager.RetainEntry(avatar.Data); - var oldTag = avatarEntity?.DataTag; - var create = avatarEntity == null; - if (avatarEntity == null) - { - avatarEntity = new UserAvatarEntity(); - _database.UserAvatars.Add(avatarEntity); - } - avatarEntity.DataTag = tag; - avatarEntity.Type = avatar.Type; - avatarEntity.LastModified = _clock.GetCurrentTime(); - avatarEntity.UserId = id; - await _database.SaveChangesAsync(); - _logger.LogInformation(create ? - Resources.Services.UserAvatarService.LogCreateEntity - : Resources.Services.UserAvatarService.LogUpdateEntity); - if (oldTag != null) - { - await _dataManager.FreeEntry(oldTag); - } - - return avatarEntity.DataTag; - } - } - } - - public static class UserAvatarServiceCollectionExtensions - { - public static void AddUserAvatarService(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - } - } -} diff --git a/Timeline/Services/UserDeleteService.cs b/Timeline/Services/UserDeleteService.cs deleted file mode 100644 index 845de573..00000000 --- a/Timeline/Services/UserDeleteService.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Models.Validation; -using static Timeline.Resources.Services.UserService; - -namespace Timeline.Services -{ - public interface IUserDeleteService - { - /// - /// Delete a user of given username. - /// - /// Username of the user to delete. Can't be null. - /// True if user is deleted, false if user not exist. - /// Thrown if is null. - /// Thrown when is of bad format. - Task DeleteUser(string username); - } - - public class UserDeleteService : IUserDeleteService - { - private readonly ILogger _logger; - - private readonly DatabaseContext _databaseContext; - - private readonly ITimelineService _timelineService; - - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - - public UserDeleteService(ILogger logger, DatabaseContext databaseContext, ITimelineService timelineService) - { - _logger = logger; - _databaseContext = databaseContext; - _timelineService = timelineService; - } - - public async Task DeleteUser(string username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - if (!_usernameValidator.Validate(username, out var message)) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), nameof(username)); - } - - var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); - if (user == null) - return false; - - await _timelineService.DeleteAllPostsOfUser(user.Id); - - _databaseContext.Users.Remove(user); - - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(LogDatabaseRemove, ("Id", user.Id), ("Username", user.Username))); - - return true; - } - - } -} diff --git a/Timeline/Services/UserRoleConvert.cs b/Timeline/Services/UserRoleConvert.cs deleted file mode 100644 index f27ee1bb..00000000 --- a/Timeline/Services/UserRoleConvert.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Timeline.Entities; - -namespace Timeline.Services -{ - public static class UserRoleConvert - { - public const string UserRole = UserRoles.User; - public const string AdminRole = UserRoles.Admin; - - public static string[] ToArray(bool administrator) - { - return administrator ? new string[] { UserRole, AdminRole } : new string[] { UserRole }; - } - - public static string[] ToArray(string s) - { - return s.Split(',').ToArray(); - } - - public static bool ToBool(IReadOnlyCollection roles) - { - return roles.Contains(AdminRole); - } - - public static string ToString(IReadOnlyCollection roles) - { - return string.Join(',', roles); - } - - public static string ToString(bool administrator) - { - return administrator ? UserRole + "," + AdminRole : UserRole; - } - - public static bool ToBool(string s) - { - return s.Contains("admin", StringComparison.InvariantCulture); - } - } -} diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs deleted file mode 100644 index 821bc33d..00000000 --- a/Timeline/Services/UserService.cs +++ /dev/null @@ -1,437 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using Timeline.Entities; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Models.Validation; -using Timeline.Services.Exceptions; -using static Timeline.Resources.Services.UserService; - -namespace Timeline.Services -{ - public interface IUserService - { - /// - /// Try to verify the given username and password. - /// - /// The username of the user to verify. - /// The password of the user to verify. - /// The user info and auth info. - /// Thrown when or is null. - /// Thrown when is of bad format or is empty. - /// Thrown when the user with given username does not exist. - /// Thrown when password is wrong. - Task VerifyCredential(string username, string password); - - /// - /// Try to get a user by id. - /// - /// The id of the user. - /// The user info. - /// Thrown when the user with given id does not exist. - Task GetUserById(long id); - - /// - /// Get the user info of given username. - /// - /// Username of the user. - /// The info of the user. - /// Thrown when is null. - /// Thrown when is of bad format. - /// Thrown when the user with given username does not exist. - Task GetUserByUsername(string username); - - /// - /// Get the user id of given username. - /// - /// Username of the user. - /// The id of the user. - /// Thrown when is null. - /// Thrown when is of bad format. - /// Thrown when the user with given username does not exist. - Task GetUserIdByUsername(string username); - - /// - /// List all users. - /// - /// The user info of users. - Task GetUsers(); - - /// - /// Create a user with given info. - /// - /// The info of new user. - /// The the new user. - /// Thrown when is null. - /// Thrown when some fields in is bad. - /// Thrown when a user with given username already exists. - /// - /// must not be null and must be a valid username. - /// must not be null or empty. - /// is false by default (null). - /// must be a valid nickname if set. It is empty by default. - /// Other fields are ignored. - /// - Task CreateUser(User info); - - /// - /// Modify a user's info. - /// - /// The id of the user. - /// The new info. May be null. - /// The new user info. - /// Thrown when some fields in is bad. - /// Thrown when user with given id does not exist. - /// - /// Only , , and will be used. - /// If null, then not change. - /// Other fields are ignored. - /// Version will increase if password is changed. - /// - /// must be a valid username if set. - /// can't be empty if set. - /// must be a valid nickname if set. - /// - /// - /// - Task ModifyUser(long id, User? info); - - /// - /// Modify a user's info. - /// - /// The username of the user. - /// The new info. May be null. - /// The new user info. - /// Thrown when is null. - /// Thrown when is of bad format or some fields in is bad. - /// Thrown when user with given id does not exist. - /// Thrown when user with the newusername already exist. - /// - /// Only , and will be used. - /// If null, then not change. - /// Other fields are ignored. - /// After modified, even if nothing is changed, version will increase. - /// - /// must be a valid username if set. - /// can't be empty if set. - /// must be a valid nickname if set. - /// - /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. - /// - /// - Task ModifyUser(string username, User? info); - - /// - /// Try to change a user's password with old password. - /// - /// The id of user to change password of. - /// Old password. - /// New password. - /// Thrown if or is null. - /// Thrown if or is empty. - /// Thrown if the user with given username does not exist. - /// Thrown if the old password is wrong. - Task ChangePassword(long id, string oldPassword, string newPassword); - } - - public class UserService : IUserService - { - private readonly ILogger _logger; - private readonly IClock _clock; - - private readonly DatabaseContext _databaseContext; - - private readonly IPasswordService _passwordService; - - private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); - public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) - { - _logger = logger; - _clock = clock; - _databaseContext = databaseContext; - _passwordService = passwordService; - } - - private void CheckUsernameFormat(string username, string? paramName) - { - if (!_usernameValidator.Validate(username, out var message)) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName); - } - } - - private static void CheckPasswordFormat(string password, string? paramName) - { - if (password.Length == 0) - { - throw new ArgumentException(ExceptionPasswordEmpty, paramName); - } - } - - private void CheckNicknameFormat(string nickname, string? paramName) - { - if (!_nicknameValidator.Validate(nickname, out var message)) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionNicknameBadFormat, message), paramName); - } - } - - private static void ThrowUsernameConflict() - { - throw new EntityAlreadyExistException(EntityNames.User, ExceptionUsernameConflict); - } - - private static User CreateUserFromEntity(UserEntity entity) - { - return new User - { - UniqueId = entity.UniqueId, - Username = entity.Username, - Administrator = UserRoleConvert.ToBool(entity.Roles), - Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, - Id = entity.Id, - Version = entity.Version, - CreateTime = entity.CreateTime, - UsernameChangeTime = entity.UsernameChangeTime, - LastModified = entity.LastModified - }; - } - - public async Task VerifyCredential(string username, string password) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - - CheckUsernameFormat(username, nameof(username)); - CheckPasswordFormat(password, nameof(password)); - - var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); - - if (entity == null) - throw new UserNotExistException(username); - - if (!_passwordService.VerifyPassword(entity.Password, password)) - throw new BadPasswordException(password); - - return CreateUserFromEntity(entity); - } - - public async Task GetUserById(long id) - { - var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - - if (user == null) - throw new UserNotExistException(id); - - return CreateUserFromEntity(user); - } - - public async Task GetUserByUsername(string username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - CheckUsernameFormat(username, nameof(username)); - - var entity = await _databaseContext.Users.Where(user => user.Username == username).SingleOrDefaultAsync(); - - if (entity == null) - throw new UserNotExistException(username); - - return CreateUserFromEntity(entity); - } - - public async Task GetUserIdByUsername(string username) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - - CheckUsernameFormat(username, nameof(username)); - - var entity = await _databaseContext.Users.Where(user => user.Username == username).Select(u => new { u.Id }).SingleOrDefaultAsync(); - - if (entity == null) - throw new UserNotExistException(username); - - return entity.Id; - } - - public async Task GetUsers() - { - var entities = await _databaseContext.Users.ToArrayAsync(); - return entities.Select(user => CreateUserFromEntity(user)).ToArray(); - } - - public async Task CreateUser(User info) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - if (info.Username == null) - throw new ArgumentException(ExceptionUsernameNull, nameof(info)); - CheckUsernameFormat(info.Username, nameof(info)); - - if (info.Password == null) - throw new ArgumentException(ExceptionPasswordNull, nameof(info)); - CheckPasswordFormat(info.Password, nameof(info)); - - if (info.Nickname != null) - CheckNicknameFormat(info.Nickname, nameof(info)); - - var username = info.Username; - - var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); - if (conflict) - ThrowUsernameConflict(); - - var administrator = info.Administrator ?? false; - var password = info.Password; - var nickname = info.Nickname; - - var newEntity = new UserEntity - { - Username = username, - Password = _passwordService.HashPassword(password), - Roles = UserRoleConvert.ToString(administrator), - Nickname = nickname, - Version = 1 - }; - _databaseContext.Users.Add(newEntity); - await _databaseContext.SaveChangesAsync(); - - _logger.LogInformation(Log.Format(LogDatabaseCreate, - ("Id", newEntity.Id), ("Username", username), ("Administrator", administrator))); - - return CreateUserFromEntity(newEntity); - } - - private void ValidateModifyUserInfo(User? info) - { - if (info != null) - { - if (info.Username != null) - CheckUsernameFormat(info.Username, nameof(info)); - - if (info.Password != null) - CheckPasswordFormat(info.Password, nameof(info)); - - if (info.Nickname != null) - CheckNicknameFormat(info.Nickname, nameof(info)); - } - } - - private async Task UpdateUserEntity(UserEntity entity, User? info) - { - if (info != null) - { - var now = _clock.GetCurrentTime(); - bool updateLastModified = false; - - var username = info.Username; - if (username != null && username != entity.Username) - { - var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); - if (conflict) - ThrowUsernameConflict(); - - entity.Username = username; - entity.UsernameChangeTime = now; - updateLastModified = true; - } - - var password = info.Password; - if (password != null) - { - entity.Password = _passwordService.HashPassword(password); - entity.Version += 1; - } - - var administrator = info.Administrator; - if (administrator.HasValue && UserRoleConvert.ToBool(entity.Roles) != administrator) - { - entity.Roles = UserRoleConvert.ToString(administrator.Value); - updateLastModified = true; - } - - var nickname = info.Nickname; - if (nickname != null && nickname != entity.Nickname) - { - entity.Nickname = nickname; - updateLastModified = true; - } - - if (updateLastModified) - { - entity.LastModified = now; - } - } - } - - - public async Task ModifyUser(long id, User? info) - { - ValidateModifyUserInfo(info); - - var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - if (entity == null) - throw new UserNotExistException(id); - - await UpdateUserEntity(entity, info); - - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(LogDatabaseUpdate, ("Id", id)); - - return CreateUserFromEntity(entity); - } - - public async Task ModifyUser(string username, User? info) - { - if (username == null) - throw new ArgumentNullException(nameof(username)); - CheckUsernameFormat(username, nameof(username)); - - ValidateModifyUserInfo(info); - - var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); - if (entity == null) - throw new UserNotExistException(username); - - await UpdateUserEntity(entity, info); - - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(LogDatabaseUpdate, ("Username", username)); - - return CreateUserFromEntity(entity); - } - - public async Task ChangePassword(long id, string oldPassword, string newPassword) - { - if (oldPassword == null) - throw new ArgumentNullException(nameof(oldPassword)); - if (newPassword == null) - throw new ArgumentNullException(nameof(newPassword)); - CheckPasswordFormat(oldPassword, nameof(oldPassword)); - CheckPasswordFormat(newPassword, nameof(newPassword)); - - var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - - if (entity == null) - throw new UserNotExistException(id); - - if (!_passwordService.VerifyPassword(entity.Password, oldPassword)) - throw new BadPasswordException(oldPassword); - - entity.Password = _passwordService.HashPassword(newPassword); - entity.Version += 1; - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(LogDatabaseUpdate, ("Id", id), ("Operation", "Change password"))); - } - } -} diff --git a/Timeline/Services/UserTokenException.cs b/Timeline/Services/UserTokenException.cs deleted file mode 100644 index d25fabb3..00000000 --- a/Timeline/Services/UserTokenException.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace Timeline.Services -{ - - [Serializable] - public class UserTokenException : Exception - { - public UserTokenException() { } - public UserTokenException(string message) : base(message) { } - public UserTokenException(string message, Exception inner) : base(message, inner) { } - public UserTokenException(string token, string message) : base(message) { Token = token; } - public UserTokenException(string token, string message, Exception inner) : base(message, inner) { Token = token; } - protected UserTokenException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public string Token { get; private set; } = ""; - } - - - [Serializable] - public class UserTokenTimeExpireException : UserTokenException - { - public UserTokenTimeExpireException() : base(Resources.Services.Exception.UserTokenTimeExpireException) { } - public UserTokenTimeExpireException(string message) : base(message) { } - public UserTokenTimeExpireException(string message, Exception inner) : base(message, inner) { } - public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime) : base(token, Resources.Services.Exception.UserTokenTimeExpireException) { ExpireTime = expireTime; VerifyTime = verifyTime; } - public UserTokenTimeExpireException(string token, DateTime expireTime, DateTime verifyTime, Exception inner) : base(token, Resources.Services.Exception.UserTokenTimeExpireException, inner) { ExpireTime = expireTime; VerifyTime = verifyTime; } - protected UserTokenTimeExpireException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public DateTime ExpireTime { get; private set; } - - public DateTime VerifyTime { get; private set; } - } - - [Serializable] - public class UserTokenBadVersionException : UserTokenException - { - public UserTokenBadVersionException() : base(Resources.Services.Exception.UserTokenBadVersionException) { } - public UserTokenBadVersionException(string message) : base(message) { } - public UserTokenBadVersionException(string message, Exception inner) : base(message, inner) { } - public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion) : base(token, Resources.Services.Exception.UserTokenBadVersionException) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; } - public UserTokenBadVersionException(string token, long tokenVersion, long requiredVersion, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadVersionException, inner) { TokenVersion = tokenVersion; RequiredVersion = requiredVersion; } - protected UserTokenBadVersionException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - - public long TokenVersion { get; set; } - - public long RequiredVersion { get; set; } - } - - [Serializable] - public class UserTokenBadFormatException : UserTokenException - { - public UserTokenBadFormatException() : base(Resources.Services.Exception.UserTokenBadFormatException) { } - public UserTokenBadFormatException(string token) : base(token, Resources.Services.Exception.UserTokenBadFormatException) { } - public UserTokenBadFormatException(string token, string message) : base(token, message) { } - public UserTokenBadFormatException(string token, Exception inner) : base(token, Resources.Services.Exception.UserTokenBadFormatException, inner) { } - public UserTokenBadFormatException(string token, string message, Exception inner) : base(token, message, inner) { } - protected UserTokenBadFormatException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - } -} diff --git a/Timeline/Services/UserTokenManager.cs b/Timeline/Services/UserTokenManager.cs deleted file mode 100644 index 813dae67..00000000 --- a/Timeline/Services/UserTokenManager.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Threading.Tasks; -using Timeline.Helpers; -using Timeline.Models; -using Timeline.Services.Exceptions; - -namespace Timeline.Services -{ - public class UserTokenCreateResult - { - public string Token { get; set; } = default!; - public User User { get; set; } = default!; - } - - public interface IUserTokenManager - { - /// - /// Try to create a token for given username and password. - /// - /// The username. - /// The password. - /// The expire time of the token. - /// The created token and the user info. - /// Thrown when or is null. - /// Thrown when is of bad format. - /// Thrown when the user with does not exist. - /// Thrown when is wrong. - public Task CreateToken(string username, string password, DateTime? expireAt = null); - - /// - /// Verify a token and get the saved user info. This also check the database for existence of the user. - /// - /// The token. - /// The user stored in token. - /// Thrown when is null. - /// Thrown when the token is expired. - /// Thrown when the token is of bad version. - /// Thrown when the token is of bad format. - /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued. - public Task VerifyToken(string token); - } - - public class UserTokenManager : IUserTokenManager - { - private readonly ILogger _logger; - private readonly IUserService _userService; - private readonly IUserTokenService _userTokenService; - private readonly IClock _clock; - - public UserTokenManager(ILogger logger, IUserService userService, IUserTokenService userTokenService, IClock clock) - { - _logger = logger; - _userService = userService; - _userTokenService = userTokenService; - _clock = clock; - } - - public async Task CreateToken(string username, string password, DateTime? expireAt = null) - { - expireAt = expireAt?.MyToUtc(); - - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - - var user = await _userService.VerifyCredential(username, password); - var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id!.Value, Version = user.Version!.Value, ExpireAt = expireAt }); - - return new UserTokenCreateResult { Token = token, User = user }; - } - - - public async Task VerifyToken(string token) - { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - var tokenInfo = _userTokenService.VerifyToken(token); - - if (tokenInfo.ExpireAt.HasValue) - { - var currentTime = _clock.GetCurrentTime(); - if (tokenInfo.ExpireAt < currentTime) - throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime); - } - - var user = await _userService.GetUserById(tokenInfo.Id); - - if (tokenInfo.Version < user.Version) - throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version.Value); - - return user; - } - } -} diff --git a/Timeline/Services/UserTokenService.cs b/Timeline/Services/UserTokenService.cs deleted file mode 100644 index 86f3a0f7..00000000 --- a/Timeline/Services/UserTokenService.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Globalization; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using Timeline.Configs; -using Timeline.Entities; - -namespace Timeline.Services -{ - public class UserTokenInfo - { - public long Id { get; set; } - public long Version { get; set; } - public DateTime? ExpireAt { get; set; } - } - - public interface IUserTokenService - { - /// - /// Create a token for a given token info. - /// - /// The info to generate token. - /// Return the generated token. - /// Thrown when is null. - string GenerateToken(UserTokenInfo tokenInfo); - - /// - /// Verify a token and get the saved info. - /// - /// The token to verify. - /// The saved info in token. - /// Thrown when is null. - /// Thrown when the token is of bad format. - /// - /// If this method throw , it usually means the token is not created by this service. - /// - UserTokenInfo VerifyToken(string token); - } - - public class JwtUserTokenService : IUserTokenService - { - private const string VersionClaimType = "timeline_version"; - - private readonly IOptionsMonitor _jwtConfig; - private readonly IClock _clock; - - private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler(); - private SymmetricSecurityKey _tokenSecurityKey; - - public JwtUserTokenService(IOptionsMonitor jwtConfig, IClock clock, DatabaseContext database) - { - _jwtConfig = jwtConfig; - _clock = clock; - - var key = database.JwtToken.Select(t => t.Key).SingleOrDefault(); - - if (key == null) - { - throw new InvalidOperationException(Resources.Services.UserTokenService.JwtKeyNotExist); - } - - _tokenSecurityKey = new SymmetricSecurityKey(key); - } - - public string GenerateToken(UserTokenInfo tokenInfo) - { - if (tokenInfo == null) - throw new ArgumentNullException(nameof(tokenInfo)); - - var config = _jwtConfig.CurrentValue; - - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokenInfo.Id.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64)); - identity.AddClaim(new Claim(VersionClaimType, tokenInfo.Version.ToString(CultureInfo.InvariantCulture.NumberFormat), ClaimValueTypes.Integer64)); - - var tokenDescriptor = new SecurityTokenDescriptor() - { - Subject = identity, - Issuer = config.Issuer, - Audience = config.Audience, - SigningCredentials = new SigningCredentials(_tokenSecurityKey, SecurityAlgorithms.HmacSha384), - IssuedAt = _clock.GetCurrentTime(), - Expires = tokenInfo.ExpireAt.GetValueOrDefault(_clock.GetCurrentTime().AddSeconds(config.DefaultExpireOffset)), - NotBefore = _clock.GetCurrentTime() // I must explicitly set this or it will use the current time by default and mock is not work in which case test will not pass. - }; - - var token = _tokenHandler.CreateToken(tokenDescriptor); - var tokenString = _tokenHandler.WriteToken(token); - - return tokenString; - } - - - public UserTokenInfo VerifyToken(string token) - { - if (token == null) - throw new ArgumentNullException(nameof(token)); - - var config = _jwtConfig.CurrentValue; - try - { - var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateIssuerSigningKey = true, - ValidateLifetime = false, - ValidIssuer = config.Issuer, - ValidAudience = config.Audience, - IssuerSigningKey = _tokenSecurityKey - }, out var t); - - var idClaim = principal.FindFirstValue(ClaimTypes.NameIdentifier); - if (idClaim == null) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoIdClaim); - if (!long.TryParse(idClaim, out var id)) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.IdClaimBadFormat); - - var versionClaim = principal.FindFirstValue(VersionClaimType); - if (versionClaim == null) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.NoVersionClaim); - if (!long.TryParse(versionClaim, out var version)) - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.VersionClaimBadFormat); - - var decodedToken = (JwtSecurityToken)t; - var exp = decodedToken.Payload.Exp; - DateTime? expireAt = null; - if (exp.HasValue) - { - expireAt = EpochTime.DateTime(exp.Value); - } - - return new UserTokenInfo - { - Id = id, - Version = version, - ExpireAt = expireAt - }; - } - catch (Exception e) when (e is SecurityTokenException || e is ArgumentException) - { - throw new JwtUserTokenBadFormatException(token, JwtUserTokenBadFormatException.ErrorKind.Other, e); - } - } - } -} diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs deleted file mode 100644 index 82c231cb..00000000 --- a/Timeline/Startup.cs +++ /dev/null @@ -1,187 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using NSwag; -using NSwag.Generation.Processors.Security; -using System; -using System.ComponentModel; -using System.Net.Mime; -using System.Text.Json.Serialization; -using Timeline.Auth; -using Timeline.Configs; -using Timeline.Entities; -using Timeline.Formatters; -using Timeline.Helpers; -using Timeline.Models.Converters; -using Timeline.Routes; -using Timeline.Services; -using Timeline.Swagger; - -namespace Timeline -{ - public class Startup - { - private readonly bool disableFrontEnd; - private readonly bool useMockFrontEnd; - - public Startup(IConfiguration configuration, IWebHostEnvironment environment) - { - Environment = environment; - Configuration = configuration; - - disableFrontEnd = Configuration.GetValue(ApplicationConfiguration.DisableFrontEndKey) ?? false; - useMockFrontEnd = Configuration.GetValue(ApplicationConfiguration.UseMockFrontEndKey) ?? false; - } - - public IWebHostEnvironment Environment { get; } - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - TypeDescriptor.AddAttributes(typeof(DateTime), new TypeConverterAttribute(typeof(MyDateTimeConverter))); - - services.AddControllers(setup => - { - setup.InputFormatters.Add(new StringInputFormatter()); - setup.InputFormatters.Add(new BytesInputFormatter()); - setup.Filters.Add(new ConsumesAttribute(MediaTypeNames.Application.Json, "text/json")); - setup.Filters.Add(new ProducesAttribute(MediaTypeNames.Application.Json, "text/json")); - setup.UseApiRoutePrefix("api"); - }) - .AddJsonOptions(options => - { - options.JsonSerializerOptions.IgnoreNullValues = true; - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - options.JsonSerializerOptions.Converters.Add(new JsonDateTimeConverter()); - }) - .ConfigureApiBehaviorOptions(options => - { - options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory; - }); - - services.Configure(Configuration.GetSection("Jwt")); - services.AddAuthentication(AuthenticationConstants.Scheme) - .AddScheme(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { }); - services.AddAuthorization(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddAutoMapper(GetType().Assembly); - - services.AddTransient(); - - services.AddTransient(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddScoped(); - services.AddScoped(); - - services.AddScoped(); - - services.AddUserAvatarService(); - - services.AddScoped(); - - services.TryAddSingleton(); - - services.AddDbContext((services, options) => - { - var pathProvider = services.GetRequiredService(); - options.UseSqlite($"Data Source={pathProvider.GetDatabaseFilePath()}"); - }); - - services.AddSwaggerDocument(document => - { - document.DocumentName = "Timeline"; - document.Title = "Timeline REST API Reference"; - document.Version = typeof(Startup).Assembly.GetName().Version?.ToString() ?? "unknown version"; - document.DocumentProcessors.Add(new DocumentDescriptionDocumentProcessor()); - document.DocumentProcessors.Add( - new SecurityDefinitionAppender("JWT", - new OpenApiSecurityScheme - { - Type = OpenApiSecuritySchemeType.ApiKey, - Name = "Authorization", - In = OpenApiSecurityApiKeyLocation.Header, - Description = "Create token via `/api/token/create` ." - })); - document.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT")); - document.OperationProcessors.Add(new DefaultDescriptionOperationProcessor()); - document.OperationProcessors.Add(new ByteDataRequestOperationProcessor()); - }); - - if (!disableFrontEnd) - { - if (useMockFrontEnd) - { - services.AddSpaStaticFiles(config => - { - config.RootPath = "MockClientApp"; - }); - - } - else if (!Environment.IsDevelopment()) // In development, we don't want to serve dist. Or it will take precedence than front end dev server. - { - services.AddSpaStaticFiles(config => - { - config.RootPath = "ClientApp/dist"; - }); - } - } - } - - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - - if (!disableFrontEnd && (useMockFrontEnd || !Environment.IsDevelopment())) - { - app.UseSpaStaticFiles(new StaticFileOptions - { - ServeUnknownFileTypes = true - }); - } - - app.UseOpenApi(); - app.UseReDoc(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - - UnknownEndpointMiddleware.Attach(app); - - if (!disableFrontEnd) - { - app.UseSpa(spa => - { - spa.Options.SourcePath = useMockFrontEnd ? "MockClientApp" : "ClientApp"; - - if (!useMockFrontEnd && (Configuration.GetValue(ApplicationConfiguration.UseProxyFrontEndKey) ?? false)) - { - spa.UseProxyToSpaDevelopmentServer(new UriBuilder("http", "localhost", 3000).Uri); - } - }); - } - } - } -} diff --git a/Timeline/Swagger/ApiConvention.cs b/Timeline/Swagger/ApiConvention.cs deleted file mode 100644 index dbf0b2fe..00000000 --- a/Timeline/Swagger/ApiConvention.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -[assembly: ApiConventionType(typeof(Timeline.Controllers.ApiConvention))] - -namespace Timeline.Controllers -{ - // There is some bug if nullable is enable. So disable it. -#nullable disable - /// - /// My api convention. - /// - public static class ApiConvention - { - } -} diff --git a/Timeline/Swagger/ByteDataRequestOperationProcessor.cs b/Timeline/Swagger/ByteDataRequestOperationProcessor.cs deleted file mode 100644 index 887831ac..00000000 --- a/Timeline/Swagger/ByteDataRequestOperationProcessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NJsonSchema; -using NSwag; -using NSwag.Generation.Processors; -using NSwag.Generation.Processors.Contexts; -using System.Linq; -using Timeline.Models; - -namespace Timeline.Swagger -{ - /// - /// Coerce ByteData body type into the right one. - /// - public class ByteDataRequestOperationProcessor : IOperationProcessor - { - /// - public bool Process(OperationProcessorContext context) - { - var hasByteDataBody = context.MethodInfo.GetParameters().Where(p => p.ParameterType == typeof(ByteData)).Any(); - if (hasByteDataBody) - { - var bodyParameter = context.OperationDescription.Operation.Parameters.Where(p => p.Kind == OpenApiParameterKind.Body).Single(); - bodyParameter.Schema = JsonSchema.FromType(); - } - return true; - } - } -} diff --git a/Timeline/Swagger/DefaultDescriptionOperationProcessor.cs b/Timeline/Swagger/DefaultDescriptionOperationProcessor.cs deleted file mode 100644 index 4967cc6a..00000000 --- a/Timeline/Swagger/DefaultDescriptionOperationProcessor.cs +++ /dev/null @@ -1,39 +0,0 @@ -using NSwag.Generation.Processors; -using NSwag.Generation.Processors.Contexts; -using System.Collections.Generic; - -namespace Timeline.Swagger -{ - /// - /// Swagger operation processor that adds default description to response. - /// - public class DefaultDescriptionOperationProcessor : IOperationProcessor - { - private readonly Dictionary defaultDescriptionMap = new Dictionary - { - ["200"] = "Succeeded to perform the operation.", - ["304"] = "Item does not change.", - ["400"] = "See code and message for error info.", - ["401"] = "You need to log in to perform this operation.", - ["403"] = "You have no permission to perform the operation.", - ["404"] = "Item does not exist. See code and message for error info." - }; - - /// - public bool Process(OperationProcessorContext context) - { - var responses = context.OperationDescription.Operation.Responses; - - foreach (var (httpStatusCode, res) in responses) - { - if (!string.IsNullOrEmpty(res.Description)) continue; - if (defaultDescriptionMap.ContainsKey(httpStatusCode)) - { - res.Description = defaultDescriptionMap[httpStatusCode]; - } - } - - return true; - } - } -} diff --git a/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs b/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs deleted file mode 100644 index dc5ddd96..00000000 --- a/Timeline/Swagger/DocumentDescriptionDocumentProcessor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using NSwag.Generation.Processors; -using NSwag.Generation.Processors.Contexts; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Timeline.Models.Http; - -namespace Timeline.Swagger -{ - public class DocumentDescriptionDocumentProcessor : IDocumentProcessor - { - private static Dictionary GetAllErrorCodes() - { - var errorCodes = new Dictionary(); - - void RecursiveCheckErrorCode(Type type) - { - foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) - .Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(int))) - { - var name = (type.FullName + "." + field.Name).Remove(0, typeof(ErrorCodes).FullName!.Length + 1).Replace("+", ".", StringComparison.OrdinalIgnoreCase); - int value = (int)field.GetRawConstantValue()!; - errorCodes.Add(name, value); - } - - foreach (var nestedType in type.GetNestedTypes()) - { - RecursiveCheckErrorCode(nestedType); - } - } - - RecursiveCheckErrorCode(typeof(ErrorCodes)); - - return errorCodes; - } - - public void Process(DocumentProcessorContext context) - { - StringBuilder description = new StringBuilder(); - description.AppendLine("# Error Codes"); - description.AppendLine("name | value"); - description.AppendLine("---- | -----"); - foreach (var (name, value) in GetAllErrorCodes()) - { - description.AppendLine($"`{name}` | `{value}`"); - } - - context.Document.Info.Description = description.ToString(); - } - } -} diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj deleted file mode 100644 index 39412a65..00000000 --- a/Timeline/Timeline.csproj +++ /dev/null @@ -1,289 +0,0 @@ - - - netcoreapp3.1 - 1f6fb74d-4277-4bc0-aeea-b1fc5ffb0b43 - crupest - - false - - 8.0 - enable - - true - Latest - ClientApp\ - - $(DefaultItemExcludes);$(SpaRoot)node_modules\** - - 0.3.0 - true - true - - 1701;1702;1591 - - - - - PreserveNewest - - - PreserveNewest - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - %(DistFiles.Identity) - PreserveNewest - true - - - - - - - - - - - True - True - AuthHandler.resx - - - True - True - ControllerAuthExtensions.resx - - - True - True - TimelineController.resx - - - True - True - TokenController.resx - - - True - True - UserAvatarController.resx - - - True - True - UserController.resx - - - True - True - Entities.resx - - - True - True - Filters.resx - - - True - True - DataCacheHelper.resx - - - True - True - Messages.resx - - - True - True - Common.resx - - - True - True - Exception.resx - - - True - True - NicknameValidator.resx - - - True - True - NameValidator.resx - - - True - True - Validator.resx - - - True - True - DataManager.resx - - - True - True - Exception.resx - - - True - True - Exceptions.resx - - - True - True - TimelineService.resx - - - True - True - UserAvatarService.resx - - - True - True - UserService.resx - - - True - True - UserTokenService.resx - - - - - - ResXFileCodeGenerator - AuthHandler.Designer.cs - - - ResXFileCodeGenerator - ControllerAuthExtensions.Designer.cs - - - ResXFileCodeGenerator - TimelineController.Designer.cs - - - Designer - ResXFileCodeGenerator - TokenController.Designer.cs - - - ResXFileCodeGenerator - UserAvatarController.Designer.cs - - - ResXFileCodeGenerator - UserController.Designer.cs - - - ResXFileCodeGenerator - Entities.Designer.cs - - - ResXFileCodeGenerator - Filters.Designer.cs - - - ResXFileCodeGenerator - DataCacheHelper.Designer.cs - - - ResXFileCodeGenerator - Messages.Designer.cs - - - ResXFileCodeGenerator - Common.Designer.cs - - - ResXFileCodeGenerator - Exception.Designer.cs - - - ResXFileCodeGenerator - NicknameValidator.Designer.cs - - - ResXFileCodeGenerator - NameValidator.Designer.cs - - - ResXFileCodeGenerator - Validator.Designer.cs - - - ResXFileCodeGenerator - DataManager.Designer.cs - - - ResXFileCodeGenerator - Exception.Designer.cs - - - ResXFileCodeGenerator - Exceptions.Designer.cs - - - ResXFileCodeGenerator - TimelineService.Designer.cs - - - ResXFileCodeGenerator - UserAvatarService.Designer.cs - - - ResXFileCodeGenerator - UserService.Designer.cs - - - ResXFileCodeGenerator - UserTokenService.Designer.cs - - - diff --git a/Timeline/appsettings.Development.json b/Timeline/appsettings.Development.json deleted file mode 100644 index a2880cbf..00000000 --- a/Timeline/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/Timeline/appsettings.json b/Timeline/appsettings.json deleted file mode 100644 index 804ca43a..00000000 --- a/Timeline/appsettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "Jwt": { - "Issuer": "api.crupest.xyz", - "Audience": "api.crupest.xyz" - } -} diff --git a/Timeline/default-avatar.png b/Timeline/default-avatar.png deleted file mode 100644 index 4086e1d2..00000000 Binary files a/Timeline/default-avatar.png and /dev/null differ diff --git a/Timeline/packages.lock.json b/Timeline/packages.lock.json deleted file mode 100644 index ed92c672..00000000 --- a/Timeline/packages.lock.json +++ /dev/null @@ -1,1563 +0,0 @@ -{ - "version": 1, - "dependencies": { - ".NETCoreApp,Version=v3.1": { - "AutoMapper": { - "type": "Direct", - "requested": "[10.1.1, )", - "resolved": "10.1.1", - "contentHash": "uMgbqOdu9ZG5cIOty0C85hzzayBH2i9BthnS5FlMqKtMSHDv4ts81a2jS1VFaDBVhlBeIqJ/kQKjQY95BZde9w==", - "dependencies": { - "Microsoft.CSharp": "4.7.0", - "System.Reflection.Emit": "4.7.0" - } - }, - "AutoMapper.Extensions.Microsoft.DependencyInjection": { - "type": "Direct", - "requested": "[8.1.0, )", - "resolved": "8.1.0", - "contentHash": "dQyGCAYcHbGuimVvCMu4Ea2S1oYOlgO9XfVdClmY5wgygJMZoS57emPzH0qNfknmtzMm4QbDO9i237W5IDjU1A==", - "dependencies": { - "AutoMapper": "[10.1.0, 11.0.0)", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.0", - "Microsoft.Extensions.Options": "3.0.0" - } - }, - "Microsoft.AspNetCore.SpaServices.Extensions": { - "type": "Direct", - "requested": "[3.1.9, )", - "resolved": "3.1.9", - "contentHash": "ciy2GCvRnh9C22laArLsaItS+72U6Hqf4nDYShdvFgcen2ZV+NNSitb/B3vsmFfIPM8m4mf2x4T+vZ6OlI5XaA==", - "dependencies": { - "Microsoft.AspNetCore.SpaServices": "3.1.9", - "Microsoft.Extensions.FileProviders.Physical": "3.1.9" - } - }, - "Microsoft.CodeAnalysis.FxCopAnalyzers": { - "type": "Direct", - "requested": "[3.3.0, )", - "resolved": "3.3.0", - "contentHash": "k3Icqx8kc+NrHImuiB8Jc/wd32Xeyd2B/7HOR5Qu9pyKzXQ4ikPeBAwzG2FSTuYhyIuNWvwL5k9yYBbbVz6w9w==", - "dependencies": { - "Microsoft.CodeAnalysis.VersionCheckAnalyzer": "[3.3.0]", - "Microsoft.CodeQuality.Analyzers": "[3.3.0]", - "Microsoft.NetCore.Analyzers": "[3.3.0]", - "Microsoft.NetFramework.Analyzers": "[3.3.0]" - } - }, - "Microsoft.EntityFrameworkCore": { - "type": "Direct", - "requested": "[3.1.9, )", - "resolved": "3.1.9", - "contentHash": "u3A2W0BvAuAF2jgW+WX+C+Sh8sMGX5Kl1hdA0gu6A/XSrZQoW/BUP4a/q2n3iitDGndaorqjAKx+Spb9gBto+w==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "1.1.1", - "Microsoft.Bcl.HashCode": "1.1.0", - "Microsoft.EntityFrameworkCore.Abstractions": "3.1.9", - "Microsoft.EntityFrameworkCore.Analyzers": "3.1.9", - "Microsoft.Extensions.Caching.Memory": "3.1.9", - "Microsoft.Extensions.DependencyInjection": "3.1.9", - "Microsoft.Extensions.Logging": "3.1.9", - "System.Collections.Immutable": "1.7.1", - "System.ComponentModel.Annotations": "4.7.0", - "System.Diagnostics.DiagnosticSource": "4.7.1" - } - }, - "Microsoft.EntityFrameworkCore.Analyzers": { - "type": "Direct", - "requested": "[3.1.9, )", - "resolved": "3.1.9", - "contentHash": "eXGyx/Lb1fiiKtnIStdxGrfBSSQg8oZytE10f1T/2xAx12W9dKB9U9fg05cwNCDC0S2CXILsmZHYaGqCSXVAqQ==" - }, - "Microsoft.EntityFrameworkCore.Sqlite": { - "type": "Direct", - "requested": "[3.1.9, )", - "resolved": "3.1.9", - "contentHash": "sMFCWv/1UcsFQZeGQcbfPbEZKZ1oKZqWZXTbc7PEZVMIXu82nbavstdNQ84x5IBXJkxl8iW3zjChb/FRBr5uLQ==", - "dependencies": { - "Microsoft.EntityFrameworkCore.Sqlite.Core": "3.1.9", - "SQLitePCLRaw.bundle_e_sqlite3": "2.0.2" - } - }, - "Microsoft.EntityFrameworkCore.Tools": { - "type": "Direct", - "requested": "[3.1.9, )", - "resolved": "3.1.9", - "contentHash": "mSgwjp0h5iqW5V49SVijR5O+kNpI1nitcbN12n9FYx/Ga6oCEFwXR/llBDesD6ASHw3Mx16vodJYJ7CEBx5rig==", - "dependencies": { - "Microsoft.EntityFrameworkCore.Design": "3.1.9" - } - }, - "NSwag.AspNetCore": { - "type": "Direct", - "requested": "[13.8.2, )", - "resolved": "13.8.2", - "contentHash": "SNGlVSZoMyywBWueZBxl3B/nfaIM0fAcuNhTD/cfMKUn3Cn/Oi8d45HZY5vAPqczvppTbk4cZXyVwWDOfgiPbA==", - "dependencies": { - "Microsoft.AspNetCore.Mvc.Core": "1.0.4", - "Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.4", - "Microsoft.AspNetCore.StaticFiles": "1.0.4", - "Microsoft.Extensions.ApiDescription.Server": "3.0.0", - "Microsoft.Extensions.FileProviders.Embedded": "1.0.1", - "NSwag.Annotations": "13.8.2", - "NSwag.Core": "13.8.2", - "NSwag.Generation": "13.8.2", - "NSwag.Generation.AspNetCore": "13.8.2", - "System.IO.FileSystem": "4.3.0", - "System.Xml.XPath.XDocument": "4.0.1" - } - }, - "SixLabors.ImageSharp": { - "type": "Direct", - "requested": "[1.0.1, )", - "resolved": "1.0.1", - "contentHash": "DjLoFNdUfsDP7RhPpr5hcUhl1XiejqBML9uDWuOUwCkc0Y+sG9IJLLbqSOi9XeoWqPviwdcDm1F8nKdF0qTYIQ==" - }, - "System.IdentityModel.Tokens.Jwt": { - "type": "Direct", - "requested": "[6.8.0, )", - "resolved": "6.8.0", - "contentHash": "5tBCjAub2Bhd5qmcd0WhR5s354e4oLYa//kOWrkX+6/7ZbDDJjMTfwLSOiZ/MMpWdE4DWPLOfTLOq/juj9CKzA==", - "dependencies": { - "Microsoft.IdentityModel.JsonWebTokens": "6.8.0", - "Microsoft.IdentityModel.Tokens": "6.8.0" - } - }, - "Microsoft.AspNetCore.Authorization": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "cN2KJkfHcKwh82c9WGx4Tqfd2h5HflU/Mu5vYLMHON8WahHU9hE32ciIXcEIoKLNpu+zs1u1cN/qxcKTdqu89w==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "1.0.2", - "Microsoft.Extensions.Options": "1.0.2", - "System.Security.Claims": "4.0.1" - } - }, - "Microsoft.AspNetCore.Hosting.Abstractions": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "ybY8FOkdNfBPB5PLv1JO+It/94ftBzGUI1WqU4XySbIWyhw2TPmmKAUuO9uvJoR0qpsFup8FJz6trsBcBITg9w==", - "dependencies": { - "Microsoft.AspNetCore.Hosting.Server.Abstractions": "1.0.4", - "Microsoft.AspNetCore.Http.Abstractions": "1.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "1.0.2", - "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.2", - "Microsoft.Extensions.FileProviders.Abstractions": "1.0.1", - "Microsoft.Extensions.Logging.Abstractions": "1.0.2" - } - }, - "Microsoft.AspNetCore.Hosting.Server.Abstractions": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "XUiQPe/CflK1i0Voo9S6/G1iQh00gQ6sMqi3LRtKeceBbO6AOostaAUdhjyME92MapI4VFNl+Z+/KXUlMAExJQ==", - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "1.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "1.0.2" - } - }, - "Microsoft.AspNetCore.Http": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "kfNOIGGgVtMzsSWZzXBqz5zsdo8ssBa90YHzZt95N8ARGXoolSaBHy6yBoMm/XcpbXM+m/x1fixTTMIWMgzJdQ==", - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "1.0.3", - "Microsoft.AspNetCore.WebUtilities": "1.0.3", - "Microsoft.Extensions.ObjectPool": "1.0.1", - "Microsoft.Extensions.Options": "1.0.2", - "Microsoft.Net.Http.Headers": "1.0.3", - "System.Buffers": "4.0.0", - "System.Threading": "4.0.11" - } - }, - "Microsoft.AspNetCore.Http.Abstractions": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "nnjvAf7ag6P0DyD/0nhRGjLpv+3DkPU0juF8aQh46X8uF4kzjJdrh65oL+4PVOu3K6BgSg6OVUs0QC0SE0FRtg==", - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "1.0.3", - "System.Globalization.Extensions": "4.0.1", - "System.Linq.Expressions": "4.1.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encodings.Web": "4.0.1" - } - }, - "Microsoft.AspNetCore.Http.Extensions": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "+7Sd+14nexIJqcB4S1Eur9kzeMZ5CBtrxkei+PNbD78fg8vO3+TcCgrl5SBNTsUB/VJAfD/s0fgs5t+hHRj2Pg==", - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "1.0.3", - "Microsoft.Extensions.FileProviders.Abstractions": "1.0.1", - "Microsoft.Net.Http.Headers": "1.0.3", - "System.Buffers": "4.0.0", - "System.IO.FileSystem": "4.0.1" - } - }, - "Microsoft.AspNetCore.Http.Features": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "Ihq57tseNyPbJTmFXY4jQ4JkxLP0lh45VRwocQci/sFx+qcJGvWB+sJJ2/YPLy/qTWFAEfNAcswuY3OsNH9Gwg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "1.0.1", - "System.Collections": "4.0.11", - "System.ComponentModel": "4.0.1", - "System.Linq": "4.1.0", - "System.Net.Primitives": "4.0.11", - "System.Net.WebSockets": "4.0.0", - "System.Runtime.Extensions": "4.1.0", - "System.Security.Claims": "4.0.1", - "System.Security.Cryptography.X509Certificates": "4.1.0", - "System.Security.Principal": "4.0.1" - } - }, - "Microsoft.AspNetCore.JsonPatch": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "WVaSVS+dDlWCR/qerHnBxU9tIeJ9GMA3M5tg4cxH7/cJYZZLnr2zvaFHGB+cRRNCKKTJ0pFRxT7ES8knhgAAaA==", - "dependencies": { - "Microsoft.CSharp": "4.0.1", - "Newtonsoft.Json": "9.0.1", - "System.Collections.Concurrent": "4.0.12", - "System.ComponentModel.TypeConverter": "4.1.0", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Serialization.Primitives": "4.1.1", - "System.Text.Encoding.Extensions": "4.0.11" - } - }, - "Microsoft.AspNetCore.Mvc.Abstractions": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "Isqgif1nuB+um86cEkpL8KnoxFCUCXBsbs9PuiuzElvlSiv4Ek3LvtrSUcbivekDDfys8CDbJhxwEI7WKJieAQ==", - "dependencies": { - "Microsoft.AspNetCore.Routing.Abstractions": "1.0.4", - "Microsoft.CSharp": "4.0.1", - "Microsoft.Net.Http.Headers": "1.0.3", - "System.ComponentModel.TypeConverter": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Text.Encoding.Extensions": "4.0.11" - } - }, - "Microsoft.AspNetCore.Mvc.ApiExplorer": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "ujCFTM42U2WKUBhdaoLoiI+wVHgYhrmDrkl5+hWJ7EJW4fhp42w4cRZ97tjuveWr+M6JZjpS0q+7PVofQzFUiw==", - "dependencies": { - "Microsoft.AspNetCore.Mvc.Core": "1.0.4" - } - }, - "Microsoft.AspNetCore.Mvc.Core": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "1ukcttN1+T82hWXE8WS5kawkruolKI6LPVqVI4rTzN16kFszS/UqTrcwSUEnmTRpmWgFo665V3c2GpdQ9B6znw==", - "dependencies": { - "Microsoft.AspNetCore.Authorization": "1.0.3", - "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.3", - "Microsoft.AspNetCore.Http": "1.0.3", - "Microsoft.AspNetCore.Mvc.Abstractions": "1.0.4", - "Microsoft.AspNetCore.Routing": "1.0.4", - "Microsoft.Extensions.DependencyModel": "1.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "1.0.1", - "Microsoft.Extensions.Logging.Abstractions": "1.0.2", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0", - "System.Buffers": "4.0.0", - "System.Diagnostics.DiagnosticSource": "4.0.0", - "System.Text.Encoding": "4.0.11" - } - }, - "Microsoft.AspNetCore.Mvc.Formatters.Json": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "i8WWK2GwlBHfOL+d+kknJWPks6DS9tbN6nfJZU4yb+/wfUAYd311B2CIHzdat3IewubnK1TYONwrhQcs2FbLeA==", - "dependencies": { - "Microsoft.AspNetCore.JsonPatch": "1.0.0", - "Microsoft.AspNetCore.Mvc.Core": "1.0.4" - } - }, - "Microsoft.AspNetCore.NodeServices": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "bbd3FlSPWiRQrIcBLa5TaOvo4gjmmiNMkxA8VmZ6u0eIpS0Yj35/eTopaGdtzqwlqj5jXbdRoib1MruXuPaW8A==", - "dependencies": { - "Microsoft.Extensions.Logging.Console": "3.1.9", - "Newtonsoft.Json": "12.0.2" - } - }, - "Microsoft.AspNetCore.Routing": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "mdIF3ckRothHWuCSFkk6YXACj5zxi5qM+cEAHjcpP04/wCHUoV0gGVnW+HI+LyFXE6JUwu2zXn5tfsCpW0U+SA==", - "dependencies": { - "Microsoft.AspNetCore.Http.Extensions": "1.0.3", - "Microsoft.AspNetCore.Routing.Abstractions": "1.0.4", - "Microsoft.Extensions.Logging.Abstractions": "1.0.2", - "Microsoft.Extensions.ObjectPool": "1.0.1", - "Microsoft.Extensions.Options": "1.0.2", - "System.Collections": "4.0.11", - "System.Text.RegularExpressions": "4.1.0" - } - }, - "Microsoft.AspNetCore.Routing.Abstractions": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "GHxVt6LlXHFsCUd2Un+/vY1tBTXxnogfbDO0b8G5EGmkapSK+dOGOLJviscxQkp338Uabs081JEIdkRymI5GXA==", - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "1.0.3", - "System.Collections.Concurrent": "4.0.12", - "System.Reflection.Extensions": "4.0.1", - "System.Threading.Tasks": "4.0.11" - } - }, - "Microsoft.AspNetCore.SpaServices": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "Fb+N2ZyF1wNrGeWggT+Ovv6W8AAVxfi4V/SnuEsBOR+nmkFhty9zyh6IDRRS98GJK6OE3adqqPbWMtJqbxYnNA==", - "dependencies": { - "Microsoft.AspNetCore.NodeServices": "3.1.9" - } - }, - "Microsoft.AspNetCore.StaticFiles": { - "type": "Transitive", - "resolved": "1.0.4", - "contentHash": "2pNvwewAazhaaCdw2CGUvIcDrNQMlqP57JgBDf3v+pRj1rZ29HVnpvkX6a+TrmRYlJNmmxHOKEt468uE/gDcFw==", - "dependencies": { - "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.4", - "Microsoft.AspNetCore.Http.Extensions": "1.0.3", - "Microsoft.Extensions.FileProviders.Abstractions": "1.0.1", - "Microsoft.Extensions.Logging.Abstractions": "1.0.2", - "Microsoft.Extensions.WebEncoders": "1.0.3" - } - }, - "Microsoft.AspNetCore.WebUtilities": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "snSGNs5EEisqivDjDiskFkFyu+DV2Ib9sMPOBQKtoFwI5H1W5YNB/rIVqDZQL16zj/uzdwwxrdE/5xhkVyf6gQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "1.0.1", - "System.Buffers": "4.0.0", - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Text.Encodings.Web": "4.0.1" - } - }, - "Microsoft.Bcl.AsyncInterfaces": { - "type": "Transitive", - "resolved": "1.1.1", - "contentHash": "yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==" - }, - "Microsoft.Bcl.HashCode": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "J2G1k+u5unBV+aYcwxo94ip16Rkp65pgWFb0R6zwJipzWNMgvqlWeuI7/+R+e8bob66LnSG+llLJ+z8wI94cHg==" - }, - "Microsoft.CodeAnalysis.VersionCheckAnalyzer": { - "type": "Transitive", - "resolved": "3.3.0", - "contentHash": "xjLM3DRFZMan3nQyBQEM1mBw6VqQybi4iMJhMFW6Ic1E1GCvqJR3ABOwEL7WtQjDUzxyrGld9bASnAos7G/Xyg==" - }, - "Microsoft.CodeQuality.Analyzers": { - "type": "Transitive", - "resolved": "3.3.0", - "contentHash": "zZ3miq6u22UFQKhfJyLnVEJ+DgeOopLh3eKJnKAcOetPP2hiv3wa7kHZlBDeTvtqJQiAQhAVbttket8XxjN1zw==" - }, - "Microsoft.CSharp": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" - }, - "Microsoft.Data.Sqlite.Core": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "+u4PeT1npi2EzhxGc5r1Z2z73zuXw+TlKVZm44WQhNCUw4LtUVDaxGSpUhrjW+X4snBCBfr4kT/uJyKnL4R4og==", - "dependencies": { - "SQLitePCLRaw.core": "2.0.2" - } - }, - "Microsoft.DotNet.PlatformAbstractions": { - "type": "Transitive", - "resolved": "3.1.6", - "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" - }, - "Microsoft.EntityFrameworkCore.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "IR6Y4RJVlw0QXdWXjF3Kx9s1QLiicJus+BFBKr43lBtriV20j3yrWMoaZ9W1AUUgnicZXpXVcNfklqtmwb9Sxw==" - }, - "Microsoft.EntityFrameworkCore.Design": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "2zgP7BWcw5nqGQiT4bEtiI6ras+4pvKg5D+tA3AYvjEifzzaWvmRTb3B9nRHpIYJAhPtmWNBVnVXLbu3fS1OYA==", - "dependencies": { - "Microsoft.CSharp": "4.7.0", - "Microsoft.EntityFrameworkCore.Relational": "3.1.9" - } - }, - "Microsoft.EntityFrameworkCore.Relational": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "7fhWuSfrCYlv/hvOX5OhbFJF/G9f8sifqTrJiYnAYLDOvNizwv7t9tFPD8JwaF3zM2S54O5/Vni2NxvwzSaW2w==", - "dependencies": { - "Microsoft.EntityFrameworkCore": "3.1.9" - } - }, - "Microsoft.EntityFrameworkCore.Sqlite.Core": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "Da6h8LdpJwKc1az9DMWt2Mt6gHXPRZqwiumV1Zx0AuM3EThyokVDzBGy2sti0AcBhcQMLJHPEr5R9xuiWvaYYQ==", - "dependencies": { - "Microsoft.Data.Sqlite.Core": "3.1.9", - "Microsoft.DotNet.PlatformAbstractions": "3.1.6", - "Microsoft.EntityFrameworkCore.Relational": "3.1.9", - "Microsoft.Extensions.DependencyModel": "3.1.6" - } - }, - "Microsoft.Extensions.ApiDescription.Server": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "LH4OE/76F6sOCslif7+Xh3fS/wUUrE5ryeXAMcoCnuwOQGT5Smw0p57IgDh/pHgHaGz/e+AmEQb7pRgb++wt0w==" - }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "/2QsPAsUZD4qvftZkUKHRRRryPDXWh606/iNXPLrulwHLMr9JNsKBJWVqylT3qU92nJok5VoqSblkY9mSyxFyg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.9" - } - }, - "Microsoft.Extensions.Caching.Memory": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "/JrVMVetX/kpJQUIlJ6NLQ3zbF0yyryXpo4+uFCqYIUZzgmWk8DS/zSKcyj1tQ3410+vhDEAPngxC+hg0IlJeg==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "3.1.9", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.9", - "Microsoft.Extensions.Logging.Abstractions": "3.1.9", - "Microsoft.Extensions.Options": "3.1.9" - } - }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "lqdkOGNeTMKG981Q7yWGlRiFbIlsRwTlMMiybT+WOzUCFBS/wc25tZgh7Wm/uRoBbWefgvokzmnea7ZjmFedmA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.9" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "vOJxPKczaHpXeZFrxARxYwsEulhEouXc5aZGgMdkhV/iEXX9/pfjqKk76rTG+4CsJjHV+G/4eMhvOIaQMHENNA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.9" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "BG6HcT7tARYakftqfQu+cLksgIWG1NdxMY+igI12hdZrUK+WjS973NiRyuao/U9yyTeM9NPwRnC61hCmG3G3jg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "3.1.9" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "ORqfrAACcvTInie1oGola5uky344/PiNfgayTPuZWV4WnSfIQZJQm/ZLpGshJE3h7TqwYaYElGazK/yaM2bFLA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.9" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "8PkcaPwiTPOhqshoY4+rQUbz86X6YpLDLUqXOezh7L2A3pgpBmeBBByYIffofBlvQxDdQ0zB2DkWjbZWyCxRWg==" - }, - "Microsoft.Extensions.DependencyModel": { - "type": "Transitive", - "resolved": "3.1.6", - "contentHash": "/UlDKULIVkLQYn1BaHcy/rc91ApDxJb7T75HcCbGdqwvxhnRQRKM2di1E70iCPMF9zsr6f4EgQTotBGxFIfXmw==", - "dependencies": { - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "Q4SGwEFZKiZbpzPgdGbQUULxtcH1zXMOwCPKSm6QwVcOCGshf3QLfBh+O/GyFH4B0RfZ16nKyeW1mMONlRyjUw==", - "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.9" - } - }, - "Microsoft.Extensions.FileProviders.Embedded": { - "type": "Transitive", - "resolved": "1.0.1", - "contentHash": "nSEa8bH3fVdTYGqK4twOKLxxgKIW3cz9g9mrzhPh/CmdvGJWKRTIlBIZi7lz+lqNQpxean5vbAo84R/mU+JpGA==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "1.0.1", - "System.Runtime.Extensions": "4.1.0" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "HWDSsblTCQp7EEJJmnLzttIhFGzDu+DGqBbOvGCdFT0+pkCuBkn3EiWpEEcm5WMTO5njmsbLSK9ZuUUf2zPsFg==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "3.1.9", - "Microsoft.Extensions.FileSystemGlobbing": "3.1.9" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "5bnewG1aBiSESPNwcXGIxDDRN95uqdy+fqZZ8Z63Et5rRNlAwAfXHOrg+FTht7UjHobjvtjzquMCbAWhWEPHIw==" - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "+V3i0jCQCO6IIOf6e+fL0SqrZd2x/Krug9EEL1JHa9R03RsbEpltCtjVY5hxedyuyuQKwvLoR12sCfu/9XEUAw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "3.1.9", - "Microsoft.Extensions.DependencyInjection": "3.1.9", - "Microsoft.Extensions.Logging.Abstractions": "3.1.9", - "Microsoft.Extensions.Options": "3.1.9" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "W5fbF8qVR9SMVVJqDQLIR7meWbev6Pu/lbrm7LDNr4Sp7HOotr4k2UULTdFSXOi5aoDdkQZpWnq0ZSpjrR3tjg==" - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "hv6XsGgikrbkolBJdF1usl9R/nrliC5mifMqHMEY9zWcCLwNkXMJiS8p0lbosrnpVAMi4PbNx39DB51Dqscd0w==", - "dependencies": { - "Microsoft.Extensions.Logging": "3.1.9", - "Microsoft.Extensions.Options.ConfigurationExtensions": "3.1.9" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "8Dusl1rkDivmvLrwj6QAo917xMHPiDBzG3IG3agiyDdtsC/fRp+1VN5iIN+O09PtEaMged2OLA6wCDwfSTSTZw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.9", - "Microsoft.Extensions.Logging": "3.1.9", - "Microsoft.Extensions.Logging.Configuration": "3.1.9" - } - }, - "Microsoft.Extensions.ObjectPool": { - "type": "Transitive", - "resolved": "1.0.1", - "contentHash": "pJMOnxuqmG37OjccfvtqVoo3bQGoN+0EJUzzp7+2uxSdioER82caAk6Yi/z5aysapn5XENNIIa7SaYnYKSS69A==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "EIb3G1DL+Rl9MvJR7LjI1wCy2nfTN4y8MflbOftn1HLYQBj/Rwl8kUbGTrSFE01c99Wm4ETjWVsjqKcpFvhPng==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.9", - "Microsoft.Extensions.Primitives": "3.1.9" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "u5jh7RW+Ev81YqK1ZoBG0lftp2MA9xqXiTiRL46XzaPj2ScNUyiVbzcVY0fPbE27UOpT2hj+yPzRSOMIIo55UA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.9", - "Microsoft.Extensions.Configuration.Binder": "3.1.9", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.9", - "Microsoft.Extensions.Options": "3.1.9" - } - }, - "Microsoft.Extensions.PlatformAbstractions": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "zyjUzrOmuevOAJpIo3Mt5GmpALVYCVdLZ99keMbmCxxgQH7oxzU58kGHzE6hAgYEiWsdfMJLjVR7r+vSmaJmtg==", - "dependencies": { - "System.AppContext": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "3.1.9", - "contentHash": "IrHecH0eGG7/XoeEtv++oLg/sJHRNyeCqlA9RhAo6ig4GpOTjtDr32sBMYuuLtUq8ALahneWkrOzoBAwJ4L4iA==" - }, - "Microsoft.Extensions.WebEncoders": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "TClNvczWRxF6bVPhn5EK3Y3QNi5jTP68Qur+5Fk+MQLPeBI18WN7X145DDJ6bFeNOwgdCHl73lHs5uZp9ish1A==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.2", - "Microsoft.Extensions.Options": "1.0.2", - "System.Text.Encodings.Web": "4.0.1" - } - }, - "Microsoft.IdentityModel.JsonWebTokens": { - "type": "Transitive", - "resolved": "6.8.0", - "contentHash": "+7JIww64PkMt7NWFxoe4Y/joeF7TAtA/fQ0b2GFGcagzB59sKkTt/sMZWR6aSZht5YC7SdHi3W6yM1yylRGJCQ==", - "dependencies": { - "Microsoft.IdentityModel.Tokens": "6.8.0" - } - }, - "Microsoft.IdentityModel.Logging": { - "type": "Transitive", - "resolved": "6.8.0", - "contentHash": "Rfh/p4MaN4gkmhPxwbu8IjrmoDncGfHHPh1sTnc0AcM/Oc39/fzC9doKNWvUAjzFb8LqA6lgZyblTrIsX/wDXg==" - }, - "Microsoft.IdentityModel.Tokens": { - "type": "Transitive", - "resolved": "6.8.0", - "contentHash": "gTqzsGcmD13HgtNePPcuVHZ/NXWmyV+InJgalW/FhWpII1D7V1k0obIseGlWMeA4G+tZfeGMfXr0klnWbMR/mQ==", - "dependencies": { - "Microsoft.CSharp": "4.5.0", - "Microsoft.IdentityModel.Logging": "6.8.0", - "System.Security.Cryptography.Cng": "4.5.0" - } - }, - "Microsoft.Net.Http.Headers": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "2F8USh4hR5xppvaxtw2EStX74Ih+HhRj7aQD1uaB9JmTGy478F7t4VU+IdZXauEDrvS7LYAyyhmOExsUFK3PAw==", - "dependencies": { - "System.Buffers": "4.0.0", - "System.Collections": "4.0.11", - "System.Diagnostics.Contracts": "4.0.1", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0", - "System.Text.Encoding": "4.0.11" - } - }, - "Microsoft.NetCore.Analyzers": { - "type": "Transitive", - "resolved": "3.3.0", - "contentHash": "6qptTHUu1Wfszuf83NhU0IoAb4j7YWOpJs6oc6S4G/nI6aGGWKH/Xi5Vs9L/8lrI74ijEEzPcIwafSQW5ASHtA==" - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - }, - "Microsoft.NETCore.Targets": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" - }, - "Microsoft.NetFramework.Analyzers": { - "type": "Transitive", - "resolved": "3.3.0", - "contentHash": "JTfMic5fEFWICePbr7GXOGPranqS9Qxu2U/BZEcnnGbK1SFW8TxRyGp6O1L52xsbfOdqmzjc0t5ubhDrjj+Xpg==" - }, - "Microsoft.Win32.Primitives": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "fQnBHO9DgcmkC9dYSJoBqo6sH1VJwJprUHh8F3hbcRlxiQiBUuTntdk8tUwV490OqC2kQUrinGwZyQHTieuXRA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "Namotion.Reflection": { - "type": "Transitive", - "resolved": "1.0.14", - "contentHash": "wuJGiFvGfehH2w7jAhMbCJt0/rvUuHyqSZn0sMhNTviDfBZRyX8LFlR/ndQcofkGWulPDfH5nKYTeGXE8xBHPA==", - "dependencies": { - "Microsoft.CSharp": "4.3.0" - } - }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "12.0.2", - "contentHash": "rTK0s2EKlfHsQsH6Yx2smvcTCeyoDNgCW7FEYyV01drPlh2T243PR2DiDXqtC5N4GDm4Ma/lkxfW5a/4793vbA==" - }, - "NJsonSchema": { - "type": "Transitive", - "resolved": "10.2.1", - "contentHash": "/BtWbYTusyoSgQkCB4eYijMfZotB/rfASDsl1k9evlkm5vlOP4s4Y09TOzBChU77d/qUABVYL1Xf+TB8E0Wfpw==", - "dependencies": { - "Namotion.Reflection": "1.0.14", - "Newtonsoft.Json": "9.0.1" - } - }, - "NSwag.Annotations": { - "type": "Transitive", - "resolved": "13.8.2", - "contentHash": "/GO+35CjPYQTPS5/Q8udM5JAMEWVo8JsrkV2Uw3OW4/AJU9iOS7t6WJid6ZlkpLMjnW7oex9mvJ2EZNE4eOG/Q==" - }, - "NSwag.Core": { - "type": "Transitive", - "resolved": "13.8.2", - "contentHash": "Hm6pU9qFJuXLo3b27+JTXztfeuI/15Ob1sDsfUu4rchN0+bMogtn8Lia8KVbcalw/M+hXc0rWTFp5ueP23e+iA==", - "dependencies": { - "NJsonSchema": "10.2.1", - "Newtonsoft.Json": "9.0.1" - } - }, - "NSwag.Generation": { - "type": "Transitive", - "resolved": "13.8.2", - "contentHash": "LBIrpHFRZeMMbqL1hdyGb7r8v+T52aOCARxwfAmzE+MlOHVpjsIxyNSXht9EzBFMbSH0tj7CK2Ugo7bm+zUssg==", - "dependencies": { - "NJsonSchema": "10.2.1", - "NSwag.Core": "13.8.2", - "Newtonsoft.Json": "9.0.1" - } - }, - "NSwag.Generation.AspNetCore": { - "type": "Transitive", - "resolved": "13.8.2", - "contentHash": "0ydVv6OidspZ/MS6qmU8hswGtXwq5YZPg+2a2PHGD6jNp2Fef4j1wC3xa3hplDAq7cK+BgpyDKtvj9+X01+P5g==", - "dependencies": { - "Microsoft.AspNetCore.Mvc.ApiExplorer": "1.0.4", - "Microsoft.AspNetCore.Mvc.Core": "1.0.4", - "Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.4", - "NJsonSchema": "10.2.1", - "NSwag.Generation": "13.8.2" - } - }, - "runtime.native.System": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1" - } - }, - "runtime.native.System.Net.Http": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1" - } - }, - "runtime.native.System.Security.Cryptography": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1" - } - }, - "SQLitePCLRaw.bundle_e_sqlite3": { - "type": "Transitive", - "resolved": "2.0.2", - "contentHash": "OVPI/nh5AqfLCIKhAYqjCa6AHhc7oKApGcGM3UhMRSerFiBx58nSpGwxVFdMgjOCWZR+fA49nzsnKlWp5hFo8w==", - "dependencies": { - "SQLitePCLRaw.core": "2.0.2", - "SQLitePCLRaw.lib.e_sqlite3": "2.0.2", - "SQLitePCLRaw.provider.dynamic_cdecl": "2.0.2" - } - }, - "SQLitePCLRaw.core": { - "type": "Transitive", - "resolved": "2.0.2", - "contentHash": "TFSBX426OelS1tkaVC254NVVlrJIe9YLhWPkEvuqJj2104QpmDmEYOhfdfDJD1E/2SmqDhoRw1ek5cQHj8olcQ==", - "dependencies": { - "System.Memory": "4.5.3" - } - }, - "SQLitePCLRaw.lib.e_sqlite3": { - "type": "Transitive", - "resolved": "2.0.2", - "contentHash": "S+Tsqe/M7wsc+9HeediI6UHtBKf2X586aRwhi1aBVLGe0WxkAo52O9ZxwEy/v8XMLefcrEMupd2e9CDlIT6QCw==" - }, - "SQLitePCLRaw.provider.dynamic_cdecl": { - "type": "Transitive", - "resolved": "2.0.2", - "contentHash": "ZSwacbKJUsxJEZxwT23uZVrGbaIvXcADZDz5Sr66fikO5eehdcceDncjzwzTzWfW13di8gpTpstx3WJSt/Ci5Q==", - "dependencies": { - "SQLitePCLRaw.core": "2.0.2" - } - }, - "System.AppContext": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", - "dependencies": { - "System.Runtime": "4.1.0" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "msXumHfjjURSkvxUjYuq4N2ghHoRi2VpXcKMA7gK6ujQfU3vGpl+B6ld0ATRg+FZFpRyA6PgEPA+VlIkTeNf2w==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Diagnostics.Tracing": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Collections.Concurrent": { - "type": "Transitive", - "resolved": "4.0.12", - "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Diagnostics.Tracing": "4.1.0", - "System.Globalization": "4.0.11", - "System.Reflection": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Threading.Tasks": "4.0.11" - } - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "1.7.1", - "contentHash": "B43Zsz5EfMwyEbnObwRxW5u85fzJma3lrDeGcSAV1qkhSRTNY5uXAByTn9h9ddNdhM+4/YoLc/CI43umjwIl9Q==" - }, - "System.Collections.NonGeneric": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Collections.Specialized": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==", - "dependencies": { - "System.Collections.NonGeneric": "4.0.1", - "System.Globalization": "4.0.11", - "System.Globalization.Extensions": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.ComponentModel": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "oBZFnm7seFiVfugsIyOvQCWobNZs7FzqDV/B7tx20Ep/l3UUFCPDkdTnCNaJZTU27zjeODmy2C/cP60u3D4c9w==", - "dependencies": { - "System.Runtime": "4.1.0" - } - }, - "System.ComponentModel.Annotations": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "0YFqjhp/mYkDGpU0Ye1GjE53HMp9UVfGN7seGpAMttAC0C40v5gw598jCgpbBLMmCo0E5YRLBv5Z2doypO49ZQ==" - }, - "System.ComponentModel.Primitives": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "sc/7eVCdxPrp3ljpgTKVaQGUXiW05phNWvtv/m2kocXqrUQvTVWKou1Edas2aDjTThLPZOxPYIGNb/HN0QjURg==", - "dependencies": { - "System.ComponentModel": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.ComponentModel.TypeConverter": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "MnDAlaeJZy9pdB5ZdOlwdxfpI+LJQ6e0hmH7d2+y2LkiD8DRJynyDYl4Xxf3fWFm7SbEwBZh4elcfzONQLOoQw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Collections.NonGeneric": "4.0.1", - "System.Collections.Specialized": "4.0.1", - "System.ComponentModel": "4.0.1", - "System.ComponentModel.Primitives": "4.1.0", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Diagnostics.Contracts": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "HvQQjy712vnlpPxaloZYkuE78Gn353L0SJLJVeLcNASeg9c4qla2a1Xq8I7B3jZoDzKPtHTkyVO7AZ5tpeQGuA==", - "dependencies": { - "System.Runtime": "4.1.0" - } - }, - "System.Diagnostics.Debug": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" - }, - "System.Diagnostics.Tools": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Diagnostics.Tracing": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Globalization.Calendars": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Globalization": "4.0.11", - "System.Runtime": "4.1.0" - } - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "KKo23iKeOaIg61SSXwjANN7QYDr/3op3OWGGzDzz7mypx0Za0fZSeG0l6cco8Ntp8YMYkIQcAqlk8yhm5/Uhcg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0" - } - }, - "System.Linq.Expressions": { - "type": "Transitive", - "resolved": "4.1.1", - "contentHash": "bXwi8FrK/XIGPvtk1ZnawffhqLPyacj7dZnbFaV52YGaQigNqGEzNAByAIvL9FlEe3TCzoInorHF91IK//Q3Xg==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Linq": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Emit.Lightweight": "4.0.1", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" - }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "hVvfl4405DRjA2408luZekbPhplJK03j2Y2lSfMlny7GHXlkByw1iLnc9mgKW0GdQn73vvMcWrWewAhylXA4Nw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Handles": "4.0.1" - } - }, - "System.Net.WebSockets": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "2KJo8hir6Edi9jnMDAMhiJoI691xRBmKcbNpwjrvpIMOCTYOtBpSsSEGBxBDV7PKbasJNaFp1+PZz1D7xS41Hg==", - "dependencies": { - "Microsoft.Win32.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Threading.Tasks": "4.0.11" - } - }, - "System.ObjectModel": { - "type": "Transitive", - "resolved": "4.0.12", - "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.IO": "4.1.0", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Reflection.Emit": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==" - }, - "System.Reflection.Emit.ILGeneration": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", - "dependencies": { - "System.Reflection": "4.1.0", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Reflection.Emit.Lightweight": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", - "dependencies": { - "System.Reflection": "4.1.0", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Reflection.Extensions": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Reflection": "4.1.0", - "System.Runtime": "4.1.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Reflection.TypeExtensions": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", - "dependencies": { - "System.Reflection": "4.1.0", - "System.Runtime": "4.1.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Globalization": "4.0.11", - "System.Reflection": "4.1.0", - "System.Runtime": "4.1.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Reflection": "4.1.0", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Handles": "4.0.1" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", - "dependencies": { - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0" - } - }, - "System.Runtime.Serialization.Primitives": { - "type": "Transitive", - "resolved": "4.1.1", - "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", - "dependencies": { - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0" - } - }, - "System.Security.Claims": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "4Jlp0OgJLS/Voj1kyFP6MJlIYp3crgfH8kNQk2p7+4JYfc1aAmh9PZyAMMbDhuoolGNtux9HqSOazsioRiDvCw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Security.Principal": "4.0.1" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.2.0", - "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Handles": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.Numerics": "4.0.1", - "System.Security.Cryptography.Encoding": "4.0.0", - "System.Security.Cryptography.Primitives": "4.0.0", - "System.Text.Encoding": "4.0.11", - "runtime.native.System.Security.Cryptography": "4.0.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "WG3r7EyjUe9CMPFSs6bty5doUqT+q9pbI80hlNzo2SkPkZ4VTuZkGWjpp77JB8+uaL4DFPRdBsAY+DX3dBK92A==" - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "System.IO": "4.1.0", - "System.Reflection": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Handles": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Security.Cryptography.Algorithms": "4.2.0", - "System.Security.Cryptography.Encoding": "4.0.0", - "System.Security.Cryptography.Primitives": "4.0.0", - "System.Text.Encoding": "4.0.11", - "System.Threading": "4.0.11" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "System.Collections": "4.0.11", - "System.Collections.Concurrent": "4.0.12", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Handles": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Security.Cryptography.Primitives": "4.0.0", - "System.Text.Encoding": "4.0.11", - "runtime.native.System.Security.Cryptography": "4.0.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Handles": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.Numerics": "4.0.1", - "System.Security.Cryptography.Algorithms": "4.2.0", - "System.Security.Cryptography.Encoding": "4.0.0", - "System.Security.Cryptography.Primitives": "4.0.0", - "System.Text.Encoding": "4.0.11", - "runtime.native.System.Security.Cryptography": "4.0.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Threading": "4.0.11", - "System.Threading.Tasks": "4.0.11" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Globalization.Calendars": "4.0.1", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.IO.FileSystem.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.Handles": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.Numerics": "4.0.1", - "System.Security.Cryptography.Algorithms": "4.2.0", - "System.Security.Cryptography.Cng": "4.2.0", - "System.Security.Cryptography.Csp": "4.0.0", - "System.Security.Cryptography.Encoding": "4.0.0", - "System.Security.Cryptography.OpenSsl": "4.0.0", - "System.Security.Cryptography.Primitives": "4.0.0", - "System.Text.Encoding": "4.0.11", - "System.Threading": "4.0.11", - "runtime.native.System": "4.0.0", - "runtime.native.System.Net.Http": "4.0.1", - "runtime.native.System.Security.Cryptography": "4.0.0" - } - }, - "System.Security.Principal": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "On+SKhXY5rzxh/S8wlH1Rm0ogBlu7zyHNxeNBiXauNrhHRXAe9EuX8Yl5IOzLPGU5Z4kLWHMvORDOCG8iu9hww==", - "dependencies": { - "System.Runtime": "4.1.0" - } - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1", - "Microsoft.NETCore.Targets": "1.0.1", - "System.Runtime": "4.1.0", - "System.Text.Encoding": "4.0.11" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "GgJDO6/1bW6kkttxIiPK2jsqllQ3ifaeeBAJJrcoJq0lAclIZsAZZdEqi6JHq+QLZXL2UsjyWb8K8EOH7nOSPw==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.IO": "4.1.0", - "System.Reflection": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "4.7.2", - "contentHash": "TcMd95wcrubm9nHvJEQs70rC0H/8omiSGGpU4FQ/ZA1URIqD4pjmFJh2Mfv1yH1eHgJDWTi2hMDXwTET+zOOyg==" - }, - "System.Text.RegularExpressions": { - "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", - "dependencies": { - "System.Runtime": "4.1.0", - "System.Threading.Tasks": "4.0.11" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.0.0", - "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Runtime": "4.1.0", - "System.Threading.Tasks": "4.0.11" - } - }, - "System.Xml.ReaderWriter": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.IO.FileSystem.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding": "4.0.11", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Text.RegularExpressions": "4.1.0", - "System.Threading.Tasks": "4.0.11", - "System.Threading.Tasks.Extensions": "4.0.0" - } - }, - "System.Xml.XDocument": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Diagnostics.Tools": "4.0.1", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Reflection": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Text.Encoding": "4.0.11", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11" - } - }, - "System.Xml.XPath": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11" - } - }, - "System.Xml.XPath.XDocument": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", - "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11", - "System.Xml.XDocument": "4.0.11", - "System.Xml.XPath": "4.0.1" - } - }, - "timeline.errorcodes": { - "type": "Project" - } - } - } -} \ No newline at end of file -- cgit v1.2.3