From 9719a0c0fd077d640cd52f39f3d00b39ba847055 Mon Sep 17 00:00:00 2001 From: ThePra Date: Sat, 18 Feb 2023 08:52:17 +0100 Subject: [PATCH] Init --- .../AtLeastOnePropertyAttribute.cs | 38 + SocialPub.ClientModels/Constants.cs | 11 + SocialPub.ClientModels/Data/ViewLanguage.cs | 8 + SocialPub.ClientModels/LoginForm.cs | 38 + SocialPub.ClientModels/NewPasswordForm.cs | 29 + .../PasswordRecoveryForm.cs | 25 + SocialPub.ClientModels/Policies.cs | 9 + .../Resources/ErrorsResource.Designer.cs | 198 ++ .../Resources/ErrorsResource.bg.resx | 165 ++ .../Resources/ErrorsResource.cs.resx | 165 ++ .../Resources/ErrorsResource.da.resx | 165 ++ .../Resources/ErrorsResource.de.resx | 165 ++ .../Resources/ErrorsResource.el.resx | 165 ++ .../Resources/ErrorsResource.es.resx | 165 ++ .../Resources/ErrorsResource.et.resx | 165 ++ .../Resources/ErrorsResource.fi.resx | 165 ++ .../Resources/ErrorsResource.fr.resx | 165 ++ .../Resources/ErrorsResource.hu.resx | 165 ++ .../Resources/ErrorsResource.it.resx | 165 ++ .../Resources/ErrorsResource.ja.resx | 165 ++ .../Resources/ErrorsResource.lt.resx | 165 ++ .../Resources/ErrorsResource.lv.resx | 165 ++ .../Resources/ErrorsResource.nl.resx | 165 ++ .../Resources/ErrorsResource.pl.resx | 165 ++ .../Resources/ErrorsResource.pt.resx | 165 ++ .../Resources/ErrorsResource.resx | 165 ++ .../Resources/ErrorsResource.ro.resx | 165 ++ .../Resources/ErrorsResource.ru.resx | 165 ++ .../Resources/ErrorsResource.sk.resx | 165 ++ .../Resources/ErrorsResource.sl.resx | 165 ++ .../Resources/ErrorsResource.sv.resx | 165 ++ .../Resources/ErrorsResource.zh.resx | 165 ++ .../Resources/FieldsNameResource.Designer.cs | 657 +++++ .../Resources/FieldsNameResource.bg.resx | 318 +++ .../Resources/FieldsNameResource.cs.resx | 318 +++ .../Resources/FieldsNameResource.da.resx | 318 +++ .../Resources/FieldsNameResource.de.resx | 318 +++ .../Resources/FieldsNameResource.el.resx | 318 +++ .../Resources/FieldsNameResource.es.resx | 318 +++ .../Resources/FieldsNameResource.et.resx | 318 +++ .../Resources/FieldsNameResource.fi.resx | 318 +++ .../Resources/FieldsNameResource.fr.resx | 318 +++ .../Resources/FieldsNameResource.hu.resx | 318 +++ .../Resources/FieldsNameResource.it.resx | 318 +++ .../Resources/FieldsNameResource.ja.resx | 318 +++ .../Resources/FieldsNameResource.lt.resx | 318 +++ .../Resources/FieldsNameResource.lv.resx | 318 +++ .../Resources/FieldsNameResource.nl.resx | 318 +++ .../Resources/FieldsNameResource.pl.resx | 318 +++ .../Resources/FieldsNameResource.pt.resx | 318 +++ .../Resources/FieldsNameResource.resx | 318 +++ .../Resources/FieldsNameResource.ro.resx | 318 +++ .../Resources/FieldsNameResource.ru.resx | 318 +++ .../Resources/FieldsNameResource.sk.resx | 318 +++ .../Resources/FieldsNameResource.sl.resx | 318 +++ .../Resources/FieldsNameResource.sv.resx | 318 +++ .../Resources/FieldsNameResource.zh.resx | 318 +++ .../SocialPub.ClientModels.csproj | 33 + .../User/Avatar/InsertAvatarForm.cs | 14 + .../User/Avatar/UpdateAvatarForm.cs | 14 + .../User/Avatar/UpdateAvatarSettings.cs | 14 + .../User/Avatar/ViewAvatar.cs | 16 + .../User/Avatar/ViewAvatarSettings.cs | 12 + SocialPub.ClientModels/User/JwtUser.cs | 13 + SocialPub.ClientModels/User/UserForm.cs | 40 + SocialPub.ClientModels/User/UsersIds.cs | 7 + .../User/ViewUserSettings.cs | 25 + .../NoWhiteSpacesAttribute.cs | 60 + SocialPub.ClientModels/WebResult.cs | 59 + SocialPub.sln | 31 + .../ClientToServer/AdminController.cs | 129 + .../ClientToServer/DataController.cs | 55 + .../ClientToServer/ModeratorController.cs | 10 + .../ClientToServer/UserController.cs | 404 +++ .../ServerToServer/PeasantsController.cs | 111 + .../ServerToServer/UsersController.cs | 69 + SocialPub/Data/InitDb.cs | 213 ++ SocialPub/Data/LanguagesRow.cs | 9 + SocialPub/Data/languagesNative.json | 2210 +++++++++++++++++ SocialPub/Extensions/AddAuthExtension.cs | 33 + SocialPub/Extensions/Extensions.cs | 93 + .../OperationCancelledExceptionFilter.cs | 23 + SocialPub/Extensions/StringExtensions.cs | 36 + .../Middleware/SocialPubConfigurations.cs | 223 ++ .../Models/ActivityPub/ActivityPubActivity.cs | 44 + .../Models/ActivityPub/ActivityPubActor.cs | 79 + .../ActivityPub/ActivityPubCollection.cs | 24 + .../ActivityPub/ActivityPubCollectionPage.cs | 18 + .../Models/ActivityPub/ActivityPubLink.cs | 28 + .../Models/ActivityPub/ActivityPubObject.cs | 126 + .../ActivityPubOrderedCollection.cs | 17 + .../ActivityPubOrderedCollectionPage.cs | 27 + .../ActivityPub/ActivityPubTombstone.cs | 14 + .../ActivityPub/Extra/ActivityPubAudience.cs | 13 + .../ActivityPub/Extra/ActivityPubIcon.cs | 19 + .../Extra/ActivityPubInstrument.cs | 13 + .../ActivityPub/Extra/ActivityPubOrigin.cs | 13 + .../ActivityPub/Extra/ActivityPubResult.cs | 13 + SocialPub/Models/ActivityPub/MacroType.cs | 17 + SocialPub/Models/ActivityPub/ObjectType.cs | 63 + SocialPub/Models/AppConfiguration.cs | 21 + SocialPub/Models/Data/Language.cs | 11 + SocialPub/Models/Email/EmailConfiguration.cs | 11 + SocialPub/Models/MongoSettings.cs | 9 + SocialPub/Models/Post/DmPost.cs | 22 + SocialPub/Models/Post/Post.cs | 23 + SocialPub/Models/Post/PostBoost.cs | 12 + SocialPub/Models/Post/PostMedia.cs | 12 + SocialPub/Models/User/Avatar.cs | 102 + SocialPub/Models/User/EmailRecovery.cs | 12 + SocialPub/Models/User/RootToAvatar.cs | 10 + SocialPub/Models/User/RootUser.cs | 48 + SocialPub/Models/User/RootUserNote.cs | 14 + SocialPub/Models/User/UserPolicyType.cs | 9 + SocialPub/Models/socialpub.cs | 24 + SocialPub/Program.cs | 142 ++ SocialPub/Properties/launchSettings.json | 41 + SocialPub/Resources/GenericRes.Designer.cs | 81 + SocialPub/Resources/GenericRes.resx | 126 + SocialPub/Services/ActivityPubClient.cs | 12 + SocialPub/Services/AppConfigurationService.cs | 36 + .../Private/IPrivateAvatarUsersService.cs | 13 + .../ClientToServer/Public/DataService.cs | 37 + .../ClientToServer/Public/IDataService.cs | 13 + .../Public/IPublicAvatarUsersService.cs | 54 + SocialPub/Services/IRootUsersService.cs | 34 + SocialPub/Services/JwtEvents.cs | 49 + .../RequestLocalizationOptionsService.cs | 42 + SocialPub/Services/RootUsersService.cs | 577 +++++ SocialPub/SocialPub.csproj | 56 + SocialPub/StaticServices/AuthTokenManager.cs | 55 + SocialPub/StaticServices/DbEntities.cs | 28 + SocialPub/StaticServices/IPasswordHasher.cs | 9 + SocialPub/StaticServices/PasswordHasher.cs | 54 + SocialPub/appsettings.Development.json | 95 + SocialPub/appsettings.Production.json | 65 + SocialPub/appsettings.json | 9 + 137 files changed, 19057 insertions(+) create mode 100644 SocialPub.ClientModels/AtLeastOnePropertyAttribute.cs create mode 100644 SocialPub.ClientModels/Constants.cs create mode 100644 SocialPub.ClientModels/Data/ViewLanguage.cs create mode 100644 SocialPub.ClientModels/LoginForm.cs create mode 100644 SocialPub.ClientModels/NewPasswordForm.cs create mode 100644 SocialPub.ClientModels/PasswordRecoveryForm.cs create mode 100644 SocialPub.ClientModels/Policies.cs create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.Designer.cs create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.bg.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.cs.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.da.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.de.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.el.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.es.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.et.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.fi.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.fr.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.hu.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.it.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.ja.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.lt.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.lv.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.nl.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.pl.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.pt.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.ro.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.ru.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.sk.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.sl.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.sv.resx create mode 100644 SocialPub.ClientModels/Resources/ErrorsResource.zh.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.Designer.cs create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.bg.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.cs.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.da.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.de.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.el.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.es.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.et.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.fi.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.fr.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.hu.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.it.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.ja.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.lt.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.lv.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.nl.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.pl.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.pt.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.ro.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.ru.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.sk.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.sl.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.sv.resx create mode 100644 SocialPub.ClientModels/Resources/FieldsNameResource.zh.resx create mode 100644 SocialPub.ClientModels/SocialPub.ClientModels.csproj create mode 100644 SocialPub.ClientModels/User/Avatar/InsertAvatarForm.cs create mode 100644 SocialPub.ClientModels/User/Avatar/UpdateAvatarForm.cs create mode 100644 SocialPub.ClientModels/User/Avatar/UpdateAvatarSettings.cs create mode 100644 SocialPub.ClientModels/User/Avatar/ViewAvatar.cs create mode 100644 SocialPub.ClientModels/User/Avatar/ViewAvatarSettings.cs create mode 100644 SocialPub.ClientModels/User/JwtUser.cs create mode 100644 SocialPub.ClientModels/User/UserForm.cs create mode 100644 SocialPub.ClientModels/User/UsersIds.cs create mode 100644 SocialPub.ClientModels/User/ViewUserSettings.cs create mode 100644 SocialPub.ClientModels/ValidatorAttributes/NoWhiteSpacesAttribute.cs create mode 100644 SocialPub.ClientModels/WebResult.cs create mode 100644 SocialPub.sln create mode 100644 SocialPub/Controllers/ClientToServer/AdminController.cs create mode 100644 SocialPub/Controllers/ClientToServer/DataController.cs create mode 100644 SocialPub/Controllers/ClientToServer/ModeratorController.cs create mode 100644 SocialPub/Controllers/ClientToServer/UserController.cs create mode 100644 SocialPub/Controllers/ServerToServer/PeasantsController.cs create mode 100644 SocialPub/Controllers/ServerToServer/UsersController.cs create mode 100644 SocialPub/Data/InitDb.cs create mode 100644 SocialPub/Data/LanguagesRow.cs create mode 100644 SocialPub/Data/languagesNative.json create mode 100644 SocialPub/Extensions/AddAuthExtension.cs create mode 100644 SocialPub/Extensions/Extensions.cs create mode 100644 SocialPub/Extensions/OperationCancelledExceptionFilter.cs create mode 100644 SocialPub/Extensions/StringExtensions.cs create mode 100644 SocialPub/Middleware/SocialPubConfigurations.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubActivity.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubActor.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubCollection.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubCollectionPage.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubLink.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubObject.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubOrderedCollection.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubOrderedCollectionPage.cs create mode 100644 SocialPub/Models/ActivityPub/ActivityPubTombstone.cs create mode 100644 SocialPub/Models/ActivityPub/Extra/ActivityPubAudience.cs create mode 100644 SocialPub/Models/ActivityPub/Extra/ActivityPubIcon.cs create mode 100644 SocialPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs create mode 100644 SocialPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs create mode 100644 SocialPub/Models/ActivityPub/Extra/ActivityPubResult.cs create mode 100644 SocialPub/Models/ActivityPub/MacroType.cs create mode 100644 SocialPub/Models/ActivityPub/ObjectType.cs create mode 100644 SocialPub/Models/AppConfiguration.cs create mode 100644 SocialPub/Models/Data/Language.cs create mode 100644 SocialPub/Models/Email/EmailConfiguration.cs create mode 100644 SocialPub/Models/MongoSettings.cs create mode 100644 SocialPub/Models/Post/DmPost.cs create mode 100644 SocialPub/Models/Post/Post.cs create mode 100644 SocialPub/Models/Post/PostBoost.cs create mode 100644 SocialPub/Models/Post/PostMedia.cs create mode 100644 SocialPub/Models/User/Avatar.cs create mode 100644 SocialPub/Models/User/EmailRecovery.cs create mode 100644 SocialPub/Models/User/RootToAvatar.cs create mode 100644 SocialPub/Models/User/RootUser.cs create mode 100644 SocialPub/Models/User/RootUserNote.cs create mode 100644 SocialPub/Models/User/UserPolicyType.cs create mode 100644 SocialPub/Models/socialpub.cs create mode 100644 SocialPub/Program.cs create mode 100644 SocialPub/Properties/launchSettings.json create mode 100644 SocialPub/Resources/GenericRes.Designer.cs create mode 100644 SocialPub/Resources/GenericRes.resx create mode 100644 SocialPub/Services/ActivityPubClient.cs create mode 100644 SocialPub/Services/AppConfigurationService.cs create mode 100644 SocialPub/Services/ClientToServer/Private/IPrivateAvatarUsersService.cs create mode 100644 SocialPub/Services/ClientToServer/Public/DataService.cs create mode 100644 SocialPub/Services/ClientToServer/Public/IDataService.cs create mode 100644 SocialPub/Services/ClientToServer/Public/IPublicAvatarUsersService.cs create mode 100644 SocialPub/Services/IRootUsersService.cs create mode 100644 SocialPub/Services/JwtEvents.cs create mode 100644 SocialPub/Services/RequestLocalizationOptionsService.cs create mode 100644 SocialPub/Services/RootUsersService.cs create mode 100644 SocialPub/SocialPub.csproj create mode 100644 SocialPub/StaticServices/AuthTokenManager.cs create mode 100644 SocialPub/StaticServices/DbEntities.cs create mode 100644 SocialPub/StaticServices/IPasswordHasher.cs create mode 100644 SocialPub/StaticServices/PasswordHasher.cs create mode 100644 SocialPub/appsettings.Development.json create mode 100644 SocialPub/appsettings.Production.json create mode 100644 SocialPub/appsettings.json diff --git a/SocialPub.ClientModels/AtLeastOnePropertyAttribute.cs b/SocialPub.ClientModels/AtLeastOnePropertyAttribute.cs new file mode 100644 index 0000000..52d81b3 --- /dev/null +++ b/SocialPub.ClientModels/AtLeastOnePropertyAttribute.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace SocialPub.ClientModels +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class AtLeastOnePropertyAttribute : ValidationAttribute + { + private string[] PropertyList { get; set; } + + public AtLeastOnePropertyAttribute(params string[] propertyList) + { + PropertyList = propertyList; + } + + public override object TypeId + { + get + { + return this; + } + } + + public override bool IsValid(object value) + { + PropertyInfo propertyInfo; + foreach (string propertyName in PropertyList) + { + propertyInfo = value.GetType().GetProperty(propertyName); + + if (propertyInfo != null && propertyInfo.GetValue(value, null) != null) + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/Constants.cs b/SocialPub.ClientModels/Constants.cs new file mode 100644 index 0000000..2b9b47c --- /dev/null +++ b/SocialPub.ClientModels/Constants.cs @@ -0,0 +1,11 @@ +namespace SocialPub.ClientModels +{ + public static class Constants + { + public const int MinPasswordLength = 7; + public const int MaxPasswordLength = 1025; + public const int GroupInvitationLength = 64; + + public const string PasswordRegex = @"^(?=.*)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$"; + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/Data/ViewLanguage.cs b/SocialPub.ClientModels/Data/ViewLanguage.cs new file mode 100644 index 0000000..c413e5a --- /dev/null +++ b/SocialPub.ClientModels/Data/ViewLanguage.cs @@ -0,0 +1,8 @@ +namespace SocialPub.ClientModels.Data +{ + public class ViewLanguage + { + public string Name { get; set; } + public string International2Code { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/LoginForm.cs b/SocialPub.ClientModels/LoginForm.cs new file mode 100644 index 0000000..5d14ab1 --- /dev/null +++ b/SocialPub.ClientModels/LoginForm.cs @@ -0,0 +1,38 @@ +using SocialPub.ClientModels.Resources; +using SocialPub.ClientModels.ValidatorAttributes; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels +{ + public class LoginForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = "Username", ResourceType = typeof(FieldsNameResource)), + StringLength(32, MinimumLength = 3, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + NoWhiteSpaces] + public string UserName { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Display(Name = "Password", ResourceType = typeof(FieldsNameResource)), + StringLength(Constants.MaxPasswordLength, MinimumLength = Constants.MinPasswordLength, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + RegularExpression(Constants.PasswordRegex, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Password { get; set; } + + [Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))] + public short LightThemeIndexColour { get; set; } = 25; + [Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))] + public short DarkThemeIndexColour { get; set; } = 215; + public short IconsThemeIndexColour { get; set; } = 25; + public bool ThemeIsDarkGray { get; set; } = false; + public bool ThemeIsLightGray { get; set; } = false; + public bool PreferSystemTheming { get; set; } = false; + + public bool ThemeIsDarkMode { get; set; } = false; + + public string InvitationPassword { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/NewPasswordForm.cs b/SocialPub.ClientModels/NewPasswordForm.cs new file mode 100644 index 0000000..3fdeafd --- /dev/null +++ b/SocialPub.ClientModels/NewPasswordForm.cs @@ -0,0 +1,29 @@ +using SocialPub.ClientModels.Resources; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels +{ + public class NewPasswordForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Display(Name = "NewPassword", ResourceType = typeof(FieldsNameResource)), + StringLength(Constants.MaxPasswordLength, MinimumLength = Constants.MinPasswordLength, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + RegularExpression(Constants.PasswordRegex, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource))] + public string NewPassword { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Compare(nameof(NewPassword), ErrorMessageResourceName = "ComparePasswords", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = "NewPasswordConfirmation", ResourceType = typeof(FieldsNameResource))] + public string RepeatedPassword { get; set; } + + [Display(Name = "RecoveryCode", ResourceType = typeof(FieldsNameResource)), + Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string RecoveryCode { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/PasswordRecoveryForm.cs b/SocialPub.ClientModels/PasswordRecoveryForm.cs new file mode 100644 index 0000000..8ed9703 --- /dev/null +++ b/SocialPub.ClientModels/PasswordRecoveryForm.cs @@ -0,0 +1,25 @@ +using SocialPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels +{ + [AtLeastOneProperty(nameof(UserName), nameof(Email), + ErrorMessageResourceName = "AtLeastOneProperty", + ErrorMessageResourceType = typeof(ErrorsResource))] + public class PasswordRecoveryForm + { + [Display(Name = "UserName", ResourceType = typeof(FieldsNameResource)), + MinLength(3, ErrorMessageResourceName = "MinLength", ErrorMessageResourceType = typeof(ErrorsResource))] + public string UserName { get; set; } + + [Display(Name = "Email", ResourceType = typeof(FieldsNameResource)), + DataType(DataType.EmailAddress), + MinLength(3, ErrorMessageResourceName = "MinLength", ErrorMessageResourceType = typeof(ErrorsResource)), + EmailAddress(ErrorMessageResourceName = "InvalidEmail", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Email { get; set; } + + public bool IsUsernameDisabled => !string.IsNullOrEmpty(Email); + public bool IsEmailDisabled => !string.IsNullOrEmpty(UserName); + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/Policies.cs b/SocialPub.ClientModels/Policies.cs new file mode 100644 index 0000000..ae17cdb --- /dev/null +++ b/SocialPub.ClientModels/Policies.cs @@ -0,0 +1,9 @@ +namespace SocialPub.ClientModels +{ + public static class Policies + { + public const string IsAdmin = "isAdmin"; + public const string IsUser = "isUser"; + public const string IsModerator = "isModerator"; + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.Designer.cs b/SocialPub.ClientModels/Resources/ErrorsResource.Designer.cs new file mode 100644 index 0000000..cc9ca64 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.Designer.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// 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 SocialPub.ClientModels.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", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ErrorsResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ErrorsResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SocialPub.ClientModels.Resources.ErrorsResource", typeof(ErrorsResource).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to At least one field should be filled.. + /// + public static string AtLeastOneProperty { + get { + return ResourceManager.GetString("AtLeastOneProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} doesn't match the new password.. + /// + public static string ComparePasswords { + get { + return ResourceManager.GetString("ComparePasswords", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid format.. + /// + public static string DataType { + get { + return ResourceManager.GetString("DataType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Empty spaces are not allowed.. + /// + public static string EmptySpacesNotAllowed { + get { + return ResourceManager.GetString("EmptySpacesNotAllowed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is invalid.. + /// + public static string InvalidEmail { + get { + return ResourceManager.GetString("InvalidEmail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid password, upper-case, lower-case and number characters required.. + /// + public static string InvalidPassword { + get { + return ResourceManager.GetString("InvalidPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max amount of {0} items reached.. + /// + public static string MaxLengthArray { + get { + return ResourceManager.GetString("MaxLengthArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max file size is 1MB.. + /// + public static string MaxLengthFileConfrontation { + get { + return ResourceManager.GetString("MaxLengthFileConfrontation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max length of {0} characters reached.. + /// + public static string MaxLengthString { + get { + return ResourceManager.GetString("MaxLengthString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} should be {1} characters long. + /// + public static string MinLength { + get { + return ResourceManager.GetString("MinLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The accepted value must be between {1} and {2}. + /// + public static string Range { + get { + return ResourceManager.GetString("Range", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is required.. + /// + public static string Required { + get { + return ResourceManager.GetString("Required", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one character is allowed.. + /// + public static string SingleCharacterValidator { + get { + return ResourceManager.GetString("SingleCharacterValidator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is exceeding the length of {1} characters.. + /// + public static string StringLength { + get { + return ResourceManager.GetString("StringLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is exceeding the length between {2} and {1} characters.. + /// + public static string StringLengthMinMax { + get { + return ResourceManager.GetString("StringLengthMinMax", resourceCulture); + } + } + } +} diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.bg.resx b/SocialPub.ClientModels/Resources/ErrorsResource.bg.resx new file mode 100644 index 0000000..ce4a45e --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.bg.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Трябва да се попълни поне едно поле. + + + {0} не съвпада с новата парола. + + + Невалиден формат. + + + Празните пространства не са разрешени. + + + {0} е невалиден. + + + Неправилна парола, изискват се главни, малки букви и цифри. + + + Достигнат е максималният брой на {0} елементите. + + + Максималният размер на файла е 1 MB. + + + Достигната е максималната дължина от {0} символа. + + + Дължината на {0} трябва да бъде {1} знака + + + Приетата стойност трябва да е между {1} и {2} + + + {0} се изисква. + + + Позволен е само един символ. + + + {0} превишава дължината на {1} символа. + + + {0} превишава дължината между {2} и {1} символа. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.cs.resx b/SocialPub.ClientModels/Resources/ErrorsResource.cs.resx new file mode 100644 index 0000000..ce0f462 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.cs.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Mělo by být vyplněno alespoň jedno pole. + + + {0} neodpovídá novému heslu. + + + Nesprávný formát. + + + Prázdná místa nejsou povolena. + + + {0} je neplatný. + + + Nesprávné heslo, vyžadují se velká a malá písmena a číslice. + + + Bylo dosaženo maximálního počtu {0} položek. + + + Maximální velikost souboru je 1 MB. + + + Dosažena maximální délka {0} znaků. + + + {0} by mělo mít délku {1} znaků + + + Přijatá hodnota musí být mezi {1} a {2} + + + {0} je vyžadováno. + + + Je povolen pouze jeden znak. + + + {0} přesahuje délku {1} znaků. + + + {0} přesahuje délku mezi {2} a {1} znaky. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.da.resx b/SocialPub.ClientModels/Resources/ErrorsResource.da.resx new file mode 100644 index 0000000..9f24a0b --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.da.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Mindst ét felt skal være udfyldt. + + + {0} passer ikke til den nye adgangskode. + + + Ugyldigt format. + + + Tomme mellemrum er ikke tilladt. + + + {0} er ugyldig. + + + Ugyldig adgangskode, der kræves store og små bogstaver og tal. + + + Det maksimale antal {0} genstande er nået. + + + Den maksimale filstørrelse er 1 MB. + + + Den maksimale længde på {0} tegn er nået. + + + {0} skal være {1} tegn lang + + + Den accepterede værdi skal ligge mellem {1} og {2} + + + {0} er påkrævet. + + + Kun ét tegn er tilladt. + + + {0} overstiger længden af {1} tegn. + + + {0} overstiger længden mellem {2} og {1} tegn. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.de.resx b/SocialPub.ClientModels/Resources/ErrorsResource.de.resx new file mode 100644 index 0000000..d51322f --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.de.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Mindestens ein Feld sollte ausgefüllt werden. + + + {0} stimmt nicht mit dem neuen Passwort überein. + + + Ungültiges Format. + + + Leerzeichen sind nicht erlaubt. + + + {0} ist ungültig. + + + Ungültiges Passwort, Groß- und Kleinbuchstaben sowie Zahlen erforderlich. + + + Maximale Anzahl von {0} Gegenständen erreicht. + + + Die maximale Dateigröße beträgt 1 MB. + + + Maximale Länge von {0} Zeichen erreicht. + + + {0} sollte {1} Zeichen lang sein + + + Der akzeptierte Wert muss zwischen {1} und {2} liegen + + + {0} ist erforderlich. + + + Es ist nur ein Zeichen erlaubt. + + + {0} ist länger als die Länge von {1} Zeichen. + + + {0} überschreitet die Länge zwischen {2} und {1} Zeichen. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.el.resx b/SocialPub.ClientModels/Resources/ErrorsResource.el.resx new file mode 100644 index 0000000..bc469eb --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.el.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Τουλάχιστον ένα πεδίο πρέπει να είναι συμπληρωμένο. + + + {0} δεν ταιριάζει με τον νέο κωδικό πρόσβασης. + + + Μη έγκυρη μορφή. + + + Τα κενά διαστήματα δεν επιτρέπονται. + + + {0} είναι άκυρη. + + + Μη έγκυρος κωδικός πρόσβασης, απαιτούνται κεφαλαίοι, πεζοί και αριθμητικοί χαρακτήρες. + + + Έφτασε η μέγιστη ποσότητα {0} αντικειμένων. + + + Το μέγιστο μέγεθος αρχείου είναι 1MB. + + + Το μέγιστο μήκος των χαρακτήρων {0} έχει επιτευχθεί. + + + {0} πρέπει να είναι {1} χαρακτήρες + + + Η αποδεκτή τιμή πρέπει να είναι μεταξύ {1} και {2} + + + {0} απαιτείται. + + + Επιτρέπεται μόνο ένας χαρακτήρας. + + + {0} υπερβαίνει το μήκος των χαρακτήρων {1}. + + + {0} υπερβαίνει το μήκος μεταξύ {2} και {1} χαρακτήρων. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.es.resx b/SocialPub.ClientModels/Resources/ErrorsResource.es.resx new file mode 100644 index 0000000..bd6a1a4 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.es.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Se debe rellenar al menos un campo. + + + {0} no coincide con la nueva contraseña. + + + Formato inválido. + + + Los espacios vacíos no están permitidos. + + + {0} no es válido. + + + Contraseña inválida, se requieren caracteres en mayúsculas, minúsculas y números. + + + Cantidad máxima de artículos {0} alcanzada. + + + El tamaño máximo del archivo es de 1MB. + + + Se ha alcanzado la longitud máxima de {0} caracteres. + + + {0} debe tener {1} caracteres + + + El valor aceptado debe estar entre {1} y {2} + + + {0} es necesario. + + + Sólo se permite un carácter. + + + {0} supera la longitud de {1} caracteres. + + + {0} está excediendo la longitud entre {2} y {1} caracteres. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.et.resx b/SocialPub.ClientModels/Resources/ErrorsResource.et.resx new file mode 100644 index 0000000..40d175e --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.et.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Vähemalt üks väli peaks olema täidetud. + + + {0} ei vasta uuele paroolile. + + + Vale vorming. + + + Tühjad tühikud ei ole lubatud. + + + {0} on kehtetu. + + + Vale salasõna, nõutavad suur- ja väiketähed ning numbrimärgid. + + + Maksimaalne {0} esemete arv on saavutatud. + + + Faili maksimaalne suurus on 1MB. + + + Maksimaalne pikkus {0} tähemärki on saavutatud. + + + {0} peaks olema {1} tähemärki pikk + + + Aktsepteeritud väärtus peab olema vahemikus {1} ja {2} + + + {0} on nõutav. + + + Lubatud on ainult üks tähemärk. + + + {0} ületab {1} tähemärgi pikkust. + + + {0} ületab {2} ja {1} tähemärgi pikkust. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.fi.resx b/SocialPub.ClientModels/Resources/ErrorsResource.fi.resx new file mode 100644 index 0000000..849e519 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.fi.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Vähintään yksi kenttä on täytettävä. + + + {0} ei vastaa uutta salasanaa. + + + Virheellinen muoto. + + + Tyhjät välilyönnit eivät ole sallittuja. + + + {0} on virheellinen. + + + Virheellinen salasana, vaaditaan isoja ja pieniä kirjaimia sekä numeromerkkejä. + + + {0} kohteiden enimmäismäärä saavutettu. + + + Tiedoston enimmäiskoko on 1MB. + + + Maksimipituus {0} merkkiä saavutettu. + + + {0} pitäisi olla {1} merkkiä pitkä + + + Hyväksytyn arvon on oltava {1} ja {2} välillä + + + {0} tarvitaan. + + + Vain yksi merkki on sallittu. + + + {0} ylittää {1} merkin pituuden. + + + {0} ylittää pituuden {2} ja {1} merkin välillä. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.fr.resx b/SocialPub.ClientModels/Resources/ErrorsResource.fr.resx new file mode 100644 index 0000000..9902a8b --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.fr.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Au moins un champ doit être rempli. + + + {0} ne correspond pas au nouveau mot de passe. + + + Format non valide. + + + Les espaces vides ne sont pas autorisés. + + + {0} n'est pas valide. + + + Mot de passe invalide, caractères majuscules, minuscules et chiffres requis. + + + Le nombre maximum d'éléments {0} a été atteint. + + + La taille maximale des fichiers est de 1 Mo. + + + La longueur maximale de {0} caractères est atteinte. + + + {0} devrait avoir {1} caractères de long + + + La valeur acceptée doit être comprise entre {1} et {2} + + + {0} est nécessaire. + + + Un seul caractère est autorisé. + + + {0} dépasse la longueur de {1} caractères. + + + {0} dépasse la longueur comprise entre {2} et {1} caractères. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.hu.resx b/SocialPub.ClientModels/Resources/ErrorsResource.hu.resx new file mode 100644 index 0000000..f33c4ca --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.hu.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Legalább egy mezőt ki kell tölteni. + + + {0} nem felel meg az új jelszónak. + + + Érvénytelen formátum. + + + Üres szóközök nem engedélyezettek. + + + {0} érvénytelen. + + + Érvénytelen jelszó, nagybetűs, kisbetűs és számjegyek szükségesek. + + + A {0} tételek maximális száma elérte. + + + A fájl maximális mérete 1MB. + + + A {0} karakterek maximális hossza elérte. + + + {0} legyen {1} karakter hosszúságú + + + Az elfogadott értéknek {1} és {2} között kell lennie + + + {0} szükséges. + + + Csak egy karakter megengedett. + + + {0} meghaladja az {1} karakter hosszúságát. + + + {0} meghaladja a {2} és {1} karakterek közötti hosszúságot. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.it.resx b/SocialPub.ClientModels/Resources/ErrorsResource.it.resx new file mode 100644 index 0000000..8d3f199 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.it.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Almeno un campo deve essere compilato. + + + {0} non è uguale alla password precedentemente inserita. + + + Formato errato. + + + Gli spazi vuoti non sono ammessi. + + + {0} è invalida. + + + Password non valida, deve avere almeno un carattere grande, un carattere piccolo ed un numero. + + + Raggiunta la quantità massima di oggetti {0}. + + + La dimensione massima del file è di 1 MB. + + + Lunghezza massima di {0} caratteri raggiunta. + + + {0} non può essere più corto di {1} caratteri + + + Il valore accettato deve essere tra {1} e {2} + + + {0} è richiesto/a. + + + È consentito un solo carattere. + + + {0} eccede la lunghezza di {1} caratteri. + + + {0} eccede la lunghezza tra {2} e {1} caratteri. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.ja.resx b/SocialPub.ClientModels/Resources/ErrorsResource.ja.resx new file mode 100644 index 0000000..017cab9 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.ja.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 少なくとも1つのフィールドを埋める必要があります。 + + + {0}が新しいパスワードと一致しない。 + + + 無効なフォーマットです。 + + + 空白は許されない。 + + + {0}は無効です。 + + + パスワードが無効です。大文字、小文字、数字の各文字が必要です。 + + + アイテムが最大量{0}に達した。 + + + 最大ファイルサイズは1MBです。 + + + 最大長{0}文字に達しました。 + + + {0}は{1}文字の長さにしてください + + + 受入値は{1}と{2}の間でなければならない + + + {0}が必要です。 + + + 1文字のみ使用可能です。 + + + {0}は{1}文字の長さを超えています。 + + + {0}は{2}から{1}文字の間の長さを超えています。 + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.lt.resx b/SocialPub.ClientModels/Resources/ErrorsResource.lt.resx new file mode 100644 index 0000000..adb2cf6 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.lt.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Turėtų būti užpildytas bent vienas laukelis. + + + {0} neatitinka naujojo slaptažodžio. + + + Netinkamas formatas. + + + Tuščios vietos neleidžiamos. + + + {0} negalioja. + + + Neteisingas slaptažodis, reikalingi didieji, mažieji ir skaitmeniniai simboliai. + + + Pasiektas maksimalus {0} elementų kiekis. + + + Didžiausias failo dydis - 1 MB. + + + Pasiektas maksimalus {0} simbolių ilgis. + + + {0} turėtų būti {1} simbolių ilgio + + + Priimama vertė turi būti nuo {1} iki {2} + + + {0} yra privalomas. + + + Leidžiamas tik vienas simbolis. + + + {0} viršija {1} simbolių ilgį. + + + {0} viršija ilgį tarp {2} ir {1} simbolių. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.lv.resx b/SocialPub.ClientModels/Resources/ErrorsResource.lv.resx new file mode 100644 index 0000000..e9c38b1 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.lv.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Jāaizpilda vismaz viens lauks. + + + {0} neatbilst jaunajai parolei. + + + Nederīgs formāts. + + + Tukšas atstarpes nav atļautas. + + + {0} ir nederīgs. + + + Nederīga parole, nepieciešamas lielās, mazās un ciparu zīmes. + + + Sasniegts maksimālais {0} vienību skaits. + + + Maksimālais faila izmērs ir 1 MB. + + + Sasniegts maksimālais garums {0} rakstzīmju. + + + {0} jābūt {1} rakstzīmju garam + + + Pieņemtajai vērtībai jābūt no {1} līdz {2} + + + {0} ir nepieciešams. + + + Ir atļauts tikai viens raksturs. + + + {0} pārsniedz {1} rakstzīmju garumu. + + + {0} pārsniedz garumu starp {2} un {1} rakstzīmēm. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.nl.resx b/SocialPub.ClientModels/Resources/ErrorsResource.nl.resx new file mode 100644 index 0000000..83a2ee6 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.nl.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Ten minste één veld moet worden ingevuld. + + + {0} komt niet overeen met het nieuwe wachtwoord. + + + Ongeldig formaat. + + + Lege ruimtes zijn niet toegestaan. + + + {0} is ongeldig. + + + Ongeldig wachtwoord, hoofdletters, kleine letters en cijfers zijn vereist. + + + Maximum aantal {0} items bereikt. + + + Maximale bestandsgrootte is 1MB. + + + Max lengte van {0} tekens bereikt. + + + {0} moet {1} tekens lang zijn + + + De geaccepteerde waarde moet tussen {1} en {2} liggen + + + {0} is vereist. + + + Slechts één teken is toegestaan. + + + {0} overschrijdt de lengte van {1} tekens. + + + {0} overschrijdt de lengte tussen {2} en {1} tekens. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.pl.resx b/SocialPub.ClientModels/Resources/ErrorsResource.pl.resx new file mode 100644 index 0000000..6c12fe3 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.pl.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Przynajmniej jedno pole powinno być wypełnione. + + + {0} nie pasuje do nowego hasła. + + + Nieprawidłowy format. + + + Puste miejsca nie są dozwolone. + + + {0} jest nieprawidłowe. + + + Nieprawidłowe hasło, wymagane są wielkie litery, małe litery i cyfry. + + + Osiągnięto maksymalną ilość {0} elementów. + + + Maksymalna wielkość pliku to 1MB. + + + Osiągnięto maksymalną długość {0} znaków. + + + {0} powinno mieć długość {1} znaków + + + Akceptowana wartość musi zawierać się w przedziale od {1} do {2} + + + {0} jest wymagane. + + + Dozwolony jest tylko jeden znak. + + + {0} przekracza długość {1} znaków. + + + {0} przekracza długość pomiędzy {2} a {1} znaków. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.pt.resx b/SocialPub.ClientModels/Resources/ErrorsResource.pt.resx new file mode 100644 index 0000000..9ec6b4e --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.pt.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Pelo menos um campo deve ser preenchido. + + + {0} não corresponde à nova senha. + + + Formato inválido. + + + Os espaços vazios não são permitidos. + + + {0} é inválido. + + + Senha inválida, letras maiúsculas, minúsculas e caracteres numéricos necessários. + + + Quantidade máxima de {0} itens alcançados. + + + O tamanho máximo do ficheiro é de 1MB. + + + Comprimento máximo de {0} caracteres alcançados. + + + deve ter {0} caracteres {1}longos + + + O valor aceite deve estar entre {1} e {2} + + + {0} é necessário. + + + Apenas um carácter é permitido. + + + {0} excede o comprimento de caracteres {1}. + + + {0} excede o comprimento entre os caracteres {2} e {1}. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.resx b/SocialPub.ClientModels/Resources/ErrorsResource.resx new file mode 100644 index 0000000..ba9282e --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + At least one field should be filled. + + + {0} doesn't match the new password. + + + Invalid format. + + + Empty spaces are not allowed. + + + {0} is invalid. + + + Invalid password, upper-case, lower-case and number characters required. + + + Max amount of {0} items reached. + + + Max file size is 1MB. + + + Max length of {0} characters reached. + + + {0} should be {1} characters long + + + The accepted value must be between {1} and {2} + + + {0} is required. + + + Only one character is allowed. + + + {0} is exceeding the length of {1} characters. + + + {0} is exceeding the length between {2} and {1} characters. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.ro.resx b/SocialPub.ClientModels/Resources/ErrorsResource.ro.resx new file mode 100644 index 0000000..eeb2947 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.ro.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Cel puțin un câmp trebuie completat. + + + {0} nu se potrivește cu noua parolă. + + + Format nevalabil. + + + Spațiile goale nu sunt permise. + + + {0} nu este valabil. + + + Parolă invalidă, sunt necesare caractere majuscule, minuscule și numere. + + + A fost atinsă cantitatea maximă de elemente {0}. + + + Dimensiunea maximă a fișierului este de 1 MB. + + + A fost atinsă lungimea maximă de {0} caractere. + + + {0} ar trebui să aibă {1} caractere + + + Valoarea acceptată trebuie să fie cuprinsă între {1} și {2} + + + {0} este necesar. + + + Este permis doar un singur caracter. + + + {0} depășește lungimea de {1} caractere. + + + {0} depășește lungimea dintre {2} și {1} caractere. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.ru.resx b/SocialPub.ClientModels/Resources/ErrorsResource.ru.resx new file mode 100644 index 0000000..d3656b1 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.ru.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Хотя бы одно поле должно быть заполнено. + + + {0} не соответствует новому паролю. + + + Неверный формат. + + + Пустые пробелы не допускаются. + + + {0} недействителен. + + + Неверный пароль, требуются символы верхнего и нижнего регистров и цифры. + + + Достигнуто максимальное количество {0} предметов. + + + Максимальный размер файла - 1 МБ. + + + Достигнута максимальная длина {0} символов. + + + {0} должно быть длиной {1} символов + + + Принятое значение должно быть между {1} и {2} + + + требуется {0}. + + + Разрешается использовать только один символ. + + + {0} превышает длину {1} символов. + + + {0} превышает длину между {2} и {1} символами. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.sk.resx b/SocialPub.ClientModels/Resources/ErrorsResource.sk.resx new file mode 100644 index 0000000..0f3c6ed --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.sk.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Malo by byť vyplnené aspoň jedno pole. + + + {0} sa nezhoduje s novým heslom. + + + Neplatný formát. + + + Prázdne miesta nie sú povolené. + + + {0} je neplatný. + + + Nesprávne heslo, vyžadujú sa veľké, malé a číselné znaky. + + + Maximálny počet položiek {0} dosiahnutý. + + + Maximálna veľkosť súboru je 1 MB. + + + Dosiahnutá maximálna dĺžka {0} znakov. + + + {0} by malo mať dĺžku {1} znakov + + + Prijatá hodnota musí byť medzi {1} a {2} + + + {0} sa vyžaduje. + + + Povolený je len jeden znak. + + + {0} prekračuje dĺžku {1} znakov. + + + {0} prekračuje dĺžku medzi {2} a {1} znakmi. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.sl.resx b/SocialPub.ClientModels/Resources/ErrorsResource.sl.resx new file mode 100644 index 0000000..bce39a0 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.sl.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Vsaj eno polje mora biti izpolnjeno. + + + {0} se ne ujema z novim geslom. + + + Nepravilna oblika. + + + Prazni prostori niso dovoljeni. + + + {0} je neveljavno. + + + Nepravilno geslo, potrebni so veliki, mali in številčni znaki. + + + Dosežena je največja količina {0} predmetov. + + + Največja velikost datoteke je 1 MB. + + + Dosežena je največja dolžina {0} znakov. + + + {0} mora biti dolg {1} znakov + + + Sprejeta vrednost mora biti med {1} in {2} + + + {0} je potrebno. + + + Dovoljen je samo en znak. + + + {0} presega dolžino {1} znakov. + + + {0} presega dolžino med {2} in {1} znaki. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.sv.resx b/SocialPub.ClientModels/Resources/ErrorsResource.sv.resx new file mode 100644 index 0000000..952ea89 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.sv.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Minst ett fält ska fyllas i. + + + {0} matchar inte det nya lösenordet. + + + Felaktigt format. + + + Tomma blanksteg är inte tillåtna. + + + {0} är ogiltig. + + + Felaktigt lösenord, det krävs stora och små bokstäver samt siffror. + + + Max antal {0} objekt har uppnåtts. + + + Filstorleken får vara högst 1 MB. + + + Maxlängd på {0} tecken har uppnåtts. + + + {0} ska vara {1} tecken lång + + + Det accepterade värdet måste ligga mellan {1} och {2} + + + {0} krävs. + + + Endast ett tecken är tillåtet. + + + {0} överskrider längden på {1} tecken. + + + {0} överskrider längden mellan {2} och {1} tecken. + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/ErrorsResource.zh.resx b/SocialPub.ClientModels/Resources/ErrorsResource.zh.resx new file mode 100644 index 0000000..0f9e616 --- /dev/null +++ b/SocialPub.ClientModels/Resources/ErrorsResource.zh.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 至少应填写一个字段。 + + + {0}与新密码不匹配。 + + + 无效格式。 + + + 不允许出现空位。 + + + {0}是无效的。 + + + 无效的密码,需要大写字母、小写字母和数字字符。 + + + 达到{0}项的最大数量。 + + + 最大文件大小为1MB。 + + + 达到最大长度的{0}个字符。 + + + {0}应该是{1}个字符的长度 + + + 接受的值必须在{1}和{2}之间 + + + {0}是必需的。 + + + 只允许一个字符。 + + + {0}是超过了{1}个字符的长度。 + + + {0}是超过了{2}和{1}字符之间的长度。 + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.Designer.cs b/SocialPub.ClientModels/Resources/FieldsNameResource.Designer.cs new file mode 100644 index 0000000..6775f72 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.Designer.cs @@ -0,0 +1,657 @@ +//------------------------------------------------------------------------------ +// +// 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 SocialPub.ClientModels.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", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class FieldsNameResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal FieldsNameResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SocialPub.ClientModels.Resources.FieldsNameResource", typeof(FieldsNameResource).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to (necessary in case of password recovery). + /// + public static string _necessary_in_case_of_password_recovery_ { + get { + return ResourceManager.GetString("(necessary in case of password recovery)", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (<a class="is-underlined has-text-info" tabindex="-1" target="_blank" href="https://keepassxc.org/">password manager</a> suggested). + /// + public static string _password_manager_suggested_ { + get { + return ResourceManager.GetString("(password manager suggested)", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Amount. + /// + public static string Amount { + get { + return ResourceManager.GetString("Amount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Answer id. + /// + public static string AnswerId { + get { + return ResourceManager.GetString("AnswerId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Answer text. + /// + public static string AnswerText { + get { + return ResourceManager.GetString("AnswerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Custom character(emoji). + /// + public static string Character { + get { + return ResourceManager.GetString("Character", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Colour. + /// + public static string ColourIndex { + get { + return ResourceManager.GetString("ColourIndex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Comment id. + /// + public static string CommentId { + get { + return ResourceManager.GetString("CommentId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Comment text. + /// + public static string CommentText { + get { + return ResourceManager.GetString("CommentText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confrontation. + /// + public static string Confrontation { + get { + return ResourceManager.GetString("Confrontation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confrontation id. + /// + public static string ConfrontationId { + get { + return ResourceManager.GetString("ConfrontationId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Context. + /// + public static string Context { + get { + return ResourceManager.GetString("Context", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End date. + /// + public static string DateEnd { + get { + return ResourceManager.GetString("DateEnd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start date. + /// + public static string DateStart { + get { + return ResourceManager.GetString("DateStart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description. + /// + public static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discussion. + /// + public static string Discussion { + get { + return ResourceManager.GetString("Discussion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End date. + /// + public static string DiscussionEndDate { + get { + return ResourceManager.GetString("DiscussionEndDate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End time. + /// + public static string DiscussionEndTime { + get { + return ResourceManager.GetString("DiscussionEndTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discussion id. + /// + public static string DiscussionId { + get { + return ResourceManager.GetString("DiscussionId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discussion text. + /// + public static string DiscussionText { + get { + return ResourceManager.GetString("DiscussionText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discussion title. + /// + public static string DiscussionTitle { + get { + return ResourceManager.GetString("DiscussionTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Email. + /// + public static string Email { + get { + return ResourceManager.GetString("Email", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Emoji. + /// + public static string Emoji { + get { + return ResourceManager.GetString("Emoji", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max amount of confrontations. + /// + public static string ExpectedMaxConfrontationsBeforeClosingAndShowing { + get { + return ResourceManager.GetString("ExpectedMaxConfrontationsBeforeClosingAndShowing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fact. + /// + public static string Fact { + get { + return ResourceManager.GetString("Fact", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Facts. + /// + public static string Facts { + get { + return ResourceManager.GetString("Facts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File id. + /// + public static string FileId { + get { + return ResourceManager.GetString("FileId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Groups. + /// + public static string Groups { + get { + return ResourceManager.GetString("Groups", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Include the first answer in the invitation preview? (making it more engaging to join the discussion). + /// + public static string IncludeFirstAnswerInInvitation { + get { + return ResourceManager.GetString("IncludeFirstAnswerInInvitation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invitation code. + /// + public static string InvitationCode { + get { + return ResourceManager.GetString("InvitationCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invitation code or link. + /// + public static string InvitationCodeLink { + get { + return ResourceManager.GetString("InvitationCodeLink", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invitation password. + /// + public static string InvitationPassword { + get { + return ResourceManager.GetString("InvitationPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Require password for preview. + /// + public static string IsPasswordProtectedForPreview { + get { + return ResourceManager.GetString("IsPasswordProtectedForPreview", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Require password to show the preview. + /// + public static string IsPasswordProtectedForPreviewText { + get { + return ResourceManager.GetString("IsPasswordProtectedForPreviewText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add password protection. + /// + public static string IsPasswordProtectedText { + get { + return ResourceManager.GetString("IsPasswordProtectedText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to IsStructured. + /// + public static string IsStructured { + get { + return ResourceManager.GetString("IsStructured", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Language id. + /// + public static string LanguageId { + get { + return ResourceManager.GetString("LanguageId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string Name { + get { + return ResourceManager.GetString("Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable notifications?(useful to receive app updates). + /// + public static string NativeNotificationsEnabled { + get { + return ResourceManager.GetString("NativeNotificationsEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New password. + /// + public static string NewPassword { + get { + return ResourceManager.GetString("NewPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New password confirmation. + /// + public static string NewPasswordConfirmation { + get { + return ResourceManager.GetString("NewPasswordConfirmation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Old password. + /// + public static string OldPassword { + get { + return ResourceManager.GetString("OldPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to One time only. + /// + public static string OneTime { + get { + return ResourceManager.GetString("OneTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password. + /// + public static string Password { + get { + return ResourceManager.GetString("Password", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perception. + /// + public static string Perception { + get { + return ResourceManager.GetString("Perception", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perceptions. + /// + public static string Perceptions { + get { + return ResourceManager.GetString("Perceptions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prefer system theme. + /// + public static string PreferSystemTheming { + get { + return ResourceManager.GetString("PreferSystemTheming", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Recovery code. + /// + public static string RecoveryCode { + get { + return ResourceManager.GetString("RecoveryCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Recurring. + /// + public static string Recurring { + get { + return ResourceManager.GetString("Recurring", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Scan for:. + /// + public static string Scan_for_ { + get { + return ResourceManager.GetString("Scan for:", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search. + /// + public static string SearchText { + get { + return ResourceManager.GetString("SearchText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sharing Secret Code. + /// + public static string ShareCode { + get { + return ResourceManager.GetString("ShareCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show donator badge. + /// + public static string ShowDonatorBadge { + get { + return ResourceManager.GetString("ShowDonatorBadge", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show the logs tab to support troubleshooting.. + /// + public static string ShowLogs { + get { + return ResourceManager.GetString("ShowLogs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Take type. + /// + public static string TakeType { + get { + return ResourceManager.GetString("TakeType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Simple. + /// + public static string TakeTypeSimple { + get { + return ResourceManager.GetString("TakeTypeSimple", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Structured. + /// + public static string TakeTypeStructured { + get { + return ResourceManager.GetString("TakeTypeStructured", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Theme image. + /// + public static string Theme_image { + get { + return ResourceManager.GetString("Theme image", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dark mode. + /// + public static string ThemeIsDarkMode { + get { + return ResourceManager.GetString("ThemeIsDarkMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ticks. + /// + public static string Ticks { + get { + return ResourceManager.GetString("Ticks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Title. + /// + public static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update reason. + /// + public static string UpdateReason { + get { + return ResourceManager.GetString("UpdateReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Submit anonymously. + /// + public static string UpsertUserTakeIsAnonymous { + get { + return ResourceManager.GetString("UpsertUserTakeIsAnonymous", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username. + /// + public static string Username { + get { + return ResourceManager.GetString("Username", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username to search and add. + /// + public static string Username_to_search_and_add { + get { + return ResourceManager.GetString("Username to search and add", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Usernames to search and add. + /// + public static string Usernames_to_search_and_add { + get { + return ResourceManager.GetString("Usernames to search and add", resourceCulture); + } + } + } +} diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.bg.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.bg.resx new file mode 100644 index 0000000..8e091d9 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.bg.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (необходимо в случай на възстановяване на парола) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">предложен мениджър на пароли</a>) + + + Сума + + + Отговор id + + + Текст на отговора + + + Персонализиран символ (емотикон) + + + Цвят + + + Коментар id + + + Текст на коментара + + + Конфронтация + + + Конфронтация id + + + Контекст + + + Крайна дата + + + Начална дата + + + Описание + + + Дискусия + + + Крайна дата + + + Краен час + + + Дискусия id + + + Текст за обсъждане + + + Заглавие на дискусията + + + Имейл + + + Емотикони + + + Максимален брой конфронтации + + + Факт + + + Факти + + + Идентификатор на файла + + + Групи + + + Да включите първия отговор в предварителния преглед на поканата? (за да се включите в дискусията по-ангажиращо) + + + Код на поканата + + + Код или връзка за покана + + + Парола за покана + + + Изискване на парола за предварителен преглед + + + Изискване на парола за показване на предварителния преглед + + + Добавяне на защита с парола + + + IsStructured + + + Идентификатор на езика + + + Име + + + Активиране на известията?(полезно за получаване на актуализации на приложението) + + + Нова парола + + + Потвърждаване на нова парола + + + Стара парола + + + Само веднъж + + + Парола + + + Възприемане + + + Възприятия + + + Предпочитайте темата на системата + + + Код за възстановяване + + + Повтарящи се + + + Сканиране за: + + + Търсене + + + Споделяне на тайния код + + + Покажи значката на дарителя + + + Покажете раздела с дневници, за да подпомогнете отстраняването на неизправности. + + + Вид на вземането + + + Прост + + + Структуриран + + + Изображение на темата + + + Тъмен режим + + + Кърлежи + + + Заглавие + + + Причина за актуализиране + + + Подайте анонимно + + + Потребителско име + + + Потребителско име за търсене и добавяне + + + Потребителски имена за търсене и добавяне + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.cs.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.cs.resx new file mode 100644 index 0000000..abdb4ba --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.cs.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nutné v případě obnovení hesla) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">správce hesel</a> navrženo) + + + Částka + + + Odpověď id + + + Text odpovědi + + + Vlastní znak(emoji) + + + Barva + + + Komentář id + + + Text komentáře + + + Konfrontace + + + Id konfrontace + + + Kontext + + + Datum ukončení + + + Datum zahájení + + + Popis + + + Diskuse + + + Datum ukončení + + + Čas ukončení + + + Diskuze id + + + Text k diskusi + + + Název diskuse + + + E-mail + + + Emoji + + + Maximální počet konfrontací + + + Fakta + + + Fakta + + + Identifikátor souboru + + + Skupiny + + + Zahrnout první odpověď do náhledu pozvánky? (abyste se mohli zapojit do diskuse) + + + Kód pozvánky + + + Pozvánkový kód nebo odkaz + + + Heslo pozvánky + + + Vyžadovat heslo pro náhled + + + Vyžadování hesla pro zobrazení náhledu + + + Přidání ochrany heslem + + + IsStructured + + + Identifikace jazyka + + + Název + + + Povolit oznámení?(užitečné pro příjem aktualizací aplikace) + + + Nové heslo + + + Potvrzení nového hesla + + + Staré heslo + + + Pouze jednou + + + Heslo + + + Vnímání + + + Vnímání + + + Preferovat systémové téma + + + Kód obnovy + + + Opakující se + + + Vyhledat: + + + Vyhledávání + + + Sdílení tajného kódu + + + Zobrazit odznak dárce + + + Zobrazit kartu protokolů pro podporu řešení problémů. + + + Vezměte si typ + + + Jednoduché + + + Strukturovaný + + + Tématický obrázek + + + Tmavý režim + + + Klíšťata + + + Název + + + Důvod aktualizace + + + Anonymní odeslání + + + Uživatelské jméno + + + Uživatelské jméno pro vyhledávání a přidávání + + + Uživatelská jména pro vyhledávání a přidávání + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.da.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.da.resx new file mode 100644 index 0000000..356eed5 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.da.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nødvendig i tilfælde af inddrivelse af adgangskode) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">password manager</a> foreslået) + + + Beløb + + + Svar id + + + Svar tekst + + + Brugerdefineret tegn (emoji) + + + Farve + + + Kommentar id + + + Kommentar tekst + + + Konfrontation + + + Konfrontation id + + + Kontekst + + + Slutdato + + + Startdato + + + Beskrivelse + + + Diskussion + + + Slutdato + + + Sluttidspunkt + + + Diskussion id + + + Tekst til drøftelse + + + Diskussionstitel + + + E-mail + + + Emoji + + + Maksimalt antal konfrontationer + + + Fakta + + + Fakta + + + Fil id + + + Grupper + + + Medtage det første svar i forhåndsvisningen af invitationen? (gør det mere indbydende at deltage i diskussionen) + + + Kode for indbydelse + + + Indbydelseskode eller link + + + Adgangskode til invitationen + + + Kræver adgangskode til visning af preview + + + Kræver adgangskode for at vise forhåndsvisningen + + + Tilføj adgangskodebeskyttelse + + + IsStructured + + + Sprog-id + + + Navn + + + Aktiver notifikationer? (nyttigt for at modtage opdateringer til appen) + + + Ny adgangskode + + + Bekræftelse af nyt password + + + Gammel adgangskode + + + Kun én gang + + + Adgangskode + + + Opfattelse + + + Opfattelser + + + Foretrækker systemtema + + + Kode for inddrivelse + + + Tilbagevendende + + + Scan efter: + + + Søg på + + + Deling af den hemmelige kode + + + Vis donator-badge + + + Vis fanen Logfiler for at støtte fejlfinding. + + + Tag type + + + Enkel + + + Struktureret + + + Temabillede + + + Mørk tilstand + + + Flåter + + + Titel + + + Årsag til opdatering + + + Indsend anonymt + + + Brugernavn + + + Brugernavn til at søge og tilføje + + + Brugernavne til at søge og tilføje + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.de.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.de.resx new file mode 100644 index 0000000..a3290e5 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.de.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (notwendig für die Wiederherstellung des Passworts) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">Passwortmanager</a> vorgeschlagen) + + + Betrag + + + Antwort id + + + Antwort-Text + + + Benutzerdefiniertes Zeichen (Emoji) + + + Farbe + + + Kommentar + + + Text kommentieren + + + Konfrontation + + + Konfrontation id + + + Kontext + + + Datum des Endes + + + Datum des Beginns + + + Beschreibung + + + Diskussion + + + Datum des Endes + + + Endzeit + + + Diskussion id + + + Text zur Diskussion + + + Titel der Diskussion + + + E-Mail + + + Emoji + + + Maximale Anzahl von Konfrontationen + + + Tatsache + + + Fakten + + + Datei-ID + + + Gruppen + + + Die erste Antwort in die Vorschau der Einladung aufnehmen? (um den Anreiz zur Teilnahme an der Diskussion zu erhöhen) + + + Einladungs-Code + + + Einladungscode oder Link + + + Passwort für die Einladung + + + Passwort für Vorschau erforderlich + + + Passwort erforderlich, um die Vorschau anzuzeigen + + + Passwortschutz hinzufügen + + + IsStructured + + + Sprache id + + + Name + + + Benachrichtigungen aktivieren?(nützlich, um App-Updates zu erhalten) + + + Neues Passwort + + + Bestätigung des neuen Passworts + + + Altes Passwort + + + Nur einmalig + + + Passwort + + + Wahrnehmung + + + Wahrnehmungen + + + Systemthema bevorzugen + + + Wiederherstellungscode + + + Wiederkehrende + + + Scannen Sie nach: + + + Suche + + + Gemeinsamer Geheimcode + + + Spenderausweis anzeigen + + + Zeigen Sie die Registerkarte Protokolle an, um die Fehlersuche zu unterstützen. + + + Typ nehmen + + + Einfach + + + Strukturiert + + + Thema Bild + + + Dunkler Modus + + + Zecken + + + Titel + + + Grund für die Aktualisierung + + + Anonym einreichen + + + Benutzername + + + Benutzername zum Suchen und Hinzufügen + + + Nutzernamen zum Suchen und Hinzufügen + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.el.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.el.resx new file mode 100644 index 0000000..a8be981 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.el.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (απαραίτητο σε περίπτωση ανάκτησης κωδικού πρόσβασης) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">διαχειριστής κωδικών πρόσβασης</a> προτείνεται) + + + Ποσό + + + Απάντηση id + + + Κείμενο απάντησης + + + Προσαρμοσμένος χαρακτήρας (emoji) + + + Χρώμα + + + Σχόλιο id + + + Κείμενο σχολιασμού + + + Αντιπαράθεση + + + id αντιπαράθεσης + + + Πλαίσιο + + + Ημερομηνία λήξης + + + Ημερομηνία έναρξης + + + Περιγραφή + + + Συζήτηση + + + Ημερομηνία λήξης + + + Ώρα λήξης + + + Συζήτηση id + + + Κείμενο συζήτησης + + + Τίτλος συζήτησης + + + Ηλεκτρονικό ταχυδρομείο + + + Emoji + + + Μέγιστος αριθμός αντιπαραθέσεων + + + Γεγονός + + + Γεγονότα + + + id αρχείου + + + Ομάδες + + + Να συμπεριλάβετε την πρώτη απάντηση στην προεπισκόπηση της πρόσκλησης; (καθιστώντας την πιο ελκυστική για να συμμετάσχετε στη συζήτηση) + + + Κωδικός πρόσκλησης + + + Κωδικός πρόσκλησης ή σύνδεσμος + + + Κωδικός πρόσκλησης + + + Απαίτηση κωδικού πρόσβασης για προεπισκόπηση + + + Απαιτείται κωδικός πρόσβασης για την εμφάνιση της προεπισκόπησης + + + Προσθήκη προστασίας με κωδικό πρόσβασης + + + IsStructured + + + Id γλώσσας + + + Όνομα + + + Ενεργοποίηση ειδοποιήσεων; (χρήσιμο για να λαμβάνετε ενημερώσεις εφαρμογών) + + + Νέος κωδικός πρόσβασης + + + Επιβεβαίωση νέου κωδικού πρόσβασης + + + Παλαιός κωδικός πρόσβασης + + + Μόνο μία φορά + + + Κωδικός πρόσβασης + + + Αντίληψη + + + Αντιλήψεις + + + Προτιμήστε το θέμα του συστήματος + + + Κωδικός ανάκτησης + + + Επαναλαμβανόμενο + + + Σάρωση για: + + + Αναζήτηση + + + Κοινή χρήση μυστικού κώδικα + + + Εμφάνιση σήματος δωρητή + + + Εμφάνιση της καρτέλας αρχείων καταγραφής για την υποστήριξη της αντιμετώπισης προβλημάτων. + + + Πάρτε τον τύπο + + + Απλό + + + Δομημένο + + + Εικόνα θέματος + + + Σκοτεινή λειτουργία + + + Κρότωνες + + + Τίτλος + + + Αιτία ενημέρωσης + + + Υποβάλετε ανώνυμα + + + Όνομα χρήστη + + + Όνομα χρήστη για αναζήτηση και προσθήκη + + + Ονόματα χρηστών για αναζήτηση και προσθήκη + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.es.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.es.resx new file mode 100644 index 0000000..58a58ce --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.es.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (necesario en caso de recuperación de la contraseña) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">gestor de contraseñas</a> sugerido) + + + Importe + + + Respuesta + + + Texto de respuesta + + + Carácter personalizado (emoji) + + + Color + + + Comentario id + + + Texto de los comentarios + + + Confrontación + + + Identificación de la confrontación + + + Contexto + + + Fecha de finalización + + + Fecha de inicio + + + Descripción + + + Discusión + + + Fecha de finalización + + + Hora de finalización + + + Discusión + + + Texto de debate + + + Título del debate + + + Envíe un correo electrónico a + + + Emoji + + + Máxima cantidad de enfrentamientos + + + Datos + + + Hechos + + + ID de archivo + + + Grupos + + + ¿Incluir la primera respuesta en la vista previa de la invitación? (para que sea más atractivo participar en el debate) + + + Código de invitación + + + Código o enlace de invitación + + + Contraseña de la invitación + + + Requerir contraseña para la vista previa + + + Requiere contraseña para mostrar la vista previa + + + Añadir protección por contraseña + + + IsStructured + + + Id. de idioma + + + Nombre + + + Activar las notificaciones (útiles para recibir las actualizaciones de la aplicación) + + + Nueva contraseña + + + Confirmación de la nueva contraseña + + + Contraseña antigua + + + Una sola vez + + + Contraseña + + + Percepción + + + Percepciones + + + Prefiero el tema del sistema + + + Código de recuperación + + + Recurrente + + + Busca: + + + Buscar en + + + Compartir el código secreto + + + Mostrar el distintivo de donante + + + Mostrar la pestaña de registros para apoyar la resolución de problemas. + + + Toma de tipo + + + Simple + + + Estructurado + + + Imagen del tema + + + Modo oscuro + + + Garrapatas + + + Título + + + Motivo de actualización + + + Presentar de forma anónima + + + Nombre de usuario + + + Nombre de usuario para buscar y añadir + + + Nombres de usuario para buscar y añadir + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.et.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.et.resx new file mode 100644 index 0000000..73beceb --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.et.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (vajalik parooli taastamise korral) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">Password manager</a> soovitatav) + + + Summa + + + Vastus id + + + Vastuse tekst + + + Kohandatud märk (emotikoni) + + + Värv + + + Kommentaar id + + + Kommentaaride tekst + + + Vastasseis + + + Vastasseisu id + + + Kontekst + + + Lõppkuupäev + + + Alguskuupäev + + + Kirjeldus + + + Arutelu + + + Lõppkuupäev + + + Lõpuaeg + + + Arutelu id + + + Arutelu tekst + + + Arutelu pealkiri + + + E-post + + + Emoji + + + Vastasseisude maksimaalne arv + + + Fakt + + + Faktid + + + Faili id + + + Rühmad + + + Lisage esimene vastus kutse eelvaates? (muudab aruteluga liitumise atraktiivsemaks) + + + Kutse kood + + + Kutse kood või link + + + Kutse parool + + + Nõuab eelvaate jaoks parooli + + + Eelvaate kuvamiseks nõutakse parooli + + + Lisage paroolikaitse + + + IsStructured + + + Keele id + + + Nimi + + + Teavituste lubamine?(kasulik, et saada rakenduse uuendusi) + + + Uus parool + + + Uue salasõna kinnitamine + + + Vana parool + + + Ainult üks kord + + + Parool + + + Tajumine + + + Arvamused + + + Eelistage süsteemi teemat + + + Taastamiskood + + + Korduvad + + + Skaneeri: + + + Otsi + + + Salajase koodi jagamine + + + Näita doonori märki + + + Näidake veaotsingu toetamiseks logide vahekaarti. + + + Võtke tüüp + + + Lihtne + + + Struktureeritud + + + Teema pilt + + + Tume režiim + + + Puugid + + + Pealkiri + + + Uuendamise põhjus + + + Esitage anonüümselt + + + Kasutajanimi + + + Kasutajanimi otsimiseks ja lisamiseks + + + Kasutajanimed otsimiseks ja lisamiseks + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.fi.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.fi.resx new file mode 100644 index 0000000..069a323 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.fi.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (tarvitaan salasanan palautuksen yhteydessä) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">salasananhallinta</a> ehdotettu) + + + Määrä + + + Vastaa id + + + Vastaus teksti + + + Mukautettu merkki (emoji) + + + Väri + + + Kommentti id + + + Kommenttiteksti + + + Vastakkainasettelu + + + Vastakkainasettelu id + + + Konteksti + + + Loppupäivä + + + Aloituspäivä + + + Kuvaus + + + Keskustelu + + + Loppupäivä + + + Loppuaika + + + Keskustelu id + + + Keskusteluteksti + + + Keskustelun otsikko + + + Sähköposti + + + Emoji + + + Vastakkainasettelujen enimmäismäärä + + + Fakta + + + Faktat + + + Tiedoston id + + + Ryhmät + + + Sisällytä ensimmäinen vastaus kutsun esikatseluun? (mikä tekee keskusteluun osallistumisesta houkuttelevampaa) + + + Kutsun koodi + + + Kutsukoodi tai linkki + + + Kutsun salasana + + + Vaadi salasana esikatselua varten + + + Vaadi salasana esikatselun näyttämiseksi + + + Lisää salasanasuojaus + + + IsStructured + + + Kielitunnus + + + Nimi + + + Ota ilmoitukset käyttöön?(hyödyllinen sovelluksen päivitysten saamiseksi) + + + Uusi salasana + + + Uuden salasanan vahvistus + + + Vanha salasana + + + Vain kerran + + + Salasana + + + Havainto + + + Käsitykset + + + Suositeltava järjestelmäteema + + + Palautuskoodi + + + Toistuvat + + + Etsi: + + + Etsi + + + Salaisen koodin jakaminen + + + Näytä lahjoittajan merkki + + + Näytä lokit-välilehti vianmäärityksen tueksi. + + + Ota tyyppi + + + Yksinkertainen + + + Strukturoitu + + + Aiheen kuva + + + Tumma tila + + + Punkit + + + Otsikko + + + Päivityksen syy + + + Lähetä nimettömänä + + + Käyttäjätunnus + + + Käyttäjätunnus hakuun ja lisäämiseen + + + Käyttäjätunnukset, joita voit etsiä ja lisätä + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.fr.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.fr.resx new file mode 100644 index 0000000..2adedb7 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.fr.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nécessaire en cas de récupération du mot de passe) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">gestionnaire de mots de passe</a> suggéré) + + + Montant + + + Réponse id + + + Texte de réponse + + + Caractère personnalisé (emoji) + + + Couleur + + + Commentaire + + + Texte du commentaire + + + Confrontation + + + Confrontation id + + + Contexte + + + Date de fin + + + Date de début + + + Description + + + Discussion + + + Date de fin + + + Heure de fin + + + ID de discussion + + + Texte de discussion + + + Titre de la discussion + + + Courriel + + + Emoji + + + Nombre maximal de confrontations + + + Fait + + + Faits et chiffres + + + Identifiant du fichier + + + Groupes + + + Inclure la première réponse dans l'aperçu de l'invitation ? (ce qui rend plus attrayant le fait de rejoindre la discussion) + + + Code d'invitation + + + Code ou lien d'invitation + + + Mot de passe de l'invitation + + + Exiger un mot de passe pour la prévisualisation + + + Exiger un mot de passe pour afficher l'aperçu + + + Ajouter une protection par mot de passe + + + EstStructuré + + + Identifiant de langue + + + Nom + + + Activer les notifications ? (utile pour recevoir les mises à jour de l'application) + + + Nouveau mot de passe + + + Confirmation du nouveau mot de passe + + + Ancien mot de passe + + + En une seule fois + + + Mot de passe + + + Perception + + + Perceptions + + + Préférer le thème du système + + + Code de récupération + + + Récurrent + + + Scanner pour : + + + Recherche + + + Partage du code secret + + + Afficher le badge de donateur + + + Affichez l'onglet des journaux pour faciliter le dépannage. + + + Type de prise + + + Simple + + + Structuré + + + Image du thème + + + Mode foncé + + + Tiques + + + Titre + + + Raison de la mise à jour + + + Soumettre anonymement + + + Nom d'utilisateur + + + Nom d'utilisateur pour rechercher et ajouter + + + Noms d'utilisateur à rechercher et à ajouter + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.hu.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.hu.resx new file mode 100644 index 0000000..5fb87eb --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.hu.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (jelszó helyreállítása esetén szükséges) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">jelszókezelő</a> javasolt) + + + Összeg + + + Válasz id + + + Válasz szöveg + + + Egyéni karakter (emoji) + + + Színes + + + Megjegyzés id + + + Megjegyzés szövege + + + Konfrontáció + + + Konfrontációs azonosító + + + Kontextus + + + Végdátum + + + Kezdeti időpont + + + Leírás + + + Megbeszélés + + + Végdátum + + + Végidő + + + Megbeszélés id + + + Megbeszélés szövege + + + Vita címe + + + E-mail + + + Emoji + + + A konfrontációk maximális száma + + + Tény + + + Tények + + + Fájl azonosító + + + Csoportok + + + Az első válasz szerepeljen a meghívó előnézetében? (így vonzóbbá teszi a vitához való csatlakozást) + + + Meghívó kód + + + Meghívó kód vagy link + + + Meghívó jelszó + + + Jelszó megkövetelése az előnézethez + + + Jelszó megkövetelése az előnézet megjelenítéséhez + + + Jelszóvédelem hozzáadása + + + IsStructured + + + Nyelvi azonosító + + + Név + + + Értesítések engedélyezése?(hasznos az alkalmazás frissítéseinek fogadásához) + + + Új jelszó + + + Új jelszó megerősítése + + + Régi jelszó + + + Csak egyszeri alkalom + + + Jelszó + + + Percepció + + + Perceptions + + + A rendszer témájának előnyben részesítése + + + Helyreállítási kód + + + Visszatérő + + + Keresés: + + + Keresés + + + Titkos kód megosztása + + + Adományozói jelvény megjelenítése + + + A naplók lap megjelenítése a hibaelhárítás támogatására. + + + Vegye a típust + + + Egyszerű + + + Strukturált + + + Témakép + + + Sötét üzemmód + + + Kullancsok + + + Cím + + + Frissítés oka + + + Névtelenül beküldeni + + + Felhasználónév + + + Felhasználónév kereséshez és hozzáadáshoz + + + Felhasználónevek keresése és hozzáadása + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.it.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.it.resx new file mode 100644 index 0000000..9415499 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.it.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (necessaria in caso di recupero password) + + + (è suggerito l'uso del <a class="is-underlined has-text-info" tabindex="-1" target="_blank" href="https://keepassxc.org/">gestore password</a>) + + + Importo + + + ID risposta + + + Testo risposta + + + Carattere personalizzato (emoji) + + + Colore + + + ID commento + + + Testo commento + + + Confronto + + + Id confronto + + + Contesto + + + Data di fine + + + Data di inizio + + + Descrizione + + + Discussione + + + Data di fine + + + Tempo di fine + + + ID discussione + + + Testo discussione + + + Titolo discussione + + + Email + + + Emoji + + + Quantità massima di scontri + + + Fatto + + + I fatti + + + ID file + + + Gruppi + + + Includere la prima risposta nell'anteprima dell'invito? (rendendo più coinvolgente l'adesione alla discussione) + + + Codice d'invito + + + Collegamento o codice di invito + + + Password dell'invito + + + Richiedi la password per l'anteprima + + + Richiedi la password per l'anteprima della discussione + + + Aggiungere la protezione con password + + + È strutturato + + + ID lingua + + + Nome + + + Abilitare le notifiche?(utili per percepire aggiornamenti) + + + Nuova password + + + Conferma nuova password + + + Attuale password + + + Paga una volta + + + Password + + + La percezione + + + Percezioni + + + Preferire il tema di sistema + + + Codice di recupero + + + Ricorrente + + + Scansione per: + + + Ricerca + + + Condivisione del codice segreto + + + Mostra il badge del donatore + + + Mostra il tab dei log a supporto della risoluzione dei problemi. + + + Prendi il tipo + + + Semplice + + + Strutturato + + + Immagine a tema + + + Modalità scura + + + Zecche + + + Titolo + + + Motivo dell'aggiornamento + + + Invia in forma anonima + + + Soprannome + + + Soprannome da ricercare e aggiungere + + + Soprannomi da ricercare e aggiungere + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.ja.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.ja.resx new file mode 100644 index 0000000..d12ddc9 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.ja.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (パスワード復旧時に必要) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">パスワードマネージャー</a>の提案) + + + 金額 + + + 回答ID + + + 回答テキスト + + + カスタムキャラクター(絵文字) + + + カラー + + + コメントID + + + コメント文 + + + 対決 + + + コンフロンティアID + + + コンテキスト + + + 終了日 + + + 開始日 + + + 商品説明 + + + ディスカッション + + + 終了日 + + + 終了時間 + + + ディスカッションID + + + ディスカッションテキスト + + + 議論のタイトル + + + 電子メール + + + 絵文字 + + + 対決の最大量 + + + 事実 + + + 事実 + + + ファイルID + + + グループ + + + 招待状のプレビューに最初の回答を含める?(議論への参加をより魅力的にするために) + + + 招待状コード + + + 招待コードまたはリンク + + + 招待状のパスワード + + + プレビューにパスワードが必要 + + + プレビューを表示するにはパスワードが必要です + + + パスワード保護機能の追加 + + + IsStructured + + + 言語ID + + + 名前 + + + 通知を有効にしますか?(アプリの更新情報を受け取るのに便利です) + + + 新しいパスワード + + + 新しいパスワードの確認 + + + 古いパスワード + + + 1回限り + + + パスワード + + + パーセプション + + + 認知度 + + + システムテーマを優先する + + + リカバリーコード + + + リカーリング + + + スキャンしてください: + + + 検索 + + + 秘密の暗号を共有する + + + 寄付者バッジを表示する + + + トラブルシューティングをサポートするために、ログタブを表示します。 + + + テイクタイプ + + + シンプル + + + 構造化 + + + テーマイメージ + + + ダークモード + + + ダニ + + + タイトル + + + 更新理由 + + + 匿名で投稿する + + + ユーザー名 + + + 検索して追加するユーザー名 + + + 検索して追加するユーザー名 + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.lt.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.lt.resx new file mode 100644 index 0000000..f4c2554 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.lt.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (būtina slaptažodžio atkūrimo atveju) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">paslapčių tvarkyklė</a> siūloma) + + + Suma + + + Atsakymas id + + + Atsakymo tekstas + + + Pasirinktinis simbolis (emoji) + + + Spalva + + + Komentaras id + + + Komentaro tekstas + + + Konfrontacija + + + Konfrontacijos id + + + Kontekstas + + + Galutinė data + + + Pradžios data + + + Aprašymas + + + Diskusija + + + Galutinė data + + + Pabaigos laikas + + + Diskusijos id + + + Diskusijos tekstas + + + Diskusijos pavadinimas + + + El. paštas + + + Emoji + + + Didžiausias konfrontacijų skaičius + + + Faktas + + + Faktai + + + Failo ID + + + Grupės + + + Įtraukti pirmąjį atsakymą į kvietimo peržiūrą? (kad būtų patraukliau prisijungti prie diskusijos) + + + Kvietimo kodas + + + Kvietimo kodas arba nuoroda + + + Kvietimo slaptažodis + + + Reikalauti slaptažodžio peržiūrai + + + Reikalauti slaptažodžio, kad būtų rodoma peržiūra + + + Pridėti apsaugą slaptažodžiu + + + IsStructured + + + Kalbos ID + + + Pavadinimas + + + Įjungti pranešimus? (naudinga norint gauti programėlės atnaujinimus) + + + Naujas slaptažodis + + + Naujo slaptažodžio patvirtinimas + + + Senasis slaptažodis + + + Tik vieną kartą + + + Slaptažodis + + + Suvokimas + + + Suvokimas + + + Pirmenybė sistemos temai + + + Atkūrimo kodas + + + Pasikartojantis + + + Ieškoti: + + + Paieška + + + Dalijimasis slaptuoju kodu + + + Rodyti rėmėjo ženklelį + + + Parodykite skirtuką "Žurnalai", kad galėtumėte padėti šalinti trikdžius. + + + Paimkite tipą + + + Paprastas + + + Struktūrinis + + + Temos vaizdas + + + Tamsus režimas + + + Erkės + + + Pavadinimas + + + Atnaujinimo priežastis + + + Pateikti anonimiškai + + + Vartotojo vardas + + + Vartotojo vardas paieškai ir pridėjimui + + + Vartotojų vardų paieška ir pridėjimas + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.lv.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.lv.resx new file mode 100644 index 0000000..2430ee0 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.lv.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nepieciešams paroles atjaunošanas gadījumā) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">vārdu pārvaldnieks</a> ierosināts) + + + Summa + + + Atbilde id + + + Atbildes teksts + + + Pielāgots raksturs (emodži) + + + Krāsa + + + Komentārs id + + + Komentāru teksts + + + Konfrontācija + + + Konfrontācijas id + + + Konteksts + + + Beigu datums + + + Sākuma datums + + + Apraksts + + + Diskusija + + + Beigu datums + + + Beigu laiks + + + Diskusijas id + + + Diskusijas teksts + + + Diskusijas nosaukums + + + E-pasts + + + Emocijas + + + Maksimālais konfrontāciju skaits + + + Fakti + + + Fakti + + + Faila id + + + Grupas + + + Iekļaut pirmo atbildi uzaicinājuma priekšskatījumā? (lai iesaistīšanās diskusijā būtu saistošāka) + + + Uzaicinājuma kods + + + Uzaicinājuma kods vai saite + + + Uzaicinājuma parole + + + Pieprasīt paroli priekšskatīšanai + + + Pieprasīt paroli, lai parādītu priekšskatījumu + + + Pievienojiet paroles aizsardzību + + + IsStructured + + + Valodas identifikators + + + Nosaukums + + + Ieslēgt paziņojumus?(noderīgi, lai saņemtu lietotnes atjauninājumus) + + + Jauna parole + + + Jaunas paroles apstiprināšana + + + Vecā parole + + + Tikai vienu reizi + + + Parole + + + Uztvere + + + Uzskati + + + Dodiet priekšroku sistēmas tēmai + + + Atgūšanas kods + + + Atkārtoti + + + Skenēt, lai atrastu: + + + Meklēšana + + + Koplietošanas slepenais kods + + + Rādīt ziedotāja nozīmīti + + + Parādiet cilni žurnāli, lai atbalstītu problēmu novēršanu. + + + Veikt veidu + + + Vienkāršs + + + Strukturēts + + + Tēmas attēls + + + Tumšais režīms + + + Ērces + + + Nosaukums + + + Atjaunināšanas iemesls + + + Iesniedziet anonīmi + + + Lietotājvārds + + + Lietotājvārds, lai meklētu un pievienotu + + + Meklēšanas un pievienošanas lietotājvārdi + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.nl.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.nl.resx new file mode 100644 index 0000000..766bcb8 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.nl.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nodig in geval van wachtwoordherstel) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">wachtwoord manager</a> voorgesteld) + + + Bedrag + + + Antwoord id + + + Antwoord tekst + + + Aangepast karakter (emoji) + + + Kleur + + + Commentaar id + + + Commentaar tekst + + + Confrontatie + + + Confrontatie id + + + Context + + + Einddatum + + + Startdatum + + + Beschrijving + + + Discussie + + + Einddatum + + + Eindtijd + + + Discussie id + + + Besprekingstekst + + + Titel van de discussie + + + E-mail + + + Emoji + + + Maximum aantal confrontaties + + + Feit + + + Feiten + + + Bestand id + + + Groepen + + + Het eerste antwoord opnemen in de preview van de uitnodiging? (maakt het aantrekkelijker om deel te nemen aan de discussie) + + + Uitnodiging code + + + Uitnodigingscode of link + + + Uitnodiging wachtwoord + + + Wachtwoord nodig voor preview + + + Wachtwoord vereist om de preview te tonen + + + Wachtwoordbeveiliging toevoegen + + + IsStructured + + + Taal id + + + Naam + + + Meldingen inschakelen?(handig om app updates te ontvangen) + + + Nieuw wachtwoord + + + Bevestiging nieuw wachtwoord + + + Oud wachtwoord + + + Eenmalig + + + Wachtwoord + + + Perceptie + + + Percepties + + + Voorkeur systeemthema + + + Herstelcode + + + Terugkerend + + + Scan voor: + + + Zoeken op + + + Het delen van de geheime code + + + Toon donateur badge + + + Toon de logs tab ter ondersteuning van troubleshooting. + + + Neem type + + + Eenvoudig + + + Gestructureerd + + + Thema-afbeelding + + + Donkere modus + + + Teken + + + Titel + + + Update reden + + + Anoniem indienen + + + Gebruikersnaam + + + Gebruikersnaam om te zoeken en toe te voegen + + + Gebruikersnamen om te zoeken en toe te voegen + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.pl.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.pl.resx new file mode 100644 index 0000000..d8eeeb3 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.pl.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (konieczne w przypadku odzyskiwania hasła) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">password manager</a> sugerowane) + + + Kwota + + + Odpowiedź + + + Odpowiedź + + + Znak niestandardowy (emoji) + + + Kolor + + + Komentarz id + + + Tekst komentarza + + + Konfrontacja + + + Konfrontacja id + + + Kontekst + + + Data końcowa + + + Data rozpoczęcia + + + Opis + + + Dyskusja + + + Data końcowa + + + Czas zakończenia + + + Dyskusja id + + + Tekst do dyskusji + + + Tytuł dyskusji + + + E-mail + + + Emoji + + + Maksymalna ilość konfrontacji + + + Fakt + + + Fakty + + + Id pliku + + + Grupy + + + Zamieścić pierwszą odpowiedź w podglądzie zaproszenia? (sprawia, że dołączenie do dyskusji jest bardziej zachęcające) + + + Kod zaproszenia + + + Kod lub link do zaproszenia + + + Hasło do zaproszenia + + + Wymagaj hasła do podglądu + + + Wymagaj hasła, aby wyświetlić podgląd + + + Dodaj ochronę hasłem + + + IsStructured + + + Język id + + + Nazwa + + + Włączyć powiadomienia? (przydatne do otrzymywania aktualizacji aplikacji) + + + Nowe hasło + + + Potwierdzenie nowego hasła + + + Stare hasło + + + Tylko jeden raz + + + Hasło + + + Percepcja + + + Spostrzeżenia + + + Preferowany motyw systemowy + + + Kod odzyskiwania + + + Powtarzające się + + + Skanuj dla: + + + Szukaj + + + Dzielenie się tajnym kodem + + + Pokaż odznakę darczyńcy + + + Pokaż kartę logów, aby ułatwić rozwiązywanie problemów. + + + Rodzaj ujęcia + + + Proste + + + Strukturalne + + + Obrazek tematyczny + + + Tryb ciemny + + + Kleszcze + + + Tytuł + + + Powód aktualizacji + + + Zgłoś się anonimowo + + + Nazwa użytkownika + + + Nazwa użytkownika do wyszukiwania i dodawania + + + Nazwy użytkowników do wyszukiwania i dodawania + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.pt.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.pt.resx new file mode 100644 index 0000000..f9f2369 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.pt.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (necessário em caso de recuperação de senha) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">gerenciador de palavras-passe</a> sugerido) + + + Montante + + + Responder id + + + Responder texto + + + Personalidade(emoji) + + + Cor + + + Comentar id + + + Comentar texto + + + Confrontação + + + Id de confronto + + + Contexto + + + Data final + + + Data de início + + + Descrição + + + Discussão + + + Data final + + + Tempo final + + + Id de discussão + + + Texto para discussão + + + Título do debate + + + Email + + + Emoji + + + Quantidade máxima de confrontos + + + Facto + + + Fatos + + + Identificação do arquivo + + + Grupos + + + Incluir a primeira resposta na pré-visualização do convite? (tornando-a mais envolvente para participar da discussão) + + + Código de convite + + + Código do convite ou link + + + Senha do convite + + + Exigir senha para visualização + + + Exigir senha para mostrar a visualização + + + Adicionar proteção por senha + + + IsStructured + + + Identificação do idioma + + + Nome + + + Habilitar notificações?(útil para receber atualizações de aplicativos) + + + Nova senha + + + Confirmação de nova senha + + + Senha antiga + + + Só uma vez + + + Senha + + + Percepção + + + Percepções + + + Tema do sistema de preferências + + + Código de recuperação + + + Recurrente + + + Procura por: + + + Pesquisa + + + Partilha do Código Secreto + + + Mostrar crachá do doador + + + Mostrar o separador de registos para suportar a resolução de problemas. + + + Tirar tipo + + + Simples + + + Estruturado + + + Imagem temática + + + Modo escuro + + + Carraças + + + Título + + + Motivo da actualização + + + Submeter de forma anónima + + + Nome de usuário + + + Nome de usuário para pesquisar e adicionar + + + Nomes de usuário para pesquisar e adicionar + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.resx new file mode 100644 index 0000000..efa52bd --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (necessary in case of password recovery) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="_blank" href="https://keepassxc.org/">password manager</a> suggested) + + + Amount + + + Answer id + + + Answer text + + + Custom character(emoji) + + + Colour + + + Comment id + + + Comment text + + + Confrontation + + + Confrontation id + + + Context + + + End date + + + Start date + + + Description + + + Discussion + + + End date + + + End time + + + Discussion id + + + Discussion text + + + Discussion title + + + Email + + + Emoji + + + Max amount of confrontations + + + Fact + + + Facts + + + File id + + + Groups + + + Include the first answer in the invitation preview? (making it more engaging to join the discussion) + + + Invitation code + + + Invitation code or link + + + Invitation password + + + Require password for preview + + + Require password to show the preview + + + Add password protection + + + IsStructured + + + Language id + + + Name + + + Enable notifications?(useful to receive app updates) + + + New password + + + New password confirmation + + + Old password + + + One time only + + + Password + + + Perception + + + Perceptions + + + Prefer system theme + + + Recovery code + + + Recurring + + + Scan for: + + + Search + + + Sharing Secret Code + + + Show donator badge + + + Show the logs tab to support troubleshooting. + + + Take type + + + Simple + + + Structured + + + Theme image + + + Dark mode + + + Ticks + + + Title + + + Update reason + + + Submit anonymously + + + Username + + + Username to search and add + + + Usernames to search and add + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.ro.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.ro.resx new file mode 100644 index 0000000..0a79425 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.ro.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (necesar în cazul recuperării parolei) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">manager de parole</a> sugerat) + + + Suma + + + Răspuns id + + + Text de răspuns + + + Caracter personalizat (emoji) + + + Culoare + + + Comentariu id + + + Comentariu text + + + Confruntare + + + Id de confruntare + + + Context + + + Data de încheiere + + + Data de începere + + + Descriere + + + Discuție + + + Data de încheiere + + + Ora de sfârșit + + + Discuție id + + + Text de discuție + + + Titlul discuției + + + Email + + + Emoji + + + Numărul maxim de confruntări + + + Fapt + + + Fapte + + + ID fișier + + + Grupuri + + + Includeți primul răspuns în previzualizarea invitației? (pentru a face mai atractivă participarea la discuție) + + + Codul de invitație + + + Cod sau link de invitație + + + Parola de invitație + + + Necesită o parolă pentru previzualizare + + + Necesită o parolă pentru a afișa previzualizarea + + + Adăugați protecție prin parolă + + + IsStructured + + + ID-ul limbii + + + Nume + + + Activați notificările?(util pentru a primi actualizări ale aplicației) + + + Parolă nouă + + + Confirmarea noii parole + + + Parolă veche + + + O singură dată + + + Parola + + + Percepție + + + Percepții + + + Preferați tema de sistem + + + Cod de recuperare + + + Recurent + + + Scanează pentru: + + + Căutare + + + Partajarea codului secret + + + Arată insigna de donator + + + Afișați fila jurnale pentru a sprijini depanarea. + + + Tipul de luare + + + Simplu + + + Structurat + + + Imagine tematică + + + Mod întunecat + + + Căpușe + + + Titlu + + + Motivul actualizării + + + Trimiteți în mod anonim + + + Nume utilizator + + + Nume de utilizator pentru a căuta și adăuga + + + Nume de utilizator pentru căutare și adăugare + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.ru.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.ru.resx new file mode 100644 index 0000000..967227e --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.ru.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (необходимо в случае восстановления пароля) + + + (Предлагается <a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">password manager</a>) + + + Сумма + + + Идентификатор ответа + + + Текст ответа + + + Пользовательский символ (эмодзи) + + + Цвет + + + Идентификатор комментария + + + Текст комментария + + + Противостояние + + + Идентификатор противостояния + + + Контекст + + + Дата окончания + + + Дата начала + + + Описание + + + Обсуждение + + + Дата окончания + + + Время окончания + + + Обсуждаемый иди + + + Текст для обсуждения + + + Название дискуссии + + + Электронная почта + + + Emoji + + + Максимальное количество противостояний + + + Факт + + + Факты + + + Идентификатор файла + + + Группы + + + Включить первый ответ в предварительный просмотр приглашения? (что делает его более привлекательным для участия в обсуждении) + + + Код приглашения + + + Код приглашения или ссылка + + + Пароль приглашения + + + Требуется пароль для предварительного просмотра + + + Требуется пароль для показа предварительного просмотра + + + Добавить защиту паролем + + + IsStructured + + + Языковой идентификатор + + + Имя + + + Включить уведомления? (полезно для получения обновлений приложения) + + + Новый пароль + + + Подтверждение нового пароля + + + Старый пароль + + + Только один раз + + + Пароль + + + Восприятие + + + Восприятие + + + Предпочитайте системную тему + + + Код восстановления + + + Повторяющийся + + + Сканирование для: + + + Поиск + + + Обмен секретным кодом + + + Показать значок жертвователя + + + Покажите вкладку журналов для поддержки поиска и устранения неисправностей. + + + Возьмите тип + + + Простой + + + Структурированный + + + Тематическое изображение + + + Темный режим + + + Клещи + + + Название + + + Причина обновления + + + Подавать анонимно + + + Имя пользователя + + + Имя пользователя для поиска и добавления + + + Имена пользователей для поиска и добавления + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.sk.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.sk.resx new file mode 100644 index 0000000..8b2d898 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.sk.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (potrebné v prípade obnovenia hesla) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">správca hesiel</a> navrhnuté) + + + Suma + + + Odpoveď id + + + Text odpovede + + + Vlastný znak (emoji) + + + Farba + + + Komentár id + + + Text komentára + + + Konfrontácia + + + Id konfrontácie + + + Kontext + + + Dátum ukončenia + + + Dátum začiatku + + + Popis + + + Diskusia + + + Dátum ukončenia + + + Čas ukončenia + + + Diskusia id + + + Text diskusie + + + Názov diskusie + + + E-mail + + + Emoji + + + Maximálny počet konfrontácií + + + Skutočnosť + + + Fakty + + + Identifikátor súboru + + + Skupiny + + + Zahrnúť prvú odpoveď do náhľadu pozvánky? (aby bolo zapojenie do diskusie pútavejšie) + + + Pozývací kód + + + Pozývací kód alebo odkaz + + + Heslo pozvánky + + + Vyžadovať heslo pre náhľad + + + Vyžadovať heslo na zobrazenie náhľadu + + + Pridanie ochrany heslom + + + IsStructured + + + Identifikátor jazyka + + + Názov + + + Povolenie oznámení?(užitočné na prijímanie aktualizácií aplikácie) + + + Nové heslo + + + Potvrdenie nového hesla + + + Staré heslo + + + Iba jedenkrát + + + Heslo + + + Vnímanie + + + Vnímanie + + + Uprednostniť tému systému + + + Kód obnovy + + + Opakujúce sa + + + Vyhľadávanie: + + + Vyhľadávanie + + + Zdieľanie tajného kódu + + + Zobraziť odznak darcu + + + Zobrazenie karty protokolov na podporu riešenia problémov. + + + Vezmite si typ + + + Jednoduché + + + Štruktúrované + + + Obrázok témy + + + Tmavý režim + + + Kliešte + + + Názov + + + Dôvod aktualizácie + + + Odoslať anonymne + + + Používateľské meno + + + Používateľské meno na vyhľadávanie a pridávanie + + + Používateľské mená na vyhľadávanie a pridávanie + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.sl.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.sl.resx new file mode 100644 index 0000000..f14f013 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.sl.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (potrebno v primeru obnovitve gesla) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">predlagani upravitelj gesel</a>) + + + Znesek + + + Odgovor id + + + Besedilo odgovora + + + Znak po meri (emoji) + + + Barva + + + Id komentarja + + + Besedilo komentarja + + + Konfrontacija + + + Id konfrontacije + + + Kontekst + + + Končni datum + + + Datum začetka + + + Opis + + + Razprava + + + Končni datum + + + Končni čas + + + Razprava id + + + Besedilo za razpravo + + + Naslov razprave + + + E-pošta + + + Emoji + + + Največje število soočenj + + + Dejstva + + + Dejstva + + + Id datoteke + + + Skupine + + + Vključiti prvi odgovor v predogled vabila? (da bo vključitev v razpravo bolj privlačna) + + + Koda vabila + + + Koda vabila ali povezava + + + Geslo za povabilo + + + Zahtevajte geslo za predogled + + + Zahtevajte geslo za prikaz predogleda + + + Dodajanje zaščite z geslom + + + IsStructured + + + Id jezika + + + Ime + + + Omogočite obvestila? (koristno za prejemanje posodobitev aplikacije) + + + Novo geslo + + + Potrditev novega gesla + + + Staro geslo + + + Samo enkrat + + + Geslo + + + Zaznavanje + + + Dojemanje + + + Prednost sistemske teme + + + Koda za obnovitev + + + Ponavljajoče se + + + Iskanje za: + + + Iskanje + + + Deljenje skrivne kode + + + Prikaži značko donatorja + + + Prikažite zavihek dnevniki za podporo pri odpravljanju težav. + + + Vrsta posnetka + + + Enostavno + + + Strukturiran + + + Slika teme + + + Temni način + + + Klopi + + + Naslov + + + Razlog za posodobitev + + + Pošlji anonimno + + + Uporabniško ime + + + Uporabniško ime za iskanje in dodajanje + + + Uporabniška imena za iskanje in dodajanje + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.sv.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.sv.resx new file mode 100644 index 0000000..68eafc7 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.sv.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (nödvändig vid återställning av lösenord) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">password manager</a> föreslås) + + + Belopp + + + Svar id + + + Svarstext + + + Anpassat tecken (emoji) + + + Färg + + + Kommentar id + + + Kommentar + + + Konfrontation + + + Konfrontation id + + + Kontext + + + Slutdatum + + + Startdatum + + + Beskrivning + + + Diskussion + + + Slutdatum + + + Sluttid + + + Diskussion id + + + Diskussionstext + + + Titel på diskussionen + + + E-post + + + Emoji + + + Max antal konfrontationer + + + Fakta + + + Fakta + + + Filid + + + Grupper + + + Inkludera det första svaret i förhandsgranskningen av inbjudan? (för att göra det mer lockande att delta i diskussionen) + + + Kod för inbjudan + + + Inbjudningskod eller länk + + + Lösenord för inbjudan + + + Kräver lösenord för förhandsgranskning + + + Kräver lösenord för att visa förhandsgranskningen + + + Lägg till lösenordsskydd + + + IsStructured + + + Språk-id + + + Namn + + + Aktivera meddelanden (användbart för att få uppdateringar av appen) + + + Nytt lösenord + + + Bekräftelse av nytt lösenord + + + Gammalt lösenord + + + Endast en gång + + + Lösenord + + + Uppfattning + + + Uppfattningar + + + Föredrar systemtema + + + Kod för återvinning + + + Återkommande + + + Skanna efter: + + + Sök på + + + Att dela med sig av den hemliga koden + + + Visa donatormärket + + + Visa fliken Loggar för att underlätta felsökning. + + + Typ av tagning + + + Enkelt + + + Strukturerad + + + Tema bild + + + Mörkt läge + + + Fästingar + + + Titel + + + Orsak till uppdatering + + + Skicka in anonymt + + + Användarnamn + + + Användarnamn för att söka och lägga till + + + Användarnamn att söka och lägga till + + \ No newline at end of file diff --git a/SocialPub.ClientModels/Resources/FieldsNameResource.zh.resx b/SocialPub.ClientModels/Resources/FieldsNameResource.zh.resx new file mode 100644 index 0000000..93ce9c9 --- /dev/null +++ b/SocialPub.ClientModels/Resources/FieldsNameResource.zh.resx @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + (在恢复密码的情况下是必要的) + + + (<a class="is-underlined has-text-info" tabindex="-1" target="blank" href="https://keepassxc.org/">密码管理器</a>建议) + + + 数量 + + + 答案是 + + + 回答文本 + + + 自定义字符(表情符号) + + + 颜色 + + + 评论编号 + + + 评论文本 + + + 对抗 + + + 对抗的身份 + + + 背景介绍 + + + 结束日期 + + + 开始日期 + + + 描述 + + + 讨论 + + + 结束日期 + + + 结束时间 + + + 讨论的内容 + + + 讨论文本 + + + 讨论题目 + + + 电子邮件 + + + 表情符号 + + + 对抗的最大数量 + + + 事实 + + + 事实 + + + 文件编号 + + + 群体 + + + 在邀请预览中包括第一个答案?(使其更加吸引人加入讨论) + + + 邀请函代码 + + + 邀请函代码或链接 + + + 邀请函密码 + + + 预览时需要密码 + + + 需要密码才能显示预览 + + + 添加密码保护 + + + 是结构化的 + + + 语言标识 + + + 命名 + + + 启用通知?(对接收应用程序的更新很有用) + + + 新密码 + + + 确认新密码 + + + 旧密码 + + + 仅限一次 + + + 密码 + + + 感知 + + + 观念 + + + 倾向于系统主题 + + + 恢复代码 + + + 经常性的 + + + 进行扫描: + + + 搜索 + + + 分享秘密代码 + + + 显示捐赠者徽章 + + + 显示日志标签以支持故障排除。 + + + 采取类型 + + + 简单 + + + 结构化 + + + 主题图片 + + + 黑暗模式 + + + 蜱虫 + + + 标题 + + + 更新原因 + + + 匿名提交 + + + 帐号 + + + 搜索和添加的用户名 + + + 搜索和添加的用户名 + + \ No newline at end of file diff --git a/SocialPub.ClientModels/SocialPub.ClientModels.csproj b/SocialPub.ClientModels/SocialPub.ClientModels.csproj new file mode 100644 index 0000000..53b2676 --- /dev/null +++ b/SocialPub.ClientModels/SocialPub.ClientModels.csproj @@ -0,0 +1,33 @@ + + + + net7.0 + enable + + + + + + True + True + ErrorsResource.resx + + + True + True + FieldsNameResource.resx + + + + + + PublicResXFileCodeGenerator + ErrorsResource.Designer.cs + + + PublicResXFileCodeGenerator + FieldsNameResource.Designer.cs + + + + diff --git a/SocialPub.ClientModels/User/Avatar/InsertAvatarForm.cs b/SocialPub.ClientModels/User/Avatar/InsertAvatarForm.cs new file mode 100644 index 0000000..d778bdc --- /dev/null +++ b/SocialPub.ClientModels/User/Avatar/InsertAvatarForm.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace SocialPub.ClientModels.User.Avatar +{ + [JsonSerializable(typeof(InsertAvatarForm))] + public class InsertAvatarForm + { + } +} diff --git a/SocialPub.ClientModels/User/Avatar/UpdateAvatarForm.cs b/SocialPub.ClientModels/User/Avatar/UpdateAvatarForm.cs new file mode 100644 index 0000000..fe7fc4f --- /dev/null +++ b/SocialPub.ClientModels/User/Avatar/UpdateAvatarForm.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace SocialPub.ClientModels.User.Avatar +{ + [JsonSerializable(typeof(UpdateAvatarForm))] + public class UpdateAvatarForm + { + } +} diff --git a/SocialPub.ClientModels/User/Avatar/UpdateAvatarSettings.cs b/SocialPub.ClientModels/User/Avatar/UpdateAvatarSettings.cs new file mode 100644 index 0000000..a43b9ab --- /dev/null +++ b/SocialPub.ClientModels/User/Avatar/UpdateAvatarSettings.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace SocialPub.ClientModels.User.Avatar +{ + [JsonSerializable(typeof(UpdateAvatarSettings))] + public class UpdateAvatarSettings + { + } +} diff --git a/SocialPub.ClientModels/User/Avatar/ViewAvatar.cs b/SocialPub.ClientModels/User/Avatar/ViewAvatar.cs new file mode 100644 index 0000000..5e5eada --- /dev/null +++ b/SocialPub.ClientModels/User/Avatar/ViewAvatar.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace SocialPub.ClientModels.User.Avatar +{ + [JsonSerializable(typeof(ViewAvatar))] + public class ViewAvatar + { + + public ViewAvatarSettings Settings { get; set; } + } +} diff --git a/SocialPub.ClientModels/User/Avatar/ViewAvatarSettings.cs b/SocialPub.ClientModels/User/Avatar/ViewAvatarSettings.cs new file mode 100644 index 0000000..2085f57 --- /dev/null +++ b/SocialPub.ClientModels/User/Avatar/ViewAvatarSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SocialPub.ClientModels.User.Avatar +{ + public class ViewAvatarSettings + { + } +} diff --git a/SocialPub.ClientModels/User/JwtUser.cs b/SocialPub.ClientModels/User/JwtUser.cs new file mode 100644 index 0000000..1062f1e --- /dev/null +++ b/SocialPub.ClientModels/User/JwtUser.cs @@ -0,0 +1,13 @@ +namespace SocialPub.ClientModels.User +{ + public class JwtUser + { + public string UserId { get; set; } + public string Username { get; set; } + public string Email { get; set; } + public List Policies { get; set; } = new(); + public string Token { get; set; } + public long Expiration { get; set; } + public ViewUserSettings UserSettings { get; set; } = new(); + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/User/UserForm.cs b/SocialPub.ClientModels/User/UserForm.cs new file mode 100644 index 0000000..e1d4eae --- /dev/null +++ b/SocialPub.ClientModels/User/UserForm.cs @@ -0,0 +1,40 @@ +using SocialPub.ClientModels.Resources; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels.User +{ + public class UserForm + { + [EmailAddress(ErrorMessageResourceName = "InvalidEmail", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = "Email", ResourceType = typeof(FieldsNameResource))] + public string Email { get; set; } + } + + public class UserPasswordForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Display(Name = "OldPassword", ResourceType = typeof(FieldsNameResource)), + StringLength(Constants.MaxPasswordLength, MinimumLength = Constants.MinPasswordLength, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + RegularExpression(Constants.PasswordRegex, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource))] + public string OldPassword { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Display(Name = "NewPassword", ResourceType = typeof(FieldsNameResource)), + StringLength(Constants.MaxPasswordLength, MinimumLength = Constants.MinPasswordLength, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + RegularExpression(Constants.PasswordRegex, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource))] + public string NewPassword { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + DataType(DataType.Password, ErrorMessageResourceName = "InvalidPassword", ErrorMessageResourceType = typeof(ErrorsResource)), + PasswordPropertyText(true), + Compare(nameof(NewPassword), ErrorMessageResourceName = "ComparePasswords", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = "NewPasswordConfirmation", ResourceType = typeof(FieldsNameResource))] + public string RepeatedPassword { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/User/UsersIds.cs b/SocialPub.ClientModels/User/UsersIds.cs new file mode 100644 index 0000000..862b76d --- /dev/null +++ b/SocialPub.ClientModels/User/UsersIds.cs @@ -0,0 +1,7 @@ +namespace SocialPub.ClientModels.User +{ + public class UsersIds + { + public IList UserIdList { get; set; } = Array.Empty(); + } +} diff --git a/SocialPub.ClientModels/User/ViewUserSettings.cs b/SocialPub.ClientModels/User/ViewUserSettings.cs new file mode 100644 index 0000000..40bc0c2 --- /dev/null +++ b/SocialPub.ClientModels/User/ViewUserSettings.cs @@ -0,0 +1,25 @@ +using SocialPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels.User +{ + public class ViewUserSettings + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = nameof(LanguageCode), ResourceType = typeof(FieldsNameResource))] + public string LanguageCode { get; set; } = "en"; + + [Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))] + public short LightThemeIndexColour { get; set; } = 25; + [Range(0, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))] + public short DarkThemeIndexColour { get; set; } = 215; + [Range(-2, 359, ErrorMessageResourceName = nameof(Range), ErrorMessageResourceType = typeof(ErrorsResource))] + public short IconsThemeIndexColour { get; set; } = 25; + + public bool PreferSystemTheming { get; set; } = false; + public bool ThemeIsDarkMode { get; set; } = false; + public bool ThemeIsLightGray { get; set; } = false; + public bool ThemeIsDarkGray { get; set; } = false; + } +} \ No newline at end of file diff --git a/SocialPub.ClientModels/ValidatorAttributes/NoWhiteSpacesAttribute.cs b/SocialPub.ClientModels/ValidatorAttributes/NoWhiteSpacesAttribute.cs new file mode 100644 index 0000000..7dfa2aa --- /dev/null +++ b/SocialPub.ClientModels/ValidatorAttributes/NoWhiteSpacesAttribute.cs @@ -0,0 +1,60 @@ +using SocialPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.ClientModels.ValidatorAttributes +{ + [AttributeUsage(AttributeTargets.Property)] + public class NoWhiteSpacesAttribute : ValidationAttribute + { + const string errorMessageResourceName = "EmptySpacesNotAllowed"; + readonly Type errorMessageResourceType = typeof(ErrorsResource); + readonly string[] spaces = new[] { + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + }; + + public NoWhiteSpacesAttribute() + { + ErrorMessageResourceName = errorMessageResourceName; + ErrorMessageResourceType = errorMessageResourceType; + } + + public override bool IsValid(object value) + { + var str = value?.ToString(); + if (string.IsNullOrEmpty(str)) + return true; + + var hasWhiteSpace = false; + try + { + foreach (var space in spaces) + { + hasWhiteSpace = str.Contains(space); + if (hasWhiteSpace) break; + } + + return !hasWhiteSpace; + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + return false; + } + } + } +} diff --git a/SocialPub.ClientModels/WebResult.cs b/SocialPub.ClientModels/WebResult.cs new file mode 100644 index 0000000..86a35b2 --- /dev/null +++ b/SocialPub.ClientModels/WebResult.cs @@ -0,0 +1,59 @@ +using System.Net; +using System.Text.Json.Serialization; + +namespace SocialPub.ClientModels +{ + public class WebResult + { + public WebResult Invalidate(string errorMessage, + int statusCode = (int)HttpStatusCode.BadRequest, + string errorCode = "4000", + Exception exception = null, + T defaultData = default) + { + IsValid = false; + ErrorMessage += errorMessage; + StatusCode = statusCode; + ErrorCode = errorCode; + Exception = exception; + Data = defaultData; + return this; + } + + public WebResult Invalidate(string errorMessage, + int statusCode = (int)HttpStatusCode.BadRequest, + string errorCode = "4000", + Exception exception = null) + { + IsValid = false; + ErrorMessage += errorMessage; + StatusCode = statusCode; + ErrorCode = errorCode; + Exception = exception; + return this; + } + + public WebResult Invalidate(WebResult result) + { + IsValid = result.IsValid; + ErrorMessage += result.ErrorMessage; + StatusCode = result.StatusCode; + ErrorCode = result.ErrorCode; + Exception = result.Exception; + Data = result.Data; + return this; + } + + public string ErrorCode { get; set; } = "0000"; + public int StatusCode { get; set; } = (int)HttpStatusCode.OK; + public string ErrorMessage { get; set; } + + public bool IsValid { get; set; } = true; + + [JsonIgnore] + public object Data { get; set; } + + [JsonIgnore] + public Exception Exception { get; set; } + } +} diff --git a/SocialPub.sln b/SocialPub.sln new file mode 100644 index 0000000..aa63329 --- /dev/null +++ b/SocialPub.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33122.133 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocialPub", "SocialPub\SocialPub.csproj", "{EB2A0BD2-0150-405A-939E-5B0F6F642539}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocialPub.ClientModels", "SocialPub.ClientModels\SocialPub.ClientModels.csproj", "{5E63C68C-0E60-4418-A53A-FF4EF0007080}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EB2A0BD2-0150-405A-939E-5B0F6F642539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB2A0BD2-0150-405A-939E-5B0F6F642539}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB2A0BD2-0150-405A-939E-5B0F6F642539}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB2A0BD2-0150-405A-939E-5B0F6F642539}.Release|Any CPU.Build.0 = Release|Any CPU + {5E63C68C-0E60-4418-A53A-FF4EF0007080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E63C68C-0E60-4418-A53A-FF4EF0007080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E63C68C-0E60-4418-A53A-FF4EF0007080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E63C68C-0E60-4418-A53A-FF4EF0007080}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C1087A2F-C281-4AD1-AEAA-4F6E9418A8BC} + EndGlobalSection +EndGlobal diff --git a/SocialPub/Controllers/ClientToServer/AdminController.cs b/SocialPub/Controllers/ClientToServer/AdminController.cs new file mode 100644 index 0000000..b6b48a4 --- /dev/null +++ b/SocialPub/Controllers/ClientToServer/AdminController.cs @@ -0,0 +1,129 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; + +using SocialPub.ClientModels; +using SocialPub.ClientModels.User; +using SocialPub.Extensions; +using SocialPub.Resources; +using SocialPub.Services; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.Controllers.ClientToServer +{ + [ApiController, + Route("clientapi/admin")] + public class AdminController : ControllerBase + { + readonly IRootUsersService RootUsersService; + readonly ILogger Logger; + readonly IStringLocalizer Localizer; + + public AdminController(ILogger logger, + IStringLocalizer localizer, + IRootUsersService rootUsersService) + { + Logger = logger; + Localizer = localizer; + RootUsersService = rootUsersService; + } + + [HttpDelete, Route("/clientapi/admin/remove/users"), Authorize(Policy = Policies.IsAdmin)] + public async Task RemoveUsers([Required] UsersIds usersIds) + { + var result = new WebResult(); + try + { + usersIds.UserIdList.Remove(User.GetUserId()); + result = await RootUsersService.RemoveUserAsync(usersIds); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(RemoveUsers)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Authorize(Policy = Policies.IsAdmin), Route("/clientapi/admin/ban/users")] + public async Task BanUsers([Required] UsersIds usersIds) + { + if (!ModelState.IsValid) + return BadRequest(usersIds); + var result = new WebResult(); + try + { + //var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId()); + //if (isUserResult.IsValid && !(bool)isUserResult.Data) + //if (isUserResult is { IsValid: true } and not { Data: bool }) + // return Unauthorized(); + + usersIds.UserIdList.Remove(User.GetUserId()); + result = await RootUsersService.BanUserAsync(usersIds); + + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(BanUsers)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Authorize(Policy = Policies.IsAdmin), Route("/clientapi/admin/unban/users")] + public async Task UnbanUsers([Required] UsersIds usersIds) + { + if (!ModelState.IsValid) + return BadRequest(usersIds); + var result = new WebResult(); + try + { + //var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId()); + //if (isUserResult.IsValid && !(bool)isUserResult.Data) + // return Unauthorized(); + + usersIds.UserIdList.Remove(User.GetUserId()); + result = await RootUsersService.UnbanUserAsync(usersIds); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(UnbanUsers)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + //[HttpGet, Authorize(Policy = Policies.IsAdmin)] + //public async Task GetUsers() + //{ + // var result = new WebResult(); + // try + // { + // var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId()); + // if (isUserResult.IsValid && !(bool)isUserResult.Data) + // return Unauthorized(); + + // result = await UsersService.GetUsersAsync(); + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // return Ok(result.Data); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUsers)}()"); + // return BadRequest(result.Invalidate(ex.Message)); + // } + //} + } +} diff --git a/SocialPub/Controllers/ClientToServer/DataController.cs b/SocialPub/Controllers/ClientToServer/DataController.cs new file mode 100644 index 0000000..4aef4fc --- /dev/null +++ b/SocialPub/Controllers/ClientToServer/DataController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +using SocialPub.ClientModels; +using SocialPub.ClientModels.Data; +using SocialPub.Services.ClientToServer.Public; + +namespace SocialPub.Controllers.ClientToServer +{ + [ApiController, + Route("clientapi/data")] + public class DataController : ControllerBase + { + readonly IDataService DataService; + readonly ILogger Logger; + + public DataController(IDataService dataService, + ILogger logger) + { + DataService = dataService; + Logger = logger; + } + + [HttpGet, Route("/clientapi/data/ping"), AllowAnonymous] + public async ValueTask Ping() => NoContent(); + + [HttpGet, Route("/clientapi/data/current-version"), AllowAnonymous] + public IActionResult CurrentVersion() => + Ok(DataService.GetCurrentVersion()); + + [HttpGet, Route("/clientapi/data/languages"), AllowAnonymous] + public async Task Languages(CancellationToken cancellationToken) + { + var result = new WebResult(); + try + { + var languages = await DataService.GetLanguages(cancellationToken); + var viewLanguages = new List(); + + viewLanguages.AddRange(languages.Select(l => new ViewLanguage + { + Name = l.NativeName, + International2Code = l.International2Code + }).ToList()); + + return Ok(viewLanguages); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(DataController)}.{nameof(Languages)}()"); + return BadRequest(result.Invalidate(ex.Message, exception: ex)); + } + } + } +} \ No newline at end of file diff --git a/SocialPub/Controllers/ClientToServer/ModeratorController.cs b/SocialPub/Controllers/ClientToServer/ModeratorController.cs new file mode 100644 index 0000000..95f55b5 --- /dev/null +++ b/SocialPub/Controllers/ClientToServer/ModeratorController.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SocialPub.Controllers.ClientToServer +{ + [ApiController, + Route("clientapi/moderator")] + public class ModeratorController : ControllerBase + { + } +} diff --git a/SocialPub/Controllers/ClientToServer/UserController.cs b/SocialPub/Controllers/ClientToServer/UserController.cs new file mode 100644 index 0000000..09ed4ea --- /dev/null +++ b/SocialPub/Controllers/ClientToServer/UserController.cs @@ -0,0 +1,404 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; + +using SocialPub.ClientModels; +using SocialPub.ClientModels.Resources; +using SocialPub.ClientModels.User; +using SocialPub.Extensions; +using SocialPub.Models.User; +using SocialPub.Resources; +using SocialPub.Services; +using SocialPub.StaticServices; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.Controllers.ClientToServer +{ + [ApiController, + Route("clientapi/user")] + public class UserController : ControllerBase + { + readonly IRootUsersService UsersService; + readonly AuthTokenManager AuthTokenManager; + readonly ILogger Logger; + readonly IStringLocalizer Localizer; + + public UserController(IRootUsersService usersService, + AuthTokenManager authTokenManager, + IStringLocalizer localizer, + ILogger logger) + { + UsersService = usersService; + AuthTokenManager = authTokenManager; + Localizer = localizer; + Logger = logger; + } + + #region User endpoints + + [HttpPost, Route("/clientapi/user/signup"), Authorize(Policy = Policies.IsUser), AllowAnonymous] + public async Task SignUp(LoginForm signUpForm) + { + if (User.Identity?.IsAuthenticated ?? false) return Redirect("/"); + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.SignUpAsync(signUpForm); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + (var user, var userSettings) = ((RootUser, ViewUserSettings))result.Data; + var jwtUser = AuthTokenManager.GenerateToken(user, userSettings); + Logger.LogInformation( + $"{nameof(SignUp)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); + return Ok(jwtUser); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(SignUp)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/login"), Authorize(Policy = Policies.IsUser), AllowAnonymous] + public async Task Login(LoginForm loginForm) + { + if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions"); + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.LoginAsync(loginForm); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + var (user, userSettings) = + ((RootUser, ViewUserSettings))result.Data; + var jwtUser = AuthTokenManager.GenerateToken(user, userSettings); + Logger.LogInformation( + $"{nameof(Login)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); + + return Ok(jwtUser); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(Login)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + //[HttpPost, Authorize(Policy = Policies.IsUser), AllowAnonymous] + //public async Task InvitationSignUp(InvitationLoginForm signUpForm) + //{ + // if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions"); + // var result = new WebResult(); + // if (!ModelState.IsValid) + // return BadRequest(result.Invalidate(Localizer["Invalid model."])); + // try + // { + // result = await DiscussionsService.GetInvitationConfiguration(signUpForm.InvitationCode, includePassword: true); + + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // var configuration = result.Data as InvitationConfiguration; + // if (configuration.IsPasswordRequired && signUpForm.InvitationPassword != configuration.Password) + // { + // result.Invalidate(Localizer["Invalid password."], StatusCodes.Status406NotAcceptable); + // return StatusCode(result.StatusCode, result); + // } + + // result = await UsersService.SignUpAsync(new LoginForm + // { + // Username = signUpForm.Username, + // Password = signUpForm.Password, + // LightThemeIndexColour = signUpForm.ThemeIndexColour, + // ThemeIsDarkMode = signUpForm.ThemeIsDarkMode, + // InvitationPassword = signUpForm.InvitationPassword + // }, signUpForm.InvitationCode, configuration.IsPasswordRequired); + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // (var user, var userSettings) = ((User, ViewUserSettings))result.Data; + // var jwtUser = AuthTokenManager.GenerateToken(user, userSettings); + // Logger.LogInformation( + // $"{nameof(InvitationSignUp)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); + + // return Ok(jwtUser); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, $"{nameof(User)}.{nameof(InvitationSignUp)}()"); + // return BadRequest(result.Invalidate(ex.Message)); + // } + //} + + //[HttpPost, Authorize(Policy = Policies.IsUser), AllowAnonymous] + //public async Task InvitationLogin(InvitationLoginForm loginForm) + //{ + // if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions" + loginForm.InvitationCode); + // var result = new WebResult(); + // if (!ModelState.IsValid) + // return BadRequest(result.Invalidate(Localizer["Invalid model."])); + // try + // { + // result = await DiscussionsService.GetInvitationConfiguration(loginForm.InvitationCode, includePassword: true); + + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // var configuration = result.Data as InvitationConfiguration; + // if (configuration.IsPasswordRequired && loginForm.InvitationPassword != configuration.Password) + // { + // result.Invalidate(Localizer["Invalid password."], StatusCodes.Status406NotAcceptable); + // return StatusCode(result.StatusCode, result); + // } + + // result = await UsersService.LoginAsync(new LoginForm + // { + // Username = loginForm.Username, + // Password = loginForm.Password, + // ThemeIsDarkMode = loginForm.ThemeIsDarkMode, + // LightThemeIndexColour = loginForm.ThemeIndexColour, + // InvitationPassword = loginForm.InvitationPassword + // }, loginForm.InvitationCode); + + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // var (user, userSettings/*, userCurrentTier*/) = ((User, ViewUserSettings/*, UserCurrentTier*/))result.Data; + // var jwtUser = AuthTokenManager.GenerateToken(user, userSettings, 0 + // /*userCurrentTier == null ? default : (userCurrentTier.EndPeriod - DateTime.UtcNow).Days*/); + // Logger.LogInformation( + // $"{nameof(InvitationLogin)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); + + // return Ok(jwtUser); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, $"{nameof(User)}.{nameof(InvitationLogin)}()"); + // return BadRequest(result.Invalidate(ex.Message)); + // } + //} + + [HttpGet, Route("/clientapi/user/logout"), Authorize(Policy = Policies.IsUser)] + public IActionResult Logout() + { + var result = new WebResult(); + try + { + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(Logout)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/update"), Authorize(Policy = Policies.IsUser)] + public async Task UpdateUser(UserForm userEmailForm) + { + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.UpdateUserAsync(userEmailForm, User.GetUserId()); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdateUser)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/update/settings"), Authorize(Policy = Policies.IsUser)] + public async Task UpdateUserSettings(ViewUserSettings userSettings) + { + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.UpdateUserSettingsAsync(userSettings, User.GetUserId()); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdateUserSettings)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/update/password"), Authorize(Policy = Policies.IsUser)] + public async Task UpdatePassword(UserPasswordForm userPasswordForm) + { + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.UpdateUserPasswordAsync(userPasswordForm, User.GetUserId()); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdatePassword)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + //[HttpGet, Authorize(Policy = Policies.IsUser)] + //public async Task GetUser() + //{ + // var result = new WebResult(); + // try + // { + // result = await UsersService.GetUserAsync(User.GetUserId()); + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // return Ok(result.Data); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUser)}()"); + // return BadRequest(result.Invalidate(ex.Message)); + // } + //} + + [HttpGet, Route("/clientapi/user/settings"), Authorize(Policy = Policies.IsUser)] + public async Task GetUserSettings() + { + var result = new WebResult(); + try + { + result = await UsersService.GetUserSettingsAsync(User.GetUserId()); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + return Ok(result.Data); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUserSettings)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/recover/password"), AllowAnonymous] + public async Task RecoverPassword(PasswordRecoveryForm passwordRecoveryForm) + { + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + var host = $"{Request.Scheme}://{Request.Host.Host}"; + result = await UsersService.SetupAndSendRecoveryEmail(passwordRecoveryForm, host); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(result); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(RecoverPassword)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/recover/valid"), AllowAnonymous] + public async Task IsValidRecoveryCode( + [FromBody, + Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + Display(Name = "RecoveryCode", ResourceType = typeof(FieldsNameResource))] + string recoveryCode) + { + var result = new WebResult(); + try + { + result = await UsersService.IsValidRecoveryCode(recoveryCode); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(result.Data); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(IsValidRecoveryCode)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + [HttpPost, Route("/clientapi/user/recover/update/password"), AllowAnonymous] + public async Task ChangePassword(NewPasswordForm newPasswordForm) + { + var result = new WebResult(); + if (!ModelState.IsValid) + return BadRequest(result.Invalidate(Localizer["Invalid model."])); + try + { + result = await UsersService.ChangePassword(newPasswordForm); + if (!result.IsValid) + return StatusCode(result.StatusCode, result); + + return Ok(result.Data); + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(User)}.{nameof(ChangePassword)}()"); + return BadRequest(result.Invalidate(ex.Message)); + } + } + + //[HttpDelete, Route("delete"), Authorize(Policy = Policies.IsUser)] + //public async Task RemoveSelf() + //{ + // var result = new WebResult(); + // try + // { + // //result = await UsersService.RemoveUserAsync(User.GetUserId()); + // if (!result.IsValid) + // return StatusCode(result.StatusCode, result); + + // await HttpContext.SignOutAsync(); + + // return Ok(); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, $"{nameof(User)}.{nameof(RemoveSelf)}()"); + // return BadRequest(result.Invalidate(ex.Message)); + // } + //} + + #endregion User endpoints + + #region Auth refresh + + [HttpGet, Route("/clientapi/user/sniff/again"), Authorize(Policy = Policies.IsUser)] + public async Task SniffAgain() + { + + return Ok(); + } + + #endregion + } +} diff --git a/SocialPub/Controllers/ServerToServer/PeasantsController.cs b/SocialPub/Controllers/ServerToServer/PeasantsController.cs new file mode 100644 index 0000000..6902794 --- /dev/null +++ b/SocialPub/Controllers/ServerToServer/PeasantsController.cs @@ -0,0 +1,111 @@ +using Markdig; + +using Microsoft.AspNetCore.Mvc; + +using SocialPub.ClientModels.Resources; +using SocialPub.Extensions; +using SocialPub.Models.ActivityPub; +using SocialPub.Models.Group; +using SocialPub.Services; + +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +namespace SocialPub.Controllers.ServerToServer +{ + [ApiController, + Route("peasants"), Produces("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8")] + public class PeasantsController : ControllerBase + { + readonly IGroupUsersService _groupUsersService; + + public PeasantsController(IGroupUsersService groupUsersService) + { + _groupUsersService = groupUsersService; + } + + [HttpGet, Route("{actor}")] + public async Task GetActor( + [Required(ErrorMessageResourceName = "Required", + ErrorMessageResourceType = typeof(FieldsNameResource))] string actor, + CancellationToken cancellation) + { + var getResult = await _groupUsersService.GetGroup(actor, cancellation); + if (!getResult.IsValid) + return StatusCode(getResult.StatusCode, getResult); + + var actorResponse = new ActivityPubActor(); + actorResponse.Context[1] = string.Format(actorResponse.Context[1].ToString(), HttpContext.Request.PathBase); + actorResponse.Id = HttpContext.GetHostWithPath(); + actorResponse.ProfileURL = HttpContext.GetHostWithPath(); + actorResponse.Inbox = $"{HttpContext.GetHostWithPath()}/mouth"; + actorResponse.Outbox = $"{HttpContext.GetHostWithPath()}/anus"; + actorResponse.Endpoints = new() + { + SharedInboxURL = $"{HttpContext.GetHostWithPath()}/human-centipede", + OAuthAuthorizationEndpoint = $"{HttpContext.GetHost()}/sniff/again", + }; + actorResponse.PreferredUsername = actor; + actorResponse.Name = actor; + + if (getResult.Data is DmGroup dmGroup) + { + actorResponse.Summary = Markdown.ToHtml(dmGroup.Description); + actorResponse.Published = dmGroup.CreationDate; + } + else if (getResult.Data is Group dbGroup) + { + actorResponse.Summary = Markdown.ToHtml(dbGroup.Description); + actorResponse.Published = dbGroup.CreationDate; + } + + return Ok(actorResponse); + } + + [HttpGet, Route("{actor}/anus")] + public async Task Anus( + [FromQuery] bool? page, + [Required(ErrorMessageResourceName = "Required", + ErrorMessageResourceType = typeof(FieldsNameResource))] string actor) + { + if (!page.HasValue) + return Ok(new ActivityPubOrderedCollection + { + Id = HttpContext.Request.Path, + FirstItem = $"{HttpContext.GetHostWithPath()}?page=true", + }); + + + return Ok(); + } + + [HttpPost, Route("{actor}/mouth")] + public async Task Month( + [Required(ErrorMessageResourceName = "Required", + ErrorMessageResourceType = typeof(FieldsNameResource))] string actor, + [FromBody] JsonDocument json) + { + + return Ok(); + } + + [HttpPost, Route("{actor}/human-centipede")] + public async Task HumanCentipede( + [Required(ErrorMessageResourceName = "Required", + ErrorMessageResourceType = typeof(FieldsNameResource))] string actor, + [FromBody] JsonDocument json) + { + + return Ok(); + } + + [HttpPost, Route("/human-centipede")] + public async Task PublicHumanCentipede( + [FromBody] JsonDocument json) + { + + return Ok(); + } + + } +} diff --git a/SocialPub/Controllers/ServerToServer/UsersController.cs b/SocialPub/Controllers/ServerToServer/UsersController.cs new file mode 100644 index 0000000..d17c898 --- /dev/null +++ b/SocialPub/Controllers/ServerToServer/UsersController.cs @@ -0,0 +1,69 @@ +using Markdig; + +using Microsoft.AspNetCore.Mvc; +using SocialPub.ClientModels.Resources; +using SocialPub.Extensions; +using SocialPub.Models.ActivityPub; +using SocialPub.Models.Group; +using SocialPub.Services; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.Controllers.ServerToServer +{ + [ApiController, + Route("users"), Produces("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8")] + public class UsersController : ControllerBase + { + readonly IRootUsersService _rootUsersService; + + public UsersController(IRootUsersService rootUsersService) + { + _rootUsersService = rootUsersService; + } + + [HttpGet, Route("{actor}")] + public async Task GetActor( + [Required(ErrorMessageResourceName = "Required", + ErrorMessageResourceType = typeof(FieldsNameResource))] string actor, + CancellationToken cancellation) + { + var getResult = await _groupUsersService.GetGroup(actor, cancellation); + if (!getResult.IsValid) + return StatusCode(getResult.StatusCode, getResult); + + var actorResponse = new ActivityPubActor(); + actorResponse.Context[1] = string.Format(actorResponse.Context[1].ToString(), HttpContext.Request.PathBase); + actorResponse.Id = $"{HttpContext.GetHost()}/peasants/{actor}"; + actorResponse.ProfileURL = $"{HttpContext.GetHost()}/peasants/{actor}"; + actorResponse.Inbox = $"{HttpContext.GetHost()}/peasants/{actor}/mouth"; + actorResponse.Outbox = $"{HttpContext.GetHost()}/peasants/{actor}/anus"; + actorResponse.Endpoints = new() + { + SharedInboxURL = $"{HttpContext.GetHost()}/peasants/{actor}/human-centipede", + OAuthAuthorizationEndpoint = $"{HttpContext.GetHost()}/sniff/again", + }; + actorResponse.PreferredUsername = actor; + actorResponse.Name = actor; + + if (getResult.Data is DmGroup dmGroup) + { + actorResponse.Summary = Markdown.ToHtml(dmGroup.Description); + actorResponse.Published = dmGroup.CreationDate; + } + else if (getResult.Data is Group dbGroup) + { + actorResponse.Summary = Markdown.ToHtml(dbGroup.Description); + actorResponse.Published = dbGroup.CreationDate; + } + + return Ok(actorResponse); + } + + #region Admin + + + + #endregion + } +} diff --git a/SocialPub/Data/InitDb.cs b/SocialPub/Data/InitDb.cs new file mode 100644 index 0000000..82be60d --- /dev/null +++ b/SocialPub/Data/InitDb.cs @@ -0,0 +1,213 @@ +using MongoDB.Entities; + +using SocialPub.Models.Data; +using SocialPub.StaticServices; + +using System.Text; +using System.Text.Json; + +namespace SocialPub.Data +{ + public static class InitDb + { + public static async Task Init(this DbEntities dbClient, IPasswordHasher passwordHasher) + { + await SyncLanguages(dbClient); + await UpdateNewLanguages(dbClient); + //await SyncUserSettings(dbClient); + //await ClearUsers(dbClient); + //var botsInsertionAwaiter = InsertBots(dbClient, passwordHasher); + //var updateAnswersAwaiter = UpdateAnswersLikes(dbClient); + + //Task.WaitAll(botsInsertionAwaiter); + //await SyncShareDiscussions(dbClient); + } + + //static async Task UpdateAnswersLikes(DbCollections dbClient) + //{ + // if (await dbClient.AnswersLikes.Find(al => al.AnswerId != null).AnyAsync()) return; + + // foreach (var answerLikes in await dbClient.AnswersLikes.Find(al => true).ToListAsync()) + // { + // answerLikes.AnswerId = answerLikes.Id; + // answerLikes.Id = ObjectId.GenerateNewId(); + // await dbClient.AnswersLikes.InsertOneAsync(answerLikes); + // _ = await dbClient.AnswersLikes.DeleteOneAsync(al => al.Id == answerLikes.AnswerId); + // } + //} + + //static async Task ClearUsers(DbColl dbClient) + //{ + // await dbClient.Users.DeleteManyAsync(u => true); + // await dbClient.UsersSettings.DeleteManyAsync(u => true); + // await dbClient.Answers.DeleteManyAsync(u => true); + // await dbClient.AnswersLikes.DeleteManyAsync(u => true); + // await dbClient.Comments.DeleteManyAsync(u => true); + // await dbClient.EmailRecoveries.DeleteManyAsync(u => true); + // await dbClient.Threads.DeleteManyAsync(u => true); + // await dbClient.EDiscussions.DeleteManyAsync(u => true); + // await dbClient.Logs.DeleteManyAsync(u => true); + // await dbClient.ShareDiscussions.De(u => true); + // await dbClient.DiscussionFiles.DeleteManyAsync(u => true); + // await dbClient.UsersGroups.DeleteManyAsync(u => true); + //} + + // static async Task SyncShareDiscussions(DbColl dbClient) + // { + // var anyThread = await dbClient.Discussions.Match(t => true).ExecuteAnyAsync(); + // var anyShareDiscussion = await dbClient.ShareDiscussions.Match(st => true).ExecuteAnyAsync(); + // + // if (anyThread == anyShareDiscussion) return; + // + // var discussions = await dbClient.Discussions.ManyAsync(t => true); + // + // foreach (var discussion in discussions) + // { + // var newShareDiscussion = new ShareDiscussion + // { + // DiscussionId = discussion.ID, + // InvitationCode = $"{Guid.NewGuid():N}{Guid.NewGuid():N}" + // }; + // await newShareDiscussion.SaveAsync(); + // } + // } + + static async Task SyncLanguages(DbEntities dbClient) + { + if (await dbClient.Languages.ExecuteAnyAsync()) return; + + var languagesJson = await File.ReadAllTextAsync(Path.Combine(Directory.GetCurrentDirectory(), "Data", "languagesNative.json"), Encoding.UTF8); + var languagesRows = JsonSerializer.Deserialize>(languagesJson); + + var languages = new List(); + foreach (var languageRow in languagesRows ?? new LanguagesRow[] + { + }) + languages.Add(new Language + { + EnglishName = languageRow.EnglishName, + NativeName = languageRow.NativeName, + International2Code = languageRow.International2Code + }); + + await DB.SaveAsync(languages); + } + + static async Task UpdateNewLanguages(DbEntities dbClient) + { + var bulgarianLanguage = await dbClient.Languages.Match(l => l.International2Code == "bg").ExecuteFirstAsync(); + if (bulgarianLanguage.NativeName == "Български") return; + + var languagesToUpdate = new Dictionary() + { + { + "bg", "Български" + }, + { + "cs", "Česky" + }, + { + "da", "Dansk" + }, + { + "nl", "Nederlands" + }, + { + "et", "Eesti" + }, + { + "fi", "Suomalainen" + }, + { + "el", "Ελληνική" + }, + { + "hu", "Magyar" + }, + { + "lv", "Latviešu" + }, + { + "lt", "Lietuvių kalba" + }, + { + "pl", "Polski" + }, + { + "pt", "Português" + }, + { + "ro", "Românesc" + }, + { + "sk", "Slovenská" + }, + { + "sl", "Slovenski" + }, + { + "sv", "Svenska" + } + }; + + foreach (var languageToUpdate in languagesToUpdate) + { + var bgLang = await dbClient.Languages.Match(l => l.International2Code == languageToUpdate.Key).ExecuteFirstAsync(); + bgLang.NativeName = languageToUpdate.Value; + await bgLang.SaveAsync(); + } + } + + // static async Task SyncUserSettings(DbColl dbClient) + // { + // if (!await dbClient.Users.ExecuteAnyAsync()) return; + // + // var users = await dbClient.Users.ManyAsync(u => true); + // var usersId = users.Select(u => u.ID).ToList(); + // var usersSettings = await dbClient.UsersSettings + // .ManyAsync(us => true); + // + // var usersWithoutSettings = users.Where(u => !usersSettings.Any(us => us.UserId == u.ID)).Select(u => u.ID).ToList(); + // if (usersWithoutSettings.Count == 0) return; + // + // var defaultLanguage = await dbClient.Languages + // .Match(l => l.International2Code == "en") + // .ExecuteFirstAsync(); + // foreach (var userId in usersWithoutSettings) + // await DB.SaveAsync(new UserSettings + // { + // UserId = userId, + // LanguageId = defaultLanguage.International2Code + // }); + // } + + //static async Task InsertBots(DbEntities dbClient, IPasswordHasher passwordHasher) + //{ + // if (await dbClient.Users.Match(u => u.UserName == "bot0").ExecuteAnyAsync()) return; + + // var newBots = new List(); + // var newBotsUserSettings = new List(); + + // for (int i = 0; i < 100; i++) + // newBots.Add(new User + // { + // HashedPassword = passwordHasher.Hash("Asdfmov13!!!"), + // UserName = $"bot{i}" + // }); + // await newBots.SaveAsync(); + + // var defaultLanguage = await dbClient.Languages + // .Match(l => l.International2Code == "en") + // .ExecuteFirstAsync(); + + // foreach (var newBot in newBots) + // newBotsUserSettings.Add(new() + // { + // UserId = newBot.ID, + // LanguageId = defaultLanguage.International2Code, + // }); + + // await newBotsUserSettings.SaveAsync(); + //} + } +} \ No newline at end of file diff --git a/SocialPub/Data/LanguagesRow.cs b/SocialPub/Data/LanguagesRow.cs new file mode 100644 index 0000000..b1a70ee --- /dev/null +++ b/SocialPub/Data/LanguagesRow.cs @@ -0,0 +1,9 @@ +namespace SocialPub.Data +{ + public class LanguagesRow + { + public string EnglishName { get; set; } + public string NativeName { get; set; } + public string International2Code { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub/Data/languagesNative.json b/SocialPub/Data/languagesNative.json new file mode 100644 index 0000000..8c85ad7 --- /dev/null +++ b/SocialPub/Data/languagesNative.json @@ -0,0 +1,2210 @@ +[ + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Abkhazian", + "NativeName": "\u0430\u04A7\u0441\u0443\u0430 \u0431\u044B\u0437\u0448\u04D9\u0430, \u0430\u04A7\u0441\u0448\u04D9\u0430", + "International2Code": "ab" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Afar", + "NativeName": "Afaraf", + "International2Code": "aa" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Afrikaans", + "NativeName": "Afrikaans", + "International2Code": "af" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Akan", + "NativeName": "Akan", + "International2Code": "ak" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Albanian", + "NativeName": "Shqip", + "International2Code": "sq" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Amharic", + "NativeName": "\u12A0\u121B\u122D\u129B", + "International2Code": "am" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Arabic", + "NativeName": "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", + "International2Code": "ar" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Aragonese", + "NativeName": "aragon\u00E9s", + "International2Code": "an" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Armenian", + "NativeName": "\u0540\u0561\u0575\u0565\u0580\u0565\u0576", + "International2Code": "hy" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "A amese", + "NativeName": "\u0985\u09B8\u09AE\u09C0\u09AF\u09BC\u09BE", + "International2Code": "as" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Avaric", + "NativeName": "\u0430\u0432\u0430\u0440 \u043C\u0430\u0446\u04C0, \u043C\u0430\u0433\u04C0\u0430\u0440\u0443\u043B \u043C\u0430\u0446\u04C0", + "International2Code": "av" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Avestan", + "NativeName": "avesta", + "International2Code": "ae" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Aymara", + "NativeName": "aymar aru", + "International2Code": "ay" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Azerbaijani", + "NativeName": "az\u0259rbaycan dili, \u062A\u06C6\u0631\u06A9\u062C\u0647", + "International2Code": "az" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bambara", + "NativeName": "bamanankan", + "International2Code": "bm" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bashkir", + "NativeName": "\u0431\u0430\u0448\u04A1\u043E\u0440\u0442 \u0442\u0435\u043B\u0435", + "International2Code": "ba" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Basque", + "NativeName": "euskara, euskera", + "International2Code": "eu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Belarusian", + "NativeName": "\u0431\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F \u043C\u043E\u0432\u0430", + "International2Code": "be" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bengali", + "NativeName": "\u09AC\u09BE\u0982\u09B2\u09BE", + "International2Code": "bn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bihari languages", + "NativeName": "\u092D\u094B\u091C\u092A\u0941\u0930\u0940", + "International2Code": "bh" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bislama", + "NativeName": "Bislama", + "International2Code": "bi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bosnian", + "NativeName": "bosanski jezik", + "International2Code": "bs" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Breton", + "NativeName": "brezhoneg", + "International2Code": "br" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Bulgarian", + "NativeName": "\u0431\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438 \u0435\u0437\u0438\u043A", + "International2Code": "bg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Burmese", + "NativeName": "\u1017\u1019\u102C\u1005\u102C", + "International2Code": "my" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Catalan, Valencian", + "NativeName": "catal\u00E0, valenci\u00E0", + "International2Code": "ca" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Chamorro", + "NativeName": "Chamoru", + "International2Code": "ch" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Chechen", + "NativeName": "\u043D\u043E\u0445\u0447\u0438\u0439\u043D \u043C\u043E\u0442\u0442", + "International2Code": "ce" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Chichewa, Chewa, Nyanja", + "NativeName": "chiChe\u0175a, chinyanja", + "International2Code": "ny" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Chinese", + "NativeName": "\u4E2D\u6587 (Zh\u014Dngw\u00E9n), \u6C49\u8BED, \u6F22\u8A9E", + "International2Code": "zh" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Chuvash", + "NativeName": "\u0447\u04D1\u0432\u0430\u0448 \u0447\u04D7\u043B\u0445\u0438", + "International2Code": "cv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Cornish", + "NativeName": "Kernewek", + "International2Code": "kw" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Corsican", + "NativeName": "corsu, lingua corsa", + "International2Code": "co" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Cree", + "NativeName": "\u14C0\u1426\u1403\u152D\u140D\u140F\u1423", + "International2Code": "cr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Croatian", + "NativeName": "hrvatski jezik", + "International2Code": "hr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Czech", + "NativeName": "\u010De\u0161tina, \u010Desk\u00FD jazyk", + "International2Code": "cs" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Danish", + "NativeName": "dansk", + "International2Code": "da" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Divehi, Dhivehi, Maldivian", + "NativeName": "\u078B\u07A8\u0788\u07AC\u0780\u07A8", + "International2Code": "dv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Dutch, Flemish", + "NativeName": "Nederlands, Vlaams", + "International2Code": "nl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Dzongkha", + "NativeName": "\u0F62\u0FAB\u0F7C\u0F44\u0F0B\u0F41", + "International2Code": "dz" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "English", + "NativeName": "English", + "International2Code": "en" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Esperanto", + "NativeName": "Esperanto", + "International2Code": "eo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Estonian", + "NativeName": "eesti, eesti keel", + "International2Code": "et" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ewe", + "NativeName": "E\u028Begbe", + "International2Code": "ee" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Faroese", + "NativeName": "f\u00F8royskt", + "International2Code": "fo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Fijian", + "NativeName": "vosa Vakaviti", + "International2Code": "fj" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Finnish", + "NativeName": "suomi, suomen kieli", + "International2Code": "fi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "French", + "NativeName": "Français", + "International2Code": "fr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Fulah", + "NativeName": "Fulfulde, Pulaar, Pular", + "International2Code": "ff" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Galician", + "NativeName": "Galego", + "International2Code": "gl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Georgian", + "NativeName": "\u10E5\u10D0\u10E0\u10D7\u10E3\u10DA\u10D8", + "International2Code": "ka" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "German", + "NativeName": "Deutsch", + "International2Code": "de" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Greek, Modern (1453\u2013)", + "NativeName": "\u03B5\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", + "International2Code": "el" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Guarani", + "NativeName": "Ava\u00F1e\u0027\u1EBD", + "International2Code": "gn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Gujarati", + "NativeName": "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0", + "International2Code": "gu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Haitian, Haitian Creole", + "NativeName": "Krey\u00F2l ayisyen", + "International2Code": "ht" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Hausa", + "NativeName": "(Hausa) \u0647\u064E\u0648\u064F\u0633\u064E", + "International2Code": "ha" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Hebrew", + "NativeName": "\u05E2\u05D1\u05E8\u05D9\u05EA", + "International2Code": "he" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Herero", + "NativeName": "Otjiherero", + "International2Code": "hz" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Hindi", + "NativeName": "\u0939\u093F\u0928\u094D\u0926\u0940, \u0939\u093F\u0902\u0926\u0940", + "International2Code": "hi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Hiri Motu", + "NativeName": "Hiri Motu", + "International2Code": "ho" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Hungarian", + "NativeName": "magyar", + "International2Code": "hu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Interlingua (International Auxiliary Language A ociation)", + "NativeName": "Interlingua", + "International2Code": "ia" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Indonesian", + "NativeName": "Bahasa Indonesia", + "International2Code": "id" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Interlingue, Occidental", + "NativeName": "(originally:) Occidental, (after WWII:) Interlingue", + "International2Code": "ie" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Irish", + "NativeName": "Gaeilge", + "International2Code": "ga" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Igbo", + "NativeName": "As\u1EE5s\u1EE5 Igbo", + "International2Code": "ig" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Inupiaq", + "NativeName": "I\u00F1upiaq, I\u00F1upiatun", + "International2Code": "ik" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ido", + "NativeName": "Ido", + "International2Code": "io" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Icelandic", + "NativeName": "\u00CDslenska", + "International2Code": "is" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Italian", + "NativeName": "Italiano", + "International2Code": "it" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Inuktitut", + "NativeName": "\u1403\u14C4\u1483\u144E\u1450\u1466", + "International2Code": "iu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Japanese", + "NativeName": "\u65E5\u672C\u8A9E (\u306B\u307B\u3093\u3054)", + "International2Code": "ja" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Javanese", + "NativeName": "\uA9A7\uA9B1\uA997\uA9AE, Basa Jawa", + "International2Code": "jv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kalaallisut, Greenlandic", + "NativeName": "kalaallisut, kalaallit oqaasii", + "International2Code": "kl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kannada", + "NativeName": "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1", + "International2Code": "kn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kanuri", + "NativeName": "Kanuri", + "International2Code": "kr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kashmiri", + "NativeName": "\u0915\u0936\u094D\u092E\u0940\u0930\u0940, \u0643\u0634\u0645\u064A\u0631\u064A", + "International2Code": "ks" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kazakh", + "NativeName": "\u049B\u0430\u0437\u0430\u049B \u0442\u0456\u043B\u0456", + "International2Code": "kk" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Central Khmer", + "NativeName": "\u1781\u17D2\u1798\u17C2\u179A, \u1781\u17C1\u1798\u179A\u1797\u17B6\u179F\u17B6, \u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A", + "International2Code": "km" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kikuyu, Gikuyu", + "NativeName": "G\u0129k\u0169y\u0169", + "International2Code": "ki" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kinyarwanda", + "NativeName": "Ikinyarwanda", + "International2Code": "rw" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kirghiz, Kyrgyz", + "NativeName": "\u041A\u044B\u0440\u0433\u044B\u0437\u0447\u0430, \u041A\u044B\u0440\u0433\u044B\u0437 \u0442\u0438\u043B\u0438", + "International2Code": "ky" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Komi", + "NativeName": "\u043A\u043E\u043C\u0438 \u043A\u044B\u0432", + "International2Code": "kv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kongo", + "NativeName": "Kikongo", + "International2Code": "kg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Korean", + "NativeName": "\uD55C\uAD6D\uC5B4", + "International2Code": "ko" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kurdish", + "NativeName": "Kurd\u00EE, \u06A9\u0648\u0631\u062F\u06CC", + "International2Code": "ku" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Kuanyama, Kwanyama", + "NativeName": "Kuanyama", + "International2Code": "kj" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Latin", + "NativeName": "latine, lingua latina", + "International2Code": "la" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Luxembourgish, Letzeburgesch", + "NativeName": "L\u00EBtzebuergesch", + "International2Code": "lb" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ganda", + "NativeName": "Luganda", + "International2Code": "lg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Limburgan, Limburger, Limburgish", + "NativeName": "Limburgs", + "International2Code": "li" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Lingala", + "NativeName": "Ling\u00E1la", + "International2Code": "ln" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Lao", + "NativeName": "\u0E9E\u0EB2\u0EAA\u0EB2\u0EA5\u0EB2\u0EA7", + "International2Code": "lo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Lithuanian", + "NativeName": "lietuvi\u0173 kalba", + "International2Code": "lt" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Luba-Katanga", + "NativeName": "Kiluba", + "International2Code": "lu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Latvian", + "NativeName": "latvie\u0161u valoda", + "International2Code": "lv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Manx", + "NativeName": "Gaelg, Gailck", + "International2Code": "gv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Macedonian", + "NativeName": "\u043C\u0430\u043A\u0435\u0434\u043E\u043D\u0441\u043A\u0438 \u0458\u0430\u0437\u0438\u043A", + "International2Code": "mk" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Malagasy", + "NativeName": "fiteny malagasy", + "International2Code": "mg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Malay", + "NativeName": "Bahasa Melayu, \u0628\u0647\u0627\u0633 \u0645\u0644\u0627\u064A\u0648", + "International2Code": "ms" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Malayalam", + "NativeName": "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02", + "International2Code": "ml" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Maltese", + "NativeName": "Malti", + "International2Code": "mt" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Maori", + "NativeName": "te reo M\u0101ori", + "International2Code": "mi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Marathi", + "NativeName": "\u092E\u0930\u093E\u0920\u0940", + "International2Code": "mr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Marshallese", + "NativeName": "Kajin M\u0327aje\u013C", + "International2Code": "mh" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Mongolian", + "NativeName": "\u041C\u043E\u043D\u0433\u043E\u043B \u0445\u044D\u043B", + "International2Code": "mn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Nauru", + "NativeName": "Dorerin Naoero", + "International2Code": "na" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Navajo, Navaho", + "NativeName": "Din\u00E9 bizaad", + "International2Code": "nv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "North Ndebele", + "NativeName": "isiNdebele", + "International2Code": "nd" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Nepali", + "NativeName": "\u0928\u0947\u092A\u093E\u0932\u0940", + "International2Code": "ne" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ndonga", + "NativeName": "Owambo", + "International2Code": "ng" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Norwegian Bokm\u00E5l", + "NativeName": "Norsk Bokm\u00E5l", + "International2Code": "nb" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Norwegian Nynorsk", + "NativeName": "Norsk Nynorsk", + "International2Code": "nn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Norwegian", + "NativeName": "Norsk", + "International2Code": "no" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sichuan Yi, Nuosu", + "NativeName": "\uA188\uA320\uA4BF Nuosuhxop", + "International2Code": "ii" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "South Ndebele", + "NativeName": "isiNdebele", + "International2Code": "nr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Occitan", + "NativeName": "occitan, lenga d\u0027\u00F2c", + "International2Code": "oc" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ojibwa", + "NativeName": "\u140A\u14C2\u1511\u14C8\u142F\u14A7\u140E\u14D0", + "International2Code": "oj" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Church\u00A0Slavic, Old Slavonic, Church Slavonic, Old Bulgarian, Old\u00A0Church\u00A0Slavonic", + "NativeName": "\u0469\u0437\u044B\u043A\u044A \u0441\u043B\u043E\u0432\u0463\u043D\u044C\u0441\u043A\u044A", + "International2Code": "cu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Oromo", + "NativeName": "Afaan Oromoo", + "International2Code": "om" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Oriya", + "NativeName": "\u0B13\u0B21\u0B3C\u0B3F\u0B06", + "International2Code": "or" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "O etian, O etic", + "NativeName": "\u0438\u0440\u043E\u043D \u00E6\u0432\u0437\u0430\u0433", + "International2Code": "os" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Punjabi, Panjabi", + "NativeName": "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40, \u067E\u0646\u062C\u0627\u0628\u06CC", + "International2Code": "pa" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Pali", + "NativeName": "\u092A\u093E\u0932\u093F, \u092A\u093E\u0933\u093F", + "International2Code": "pi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Persian", + "NativeName": "\u0641\u0627\u0631\u0633\u06CC", + "International2Code": "fa" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Polish", + "NativeName": "j\u0119zyk polski, polszczyzna", + "International2Code": "pl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Pashto, Pushto", + "NativeName": "\u067E\u069A\u062A\u0648", + "International2Code": "ps" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Portuguese", + "NativeName": "Portugu\u00EAs", + "International2Code": "pt" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Quechua", + "NativeName": "Runa Simi, Kichwa", + "International2Code": "qu" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Romansh", + "NativeName": "Rumantsch Grischun", + "International2Code": "rm" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Rundi", + "NativeName": "Ikirundi", + "International2Code": "rn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Romanian, Moldavian, Moldovan", + "NativeName": "Rom\u00E2n\u0103", + "International2Code": "ro" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ru ian", + "NativeName": "\u0440\u0443\u0441\u0441\u043A\u0438\u0439", + "International2Code": "ru" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sanskrit", + "NativeName": "\u0938\u0902\u0938\u094D\u0915\u0943\u0924\u092E\u094D", + "International2Code": "sa" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sardinian", + "NativeName": "sardu", + "International2Code": "sc" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sindhi", + "NativeName": "\u0938\u093F\u0928\u094D\u0927\u0940, \u0633\u0646\u068C\u064A\u060C \u0633\u0646\u062F\u06BE\u06CC", + "International2Code": "sd" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Northern Sami", + "NativeName": "Davvis\u00E1megiella", + "International2Code": "se" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Samoan", + "NativeName": "gagana fa\u0027a Samoa", + "International2Code": "sm" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sango", + "NativeName": "y\u00E2ng\u00E2 t\u00EE s\u00E4ng\u00F6", + "International2Code": "sg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Serbian", + "NativeName": "\u0441\u0440\u043F\u0441\u043A\u0438 \u0458\u0435\u0437\u0438\u043A", + "International2Code": "sr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Gaelic, Scottish Gaelic", + "NativeName": "G\u00E0idhlig", + "International2Code": "gd" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Shona", + "NativeName": "chiShona", + "International2Code": "sn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sinhala, Sinhalese", + "NativeName": "\u0DC3\u0DD2\u0D82\u0DC4\u0DBD", + "International2Code": "si" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Slovak", + "NativeName": "Sloven\u010Dina, Slovensk\u00FD jazyk", + "International2Code": "sk" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Slovenian", + "NativeName": "Slovenski jezik, Sloven\u0161\u010Dina", + "International2Code": "sl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Somali", + "NativeName": "Soomaaliga, af Soomaali", + "International2Code": "so" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Southern Sotho", + "NativeName": "Sesotho", + "International2Code": "st" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Spanish, Castilian", + "NativeName": "Espa\u00F1ol", + "International2Code": "es" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Sundanese", + "NativeName": "Basa Sunda", + "International2Code": "su" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Swahili", + "NativeName": "Kiswahili", + "International2Code": "sw" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Swati", + "NativeName": "SiSwati", + "International2Code": null + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Swedish", + "NativeName": "Svenska", + "International2Code": "sv" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tamil", + "NativeName": "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", + "International2Code": "ta" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Telugu", + "NativeName": "\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41", + "International2Code": "te" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tajik", + "NativeName": "\u0442\u043E\u04B7\u0438\u043A\u04E3, to\u00E7ik\u012B, \u062A\u0627\u062C\u06CC\u06A9\u06CC", + "International2Code": "tg" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Thai", + "NativeName": "\u0E44\u0E17\u0E22", + "International2Code": "th" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tigrinya", + "NativeName": "\u1275\u130D\u122D\u129B", + "International2Code": "ti" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tibetan", + "NativeName": "\u0F56\u0F7C\u0F51\u0F0B\u0F61\u0F72\u0F42", + "International2Code": "bo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Turkmen", + "NativeName": "T\u00FCrkmen, \u0422\u04AF\u0440\u043A\u043C\u0435\u043D", + "International2Code": "tk" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tagalog", + "NativeName": "Wikang Tagalog", + "International2Code": "tl" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tswana", + "NativeName": "Setswana", + "International2Code": "tn" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tonga (Tonga Islands)", + "NativeName": "Faka Tonga", + "International2Code": "to" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Turkish", + "NativeName": "T\u00FCrk\u00E7e", + "International2Code": "tr" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tsonga", + "NativeName": "Xitsonga", + "International2Code": "ts" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tatar", + "NativeName": "\u0442\u0430\u0442\u0430\u0440 \u0442\u0435\u043B\u0435, tatar tele", + "International2Code": "tt" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Twi", + "NativeName": "Twi", + "International2Code": "tw" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Tahitian", + "NativeName": "Reo Tahiti", + "International2Code": "ty" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Uighur, Uyghur", + "NativeName": "\u0626\u06C7\u064A\u063A\u06C7\u0631\u0686\u06D5, Uyghurche", + "International2Code": "ug" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Ukrainian", + "NativeName": "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", + "International2Code": "uk" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Urdu", + "NativeName": "\u0627\u0631\u062F\u0648", + "International2Code": "ur" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Uzbek", + "NativeName": "O\u02BBzbek, \u040E\u0437\u0431\u0435\u043A, \u0623\u06C7\u0632\u0628\u06D0\u0643", + "International2Code": "uz" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Venda", + "NativeName": "Tshiven\u1E13a", + "International2Code": "ve" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Vietnamese", + "NativeName": "Ti\u1EBFng Vi\u1EC7t", + "International2Code": "vi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Volap\u00FCk", + "NativeName": "Volap\u00FCk", + "International2Code": "vo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Walloon", + "NativeName": "Walon", + "International2Code": "wa" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Welsh", + "NativeName": "Cymraeg", + "International2Code": "cy" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Wolof", + "NativeName": "Wollof", + "International2Code": "wo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Western Frisian", + "NativeName": "Frysk", + "International2Code": "fy" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Xhosa", + "NativeName": "isiXhosa", + "International2Code": "xh" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Yiddish", + "NativeName": "\u05D9\u05D9\u05B4\u05D3\u05D9\u05E9", + "International2Code": "yi" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Yoruba", + "NativeName": "Yor\u00F9b\u00E1", + "International2Code": "yo" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Zhuang, Chuang", + "NativeName": "Sa\u026F cue\u014B\u0185, Saw cuengh", + "International2Code": "za" + }, + { + "Id": { + "Timestamp": 0, + "Machine": 0, + "Pid": 0, + "Increment": 0, + "CreationTime": "1970-01-01T00:00:00Z" + }, + "EnglishName": "Zulu", + "NativeName": "isiZulu", + "International2Code": "zu" + } +] \ No newline at end of file diff --git a/SocialPub/Extensions/AddAuthExtension.cs b/SocialPub/Extensions/AddAuthExtension.cs new file mode 100644 index 0000000..2a3e4f1 --- /dev/null +++ b/SocialPub/Extensions/AddAuthExtension.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.IdentityModel.Tokens; + +using SocialPub.Services; + +using System.Text; + +namespace SocialPub.Extensions +{ + public static class AddAuthExtension + { + public static AuthenticationBuilder AddSocialPubAuth(this AuthenticationBuilder builder, IConfiguration configuration) + { + builder.AddJwtBearer(options => { +#if DEBUG + options.RequireHttpsMetadata = false; +#endif + options.TokenValidationParameters = new() + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = configuration["AppConfiguration:Jwt:Issuer"], + ValidAudience = configuration["AppConfiguration:Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"])) + }; + options.Events = new JwtEvents(); + }); + return builder; + } + } +} \ No newline at end of file diff --git a/SocialPub/Extensions/Extensions.cs b/SocialPub/Extensions/Extensions.cs new file mode 100644 index 0000000..eaec1fe --- /dev/null +++ b/SocialPub/Extensions/Extensions.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Authorization; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +using SocialPub.ClientModels; + +using System.Security.Cryptography.X509Certificates; +using Org.BouncyCastle.Math; +using SocialPub.Models.User; +using System.Security.Claims; + +namespace SocialPub.Extensions +{ + public static class Extensions + { + public static string GetLogsConnectionString(this IConfiguration configuration) => + configuration.GetSection("Serilog") + ?.GetSection("WriteTo") + ?.GetChildren() + ?.First() + ?.GetSection("Args") + ?.GetSection("databaseUrl") + ?.Value; + + public static AuthorizationPolicy IsAdminPolicy() => + new AuthorizationPolicyBuilder().RequireAuthenticatedUser() + .RequireClaim(Policies.IsAdmin, true.ToString().ToLower()) + .RequireClaim(Policies.IsUser, true.ToString().ToLower()) + .RequireClaim(Policies.IsModerator, true.ToString().ToLower()) + .Build(); + + public static AuthorizationPolicy IsUserPolicy() => + new AuthorizationPolicyBuilder().RequireAuthenticatedUser() + .RequireClaim(Policies.IsUser, true.ToString().ToLower()) + .Build(); + + public static AuthorizationPolicy IsModeratorPolicy() => + new AuthorizationPolicyBuilder().RequireAuthenticatedUser() + .RequireClaim(Policies.IsUser, true.ToString().ToLower()) + .RequireClaim(Policies.IsModerator, true.ToString().ToLower()) + .Build(); + + public static string GetHostWithPath(this HttpContext httpContext) => + $"https://{httpContext.Request.Host}{httpContext.Request.Path}"; + + public static string GetHost(this HttpContext httpContext) => + $"https://{httpContext.Request.Host}"; + + public static X509Certificate2 GetX509Certificate2(string certName) + { + var keypairgen = new RsaKeyPairGenerator(); + keypairgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 512)); + + var keypair = keypairgen.GenerateKeyPair(); + + var gen = new X509V3CertificateGenerator(); + + var CN = new X509Name("CN=" + certName); + var SN = BigInteger.ProbablePrime(120, new Random()); + + gen.SetSerialNumber(SN); + gen.SetSubjectDN(CN); + gen.SetIssuerDN(CN); + gen.SetNotAfter(DateTime.MaxValue); + gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0))); + gen.SetSignatureAlgorithm("MD5WithRSA"); + gen.SetPublicKey(keypair.Public); + + var newCert = gen.Generate(keypair.Private); + + return new X509Certificate2(DotNetUtilities.ToX509Certificate((Org.BouncyCastle.X509.X509Certificate)newCert)); + } + + public static UserPolicyType GetHighestPolicy(this ClaimsPrincipal claimsPrincipal) + { + if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsAdmin))) + return UserPolicyType.IsAdmin; + + if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsModerator))) + return UserPolicyType.IsModerator; + + if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsUser))) + return UserPolicyType.IsUser; + + return UserPolicyType.IsUser; + } + } +} diff --git a/SocialPub/Extensions/OperationCancelledExceptionFilter.cs b/SocialPub/Extensions/OperationCancelledExceptionFilter.cs new file mode 100644 index 0000000..63c6819 --- /dev/null +++ b/SocialPub/Extensions/OperationCancelledExceptionFilter.cs @@ -0,0 +1,23 @@ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace SocialPub.Extensions +{ + public class OperationCancelledExceptionFilter : ExceptionFilterAttribute + { + readonly Serilog.ILogger Logger; + + public OperationCancelledExceptionFilter(Serilog.ILogger logger) => + Logger = logger.ForContext(); + + public override void OnException(ExceptionContext context) + { + if (context.Exception is not OperationCanceledException) return; + + Logger.Information($"Request for {context.HttpContext.Request.Path} was cancelled."); + context.ExceptionHandled = true; + context.Result = new StatusCodeResult(499); + } + } +} \ No newline at end of file diff --git a/SocialPub/Extensions/StringExtensions.cs b/SocialPub/Extensions/StringExtensions.cs new file mode 100644 index 0000000..6c29953 --- /dev/null +++ b/SocialPub/Extensions/StringExtensions.cs @@ -0,0 +1,36 @@ +using SocialPub.ClientModels; + +using System.Security.Claims; + +namespace SocialPub.Extensions +{ + public static class StringExtensions + { + public static string GetUserId(this ClaimsPrincipal claimsPrincipal) + { + return claimsPrincipal?.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.UserData)?.Value; + } + + public static string GetUserName(this ClaimsPrincipal claimsPrincipal) + { + var userId = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; + return string.IsNullOrEmpty(userId) ? default : userId; + } + + public static List GetUserPolicies(this ClaimsPrincipal claimsPrincipal) + { + var policies = new List(); + + if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsAdmin && bool.Parse(c.Value))) + policies.Add(Policies.IsAdmin); + + if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsUser && bool.Parse(c.Value))) + policies.Add(Policies.IsUser); + + if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsModerator && bool.Parse(c.Value))) + policies.Add(Policies.IsModerator); + + return policies; + } + } +} diff --git a/SocialPub/Middleware/SocialPubConfigurations.cs b/SocialPub/Middleware/SocialPubConfigurations.cs new file mode 100644 index 0000000..63ec83f --- /dev/null +++ b/SocialPub/Middleware/SocialPubConfigurations.cs @@ -0,0 +1,223 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.ResponseCompression; + +using SocialPub.ClientModels; +using SocialPub.Extensions; +using SocialPub.Models; +using SocialPub.Services; +using SocialPub.StaticServices; +using NSign.AspNetCore; + +using System.Text.Json.Serialization; +using NSign.Providers; +using NSign; +using System.Security.Cryptography.X509Certificates; +using NSign.Signatures; +using System.Net.Http.Headers; +using Microsoft.Extensions.DependencyInjection; +using System.Security.Claims; +using Microsoft.Extensions.Caching.Memory; +using NSign.Client; +using static NSign.Constants; +using System.Text; +using Microsoft.OpenApi.Models; +using SocialPub.Services.ClientToServer.Public; + +namespace SocialPub.Middleware +{ + public static class SocialPubConfigurations + { + public static IServiceCollection socialPubAppSettingsConfiguration(this IServiceCollection service, IConfiguration configuration) + { + return service + .Configure(configuration.GetSection(nameof(MongoSettings))) + .Configure(configuration.GetSection(nameof(AppConfiguration))); + } + public static IServiceCollection socialPubWorkersConfiguration(this IServiceCollection service) + { + return service; + //.AddHostedService() + //.AddHostedService() + //.AddHostedService(); + } + public static IServiceCollection socialPubHTTPSignature(this IServiceCollection service, IConfiguration configuration) + { + //HTTP CLIENT + service.Configure(options => options.WithHash(AddDigestOptions.Hash.Sha256)) + .ConfigureMessageSigningOptions(options => + { + options.SignatureName = "SocialPub"; + options + .WithMandatoryComponent(SignatureComponent.Path) + .WithMandatoryComponent(SignatureComponent.RequestTarget) + .SetParameters = signatureParams => signatureParams.WithKeyId("keyId"); + }) + .Services.Configure(options => + { + + }) + .AddHttpClient(nameof(ActivityPubClient)) + .ConfigureHttpClient(httpClient => + { + httpClient.DefaultRequestHeaders.Accept.Add(new("application/ld+json")); + }) + .AddDigestAndSigningHandlers() + //.AddSignatureVerifiationHandler() + .Services + .AddSingleton(new HmacSha256SignatureProvider(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"]))); + + //MESSAGE RESPONSE + + + return service; + //.Configure(options => + //{ + // options.SignaturesToVerify.Add("sample"); + // options.RequiredSignatureComponents.Add(SignatureComponent.Path); + // options.RequiredSignatureComponents.Add(SignatureComponent.Method); + // options.RequiredSignatureComponents.Add(SignatureComponent.Digest); + //}) + //.AddSignatureVerification(serviceProvider => + //{ + // var memoryCache = serviceProvider.GetRequiredService(); + // //var httpContextAccessor = serviceProvider.GetRequiredService(); + + // //httpContextAccessor.HttpContext.Request. + + // var cert = memoryCache.GetOrCreate("SocialPub", (cacheEntry) => Extensions.Extensions.GetX509Certificate2("socialPubCert")); + // return new RsaPkcs15Sha256SignatureProvider(cert, "anon"); + //}) + //.ConfigureMessageSigningOptions(options => + //{ + // options.WithMandatoryComponent(SignatureComponent.Path) + // .WithMandatoryComponent(SignatureComponent.Method) + // .WithMandatoryComponent(SignatureComponent.Digest) + // .WithOptionalComponent(new HttpHeaderDictionaryStructuredComponent(NSign.Constants.Headers.Signature, "sample", bindRequest: true)); + // options.SignatureName = "resp"; + // options.SetParameters = (sigParams) => + // { + // sigParams.WithCreatedNow(); + // }; + //}) + //.ValidateOnStart() + //.Services + //.AddHttpClient("ActivityPub", (serviceProvider, client) => + //{ + // client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("NSignSample", "0.1-beta")); + //}).Services; + //.AddSingleton(new RsaPssSha512SignatureProvider( + // new X509Certificate2(@"path\to\certificate.pfx", "PasswordForPfx"), + // "my-cert")); + } + public static IServiceCollection socialPubAuthServicesConfiguration(this IServiceCollection service, IConfiguration configuration) + { + return service + .AddAuthorization(options => + { + options.AddPolicy(Policies.IsUser, Extensions.Extensions.IsUserPolicy()); + options.AddPolicy(Policies.IsAdmin, Extensions.Extensions.IsAdminPolicy()); + options.AddPolicy(Policies.IsModerator, Extensions.Extensions.IsModeratorPolicy()); + }) + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddSocialPubAuth(configuration) + .Services + .AddSingleton() + .AddSingleton(); + } + public static IServiceCollection socialPubInternalizationConfiguration(this IServiceCollection service, IConfiguration configuration) + { + return service + .AddLocalization() + .AddSingleton(); + } + + public static IServiceCollection socialPubOptimizationConfiguration(this IServiceCollection service) + { + return service.AddResponseCompression(opts => + { + opts.Providers.Add(); + opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" }); + }); + } + + public static IServiceCollection socialPubDataBaseConfiguration(this IServiceCollection service) + { + return service.AddSingleton(); + } + + public static IServiceCollection socialPubServicesConfiguration(this IServiceCollection service) + { + return service + .AddTransient() + .AddTransient() + .AddTransient() + .AddSingleton() + .AddHttpContextAccessor() + .AddMemoryCache() + .AddSingleton(); + } + + public static IServiceCollection socialPubMiddlewareConfiguration(this IServiceCollection service) + { + return service + .AddEndpointsApiExplorer() + .AddSwaggerGen(c => + { + c.AddSecurityDefinition("Bearer", new() + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + c.AddSecurityRequirement(new() + { + { + new() + { + Reference = new() + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[]{} + } + }); + }) + .AddControllers(options => { options.Filters.Add(); }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.IgnoreReadOnlyFields = false; + options.JsonSerializerOptions.IgnoreReadOnlyProperties = false; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }).Services; + } + + public static IServiceCollection socialPubCORSConfiguration(this IServiceCollection service) + { + return service.AddCors(options => + { + options.DefaultPolicyName = "DefaultCORS"; + options.AddDefaultPolicy(configure => + { + configure.AllowAnyMethod() + .AllowAnyHeader() + .AllowAnyOrigin() + .AllowAnyMethod() + .DisallowCredentials(); + }); + }); + } + + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubActivity.cs b/SocialPub/Models/ActivityPub/ActivityPubActivity.cs new file mode 100644 index 0000000..c187119 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubActivity.cs @@ -0,0 +1,44 @@ +using SocialPub.Models.ActivityPub.Extra; + +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubActivity))] + public class ActivityPubActivity : ActivityPubObject + { + [JsonPropertyName("actor")] + public ActivityPubActor Actor { get; set; } + + [JsonPropertyName("object")] + public ActivityPubObject Object { get; set; } + + [JsonPropertyName("target")] + public ActivityPubObject Target { get; set; } + + [JsonPropertyName("result")] + public ActivityPubResult Result { get; set; } + + [JsonPropertyName("origin")] + public ActivityPubOrigin Origin { get; set; } + + [JsonPropertyName("instrument")] + public ActivityPubInstrument Instrument { get; set; } + + [JsonIgnore] + public bool NeedsObjectForInbox => Type is + ObjectType.Create or + ObjectType.Update or + ObjectType.Delete or + ObjectType.Follow or + ObjectType.Add or + ObjectType.Remove or + ObjectType.Like or + ObjectType.Block or + ObjectType.Undo; + [JsonIgnore] + public bool NeedsTargetForInbox => Type is + ObjectType.Add or + ObjectType.Remove; + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubActor.cs b/SocialPub/Models/ActivityPub/ActivityPubActor.cs new file mode 100644 index 0000000..1fd7008 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubActor.cs @@ -0,0 +1,79 @@ +using SocialPub.Models.ActivityPub.Extra; + +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubActor))] + public class ActivityPubActor : ActivityPubObject + { + [JsonPropertyName("type")] + public new ObjectType? Type => ObjectType.Person; + + [JsonPropertyName("inbox")] + public string Inbox { get; set; } + [JsonPropertyName("outbox")] + public string Outbox { get; set; } + + [JsonPropertyName("url")] + public string ProfileURL { get; set; } + [JsonPropertyName("preferredUsername")] + public string PreferredUsername { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("summary")] + public string Summary { get; set; } + [JsonPropertyName("icon")] + public ActivityPubIcon Icon { get; set; } + [JsonPropertyName("image")] + public ActivityPubIcon Thumbnail { get; set; } + + [JsonPropertyName("manuallyApprovesFollowers")] + public bool ManuallyApprovesFollowers { get; set; } = false; + [JsonPropertyName("discoverable")] + public bool Discoverable { get; set; } = true; + + [JsonPropertyName("publicKey")] + public ActivityPubPublicKey PublicKey { get; set; } + + [JsonPropertyName("endpoints")] + public ActivityPubActorEndpoints Endpoints { get; set; } + + [JsonPropertyName("attachment")] + public IEnumerable Attachment { get; set; } = Enumerable.Empty(); + + [JsonPropertyName("tag")] + public IEnumerable Tags => Enumerable.Empty(); + } + + public class ActivityPubAttachment + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("value")] + public string Value { get; set; } + } + + public class ActivityPubActorEndpoints + { + [JsonPropertyName("sharedInbox")] + public string SharedInboxURL { get; set; } + [JsonPropertyName("oauthAuthorizationEndpoint")] + public string OAuthAuthorizationEndpoint { get; set; } + + [JsonExtensionData] + public Dictionary OtherData { get; set; } + } + + public class ActivityPubPublicKey + { + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("owner")] + public string Owner { get; set; } + [JsonPropertyName("publicKeyPem")] + public string PublicKeyPem { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubCollection.cs b/SocialPub/Models/ActivityPub/ActivityPubCollection.cs new file mode 100644 index 0000000..8ca7496 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubCollection.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubCollection))] + public class ActivityPubCollection : ActivityPubObject + { + [JsonPropertyName("type")] + public new ObjectType Type => ObjectType.Collection; + + [JsonPropertyName("current")] + public string RecentlyUpdatedItem { get; set; } + [JsonPropertyName("first")] + public string FirstItem { get; set; } + [JsonPropertyName("last")] + public string LastItem { get; set; } + + [JsonPropertyName("items")] + public List Items { get; set; } + + [JsonPropertyName("totalItems")] + public int TotalItems => Items.Count; + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubCollectionPage.cs b/SocialPub/Models/ActivityPub/ActivityPubCollectionPage.cs new file mode 100644 index 0000000..477c67e --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubCollectionPage.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubCollectionPage))] + public class ActivityPubCollectionPage : ActivityPubCollection + { + [JsonPropertyName("type")] + public new ObjectType Type => ObjectType.CollectionPage; + + [JsonPropertyName("partOf")] + public string BaseCollectionLink { get; set; } + [JsonPropertyName("next")] + public string NextCollectionLink { get; set; } + [JsonPropertyName("prev")] + public string PreviousCollectionLink { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubLink.cs b/SocialPub/Models/ActivityPub/ActivityPubLink.cs new file mode 100644 index 0000000..f65245c --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubLink.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubLink))] + public class ActivityPubLink : ActivityPubObject + { + [JsonPropertyName("id")] + public new ObjectType Type { get; set; } = ObjectType.Mention; + + [JsonPropertyName("href")] + public string Href { get; set; } + [JsonPropertyName("preview")] + public string Preview { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("rel")] + public string Relation { get; set; } + [JsonPropertyName("hreflang")] + public string HrefLang { get; set; } + + [JsonPropertyName("height")] + public int Height { get; set; } + [JsonPropertyName("width")] + public int Width { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubObject.cs b/SocialPub/Models/ActivityPub/ActivityPubObject.cs new file mode 100644 index 0000000..f6c0cd6 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubObject.cs @@ -0,0 +1,126 @@ +using SocialPub.Models.ActivityPub.Extra; + +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubObject))] + public partial class ActivityPubObject + { + [JsonPropertyName("@context")] + public List Context => new() + { + "https://www.w3.org/ns/activitystreams", + "https://{0}/schemas/litepub-0.1.jsonld", + new ActivityPubContextLanguage() + }; + + [JsonPropertyName("id")] + public string Id { get; set; } + [JsonPropertyName("type")] + public ObjectType? Type { get; set; } + + [JsonPropertyName("mediaType")] + public string MediaType { get; set; } + + [JsonPropertyName("published")] + public DateTime? Published { get; set; } + + [JsonPropertyName("updated")] + public DateTime? Updated { get; set; } + + [JsonExtensionData] + public Dictionary OtherData { get; set; } + + [JsonPropertyName("source")] + public ActivityPubSource Source { get; set; } + + //and part of link + [JsonPropertyName("to")] + public string[] To { get; set; } + [JsonPropertyName("cc")] + public string[] Cc { get; set; } + [JsonPropertyName("bcc")] + public string[] Bcc { get; set; } + [JsonPropertyName("bto")] + public string[] Bto { get; set; } + + [JsonPropertyName("audience")] + public ActivityPubAudience Audience { get; set; } + + [JsonIgnore] + public MacroType MacroType => Type switch + { + ObjectType.Article or + ObjectType.Audio or + ObjectType.Document or + ObjectType.Event or + ObjectType.Image or + ObjectType.Note or + ObjectType.Page or + ObjectType.Place or + ObjectType.Profile or + ObjectType.Relationship or + ObjectType.Tombstone or + ObjectType.Video => MacroType.Object, + + ObjectType.Mention => MacroType.Link, + + ObjectType.Application or + ObjectType.Group or + ObjectType.Organization or + ObjectType.Person or + ObjectType.Service => MacroType.Actor, + + ObjectType.Accept or + ObjectType.Add or + ObjectType.Announce or + ObjectType.Arrive or + ObjectType.Block or + ObjectType.Create or + ObjectType.Delete or + ObjectType.Dislike or + ObjectType.Flag or + ObjectType.Follow or + ObjectType.Ignore or + ObjectType.Invite or + ObjectType.Join or + ObjectType.Leave or + ObjectType.Like or + ObjectType.Listen or + ObjectType.Move or + ObjectType.Offer or + ObjectType.Question or + ObjectType.Reject or + ObjectType.Read or + ObjectType.Remove or + ObjectType.TentativeReject or + ObjectType.TentativeAccept or + ObjectType.Travel or + ObjectType.Undo or + ObjectType.Update or + ObjectType.View => MacroType.Activity, + + ObjectType.Collection => MacroType.Collection, + ObjectType.CollectionPage => MacroType.CollectionPage, + ObjectType.OrderedCollection => MacroType.OrderedCollection, + ObjectType.OrderedCollectionPage => MacroType.OrderedCollectionPage, + + _ => MacroType.Unknown + }; + } + + public class ActivityPubContextLanguage + { + [JsonPropertyName("@language")] + public string Language { get; set; } = "und"; + } + + public class ActivityPubSource + { + [JsonPropertyName("content")] + public string Content { get; set; } + [JsonPropertyName("mediaType")] + public string MediaType { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubOrderedCollection.cs b/SocialPub/Models/ActivityPub/ActivityPubOrderedCollection.cs new file mode 100644 index 0000000..77f959d --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubOrderedCollection.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubOrderedCollection))] + public class ActivityPubOrderedCollection : ActivityPubCollection + { + [JsonPropertyName("type")] + public new ObjectType Type => ObjectType.OrderedCollection; + + [JsonPropertyName("orderedItems")] + public List OrderedItems { get; set; } + + [JsonPropertyName("totalItems")] + public new int TotalItems => OrderedItems?.Count ?? default; + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubOrderedCollectionPage.cs b/SocialPub/Models/ActivityPub/ActivityPubOrderedCollectionPage.cs new file mode 100644 index 0000000..60a1981 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubOrderedCollectionPage.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubOrderedCollectionPage))] + public class ActivityPubOrderedCollectionPage : ActivityPubCollection + { + [JsonPropertyName("type")] + public new ObjectType Type => ObjectType.OrderedCollectionPage; + + [JsonPropertyName("partOf")] + public string BaseCollectionLink { get; set; } + [JsonPropertyName("next")] + public string NextCollectionLink { get; set; } + [JsonPropertyName("prev")] + public string PreviousCollectionLink { get; set; } + + [JsonPropertyName("startIndex")] + public uint? StartIndex { get; set; } + + [JsonPropertyName("orderedItems")] + public List OrderedItems { get; set; } + + [JsonPropertyName("totalItems")] + public new int TotalItems => OrderedItems?.Count ?? default; + } +} diff --git a/SocialPub/Models/ActivityPub/ActivityPubTombstone.cs b/SocialPub/Models/ActivityPub/ActivityPubTombstone.cs new file mode 100644 index 0000000..5235fb0 --- /dev/null +++ b/SocialPub/Models/ActivityPub/ActivityPubTombstone.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub +{ + [JsonSerializable(typeof(ActivityPubTombstone))] + public class ActivityPubTombstone : ActivityPubObject + { + [JsonPropertyName("formerType")] + public ObjectType FormerType { get; set; } = ObjectType.Unknown; + + [JsonPropertyName("deleted")] + public DateTime Deleted { get; set; } = DateTime.UtcNow; + } +} diff --git a/SocialPub/Models/ActivityPub/Extra/ActivityPubAudience.cs b/SocialPub/Models/ActivityPub/Extra/ActivityPubAudience.cs new file mode 100644 index 0000000..eefd7e4 --- /dev/null +++ b/SocialPub/Models/ActivityPub/Extra/ActivityPubAudience.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub.Extra +{ + [JsonSerializable(typeof(ActivityPubPublicKey))] + public class ActivityPubAudience + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/Extra/ActivityPubIcon.cs b/SocialPub/Models/ActivityPub/Extra/ActivityPubIcon.cs new file mode 100644 index 0000000..2e9c4f5 --- /dev/null +++ b/SocialPub/Models/ActivityPub/Extra/ActivityPubIcon.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub.Extra +{ + [JsonSerializable(typeof(ActivityPubPublicKey))] + public class ActivityPubIcon + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("url")] + public string URL { get; set; } + [JsonPropertyName("width")] + public int Width { get; set; } + [JsonPropertyName("height")] + public int Height { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs b/SocialPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs new file mode 100644 index 0000000..22584bf --- /dev/null +++ b/SocialPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub.Extra +{ + [JsonSerializable(typeof(ActivityPubPublicKey))] + public class ActivityPubInstrument + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs b/SocialPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs new file mode 100644 index 0000000..ecb4b4c --- /dev/null +++ b/SocialPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub.Extra +{ + [JsonSerializable(typeof(ActivityPubPublicKey))] + public class ActivityPubOrigin + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/Extra/ActivityPubResult.cs b/SocialPub/Models/ActivityPub/Extra/ActivityPubResult.cs new file mode 100644 index 0000000..50ebb17 --- /dev/null +++ b/SocialPub/Models/ActivityPub/Extra/ActivityPubResult.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace SocialPub.Models.ActivityPub.Extra +{ + [JsonSerializable(typeof(ActivityPubPublicKey))] + public class ActivityPubResult + { + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/SocialPub/Models/ActivityPub/MacroType.cs b/SocialPub/Models/ActivityPub/MacroType.cs new file mode 100644 index 0000000..7e1d356 --- /dev/null +++ b/SocialPub/Models/ActivityPub/MacroType.cs @@ -0,0 +1,17 @@ +namespace SocialPub.Models.ActivityPub +{ + public enum MacroType + { + Object, + Link, + Actor, + Activity, + + Collection, + OrderedCollection, + CollectionPage, + OrderedCollectionPage, + + Unknown + } +} diff --git a/SocialPub/Models/ActivityPub/ObjectType.cs b/SocialPub/Models/ActivityPub/ObjectType.cs new file mode 100644 index 0000000..50bec4f --- /dev/null +++ b/SocialPub/Models/ActivityPub/ObjectType.cs @@ -0,0 +1,63 @@ +namespace SocialPub.Models.ActivityPub +{ + public enum ObjectType + { + //Object types + Article, + Audio, + Document, + Event, + Image, + Note, + Page, + Place, + Profile, + Relationship, + Tombstone, + Video, + //Link types + Mention, + //Actor + Application, + Group, + Organization, + Person, + Service, + //Activity + Accept, + Add, + Announce, + Arrive, + Block, + Create, + Delete, + Dislike, + Flag, + Follow, + Ignore, + Invite, + Join, + Leave, + Like, + Listen, + Move, + Offer, + Question, + Reject, + Read, + Remove, + TentativeReject, + TentativeAccept, + Travel, + Undo, + Update, + View, + //Collections + Collection, + OrderedCollection, + CollectionPage, + OrderedCollectionPage, + //NULL or default + Unknown + } +} diff --git a/SocialPub/Models/AppConfiguration.cs b/SocialPub/Models/AppConfiguration.cs new file mode 100644 index 0000000..b6d6b9f --- /dev/null +++ b/SocialPub/Models/AppConfiguration.cs @@ -0,0 +1,21 @@ +using MongoDB.Entities; + +using SocialPub.Models.Email; +using SocialPub.StaticServices; + +namespace SocialPub.Models +{ + public class AppConfiguration : Entity + { + public bool RequiresFirstTimeSetup { get; set; } = true; + public string Version { get; set; } + public int MaxAllowedUploadFiles { get; set; } + public int MaxAllowedFileSize { get; set; } + public string ValidDiscussionFilesTypes { get; set; } + public List SupportedLanguages { get; set; } = new(); + public string BackendBaseAddress { get; set; } + public EmailConfiguration EmailConfiguration { get; set; } + public HashingOptions HashingOptions { get; set; } + public DateTime LastUpdateDate { get; set; } = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/SocialPub/Models/Data/Language.cs b/SocialPub/Models/Data/Language.cs new file mode 100644 index 0000000..9b492e3 --- /dev/null +++ b/SocialPub/Models/Data/Language.cs @@ -0,0 +1,11 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.Data +{ + public class Language : Entity + { + public string EnglishName { get; set; } + public string NativeName { get; set; } + public string International2Code { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub/Models/Email/EmailConfiguration.cs b/SocialPub/Models/Email/EmailConfiguration.cs new file mode 100644 index 0000000..0b72209 --- /dev/null +++ b/SocialPub/Models/Email/EmailConfiguration.cs @@ -0,0 +1,11 @@ +namespace SocialPub.Models.Email +{ + public class EmailConfiguration + { + public string SmtpServer { get; set; } + public int SmtpPort { get; set; } + public bool UseSSL { get; set; } + public string SmtpUsername { get; set; } + public string SmtpPassword { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub/Models/MongoSettings.cs b/SocialPub/Models/MongoSettings.cs new file mode 100644 index 0000000..c5e7b04 --- /dev/null +++ b/SocialPub/Models/MongoSettings.cs @@ -0,0 +1,9 @@ +namespace SocialPub.Models +{ + public class MongoSettings + { + public string Database { get; set; } + public string LogsDatabase { get; set; } + public string ConnectionString { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub/Models/Post/DmPost.cs b/SocialPub/Models/Post/DmPost.cs new file mode 100644 index 0000000..527b1d8 --- /dev/null +++ b/SocialPub/Models/Post/DmPost.cs @@ -0,0 +1,22 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.Post +{ + public class DmPost : Entity + { + public string GroupUserId { get; set; } + public string AnsweringToPostId { get; set; } + public string GroupId { get; set; } + public string Title { get; set; } + public string Text { get; set; } + public List Media { get; set; } = new(); + public List Location { get; set; } = new(); + public bool HasContentWarning { get; set; } = false; + + public bool IsFederatedCopy { get; set; } + + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime? UpdateDate { get; set; } = DateTime.UtcNow; + public string UpdateReason { get; set; } + } +} diff --git a/SocialPub/Models/Post/Post.cs b/SocialPub/Models/Post/Post.cs new file mode 100644 index 0000000..3eb32cb --- /dev/null +++ b/SocialPub/Models/Post/Post.cs @@ -0,0 +1,23 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.Post +{ + public class Post : Entity + { + public string GroupUserId { get; set; } + public string AnsweringToPostId { get; set; } + public string GroupId { get; set; } + public string Title { get; set; } + public string Text { get; set; } + public List Media { get; set; } = new(); + public List Location { get; set; } = new(); + public float RangeKm { get; set; } = 5.0f; + public bool HasContentWarning { get; set; } = false; + + public bool IsFederatedCopy { get; set; } + + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime? UpdateDate { get; set; } = DateTime.UtcNow; + public string UpdateReason { get; set; } + } +} diff --git a/SocialPub/Models/Post/PostBoost.cs b/SocialPub/Models/Post/PostBoost.cs new file mode 100644 index 0000000..6e6635d --- /dev/null +++ b/SocialPub/Models/Post/PostBoost.cs @@ -0,0 +1,12 @@ +namespace SocialPub.Models.Post +{ + public class PostBoost + { + public string PostId { get; set; } + public string GroupUserId { get; set; } + + public bool IsFederatedCopy { get; set; } + + public DateTime CreationDate { get; set; } + } +} diff --git a/SocialPub/Models/Post/PostMedia.cs b/SocialPub/Models/Post/PostMedia.cs new file mode 100644 index 0000000..edc0412 --- /dev/null +++ b/SocialPub/Models/Post/PostMedia.cs @@ -0,0 +1,12 @@ +namespace SocialPub.Models.Post +{ + public class PostMedia + { + public Guid Id { get; set; } = Guid.NewGuid(); + public string ContentType { get; set; } + public string FileName { get; set; } + public string Extension { get; set; } + public string Path { get; set; } + public string URL { get; set; } + } +} diff --git a/SocialPub/Models/User/Avatar.cs b/SocialPub/Models/User/Avatar.cs new file mode 100644 index 0000000..d5fb3c9 --- /dev/null +++ b/SocialPub/Models/User/Avatar.cs @@ -0,0 +1,102 @@ +using MongoDB.Entities; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.Models.User +{ + public class Avatar : Entity + { + public string Url { get; set; }//url + public string Name { get; set; }//name + public string UserName { get; set; }//preferredUsername + public string Biography { get; set; }//summary + + public Dictionary SharedPersonalContacts { get; set; } = new(); + public string PrivateKey { get; set; } + public string PublicKey { get; set; } + public AvatarAccountState AccountState { get; set; } = AvatarAccountState.Normal; + public AvatarSettings Settings { get; set; } = new(); + public Dictionary Fields { get; set; } = new(); + + public string Domain { get; set; } + public string Note { get; set; } + public bool IsDiscoverable { get; set; } = true; + + public string InboxURL { get; set; } + public string OutboxURL { get; set; } + public string MovedToURL { get; set; } + public string PictureURL { get; set; }//icon + public string ThumbnailURL { get; set; }//image + public AvatarType AvatarType { get; set; } = AvatarType.Person; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? SilencedAt { get; set; } + public DateTime? SuspendedAt { get; set; } + public DateTime? BannedAt { get; set; } + public DateTime? DeletionAt { get; set; } + } + + public class ForeignAvatar : Entity + { + public string Url { get; set; }//url + public string Name { get; set; }//name + public string UserName { get; set; }//preferredUsername + public string Biography { get; set; }//summary + + public Dictionary SharedPersonalContacts { get; set; } = new(); + public string PrivateKey { get; set; } + public string PublicKey { get; set; } + public AvatarSettings Settings { get; set; } = new(); + public Dictionary Fields { get; set; } = new(); + + public string Domain { get; set; } + public string Note { get; set; } + public bool IsDiscoverable { get; set; } = true; + + public string InboxURL { get; set; } + public string OutboxURL { get; set; } + public string MovedToURL { get; set; } + public string PictureURL { get; set; }//icon + public string ThumbnailURL { get; set; }//image + public AvatarType AvatarType { get; set; } = AvatarType.Person; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? SilencedAt { get; set; } + public DateTime? SuspendedAt { get; set; } + public DateTime? BannedAt { get; set; } + public DateTime? DeletionAt { get; set; } + } + + public class AvatarSettings + { + public string LanguageCode { get; set; } = "en-GB"; + + public short IconsThemeIndexColour { get; set; } = 25; + public short LightThemeIndexColour { get; set; } = 25; + public short DarkThemeIndexColour { get; set; } = 215; + public bool PreferSystemTheming { get; set; } = true; + public bool ThemeIsDarkMode { get; set; } = false; + public bool ThemeIsDarkGray { get; set; } = false; + public bool ThemeIsLightGray { get; set; } = true; + } + + public enum AvatarType + { + Application, + Group, + Organization, + Person, + Service + } + + public enum AvatarAccountState + { + Normal, + Silenced, + Suspended, + Banned, + Deleted + } +} diff --git a/SocialPub/Models/User/EmailRecovery.cs b/SocialPub/Models/User/EmailRecovery.cs new file mode 100644 index 0000000..973853a --- /dev/null +++ b/SocialPub/Models/User/EmailRecovery.cs @@ -0,0 +1,12 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.User +{ + public class EmailRecovery : Entity + { + public string RootUserId { get; set; } + public string RecoveryCode { get; set; } + public string RequestIP { get; set; } + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/SocialPub/Models/User/RootToAvatar.cs b/SocialPub/Models/User/RootToAvatar.cs new file mode 100644 index 0000000..5d85646 --- /dev/null +++ b/SocialPub/Models/User/RootToAvatar.cs @@ -0,0 +1,10 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.User +{ + public class RootToAvatar : Entity + { + public string RootId { get; set; } + public string AvatarId { get; set; } + } +} diff --git a/SocialPub/Models/User/RootUser.cs b/SocialPub/Models/User/RootUser.cs new file mode 100644 index 0000000..1c61d62 --- /dev/null +++ b/SocialPub/Models/User/RootUser.cs @@ -0,0 +1,48 @@ +using MongoDB.Entities; + +using System.ComponentModel.DataAnnotations; + +namespace SocialPub.Models.User +{ + public class RootUser : Entity + { + [StringLength(32)] + public string UserName { get; set; } + [EmailAddress] + public string Email { get; set; } + public bool IsEmailValidated { get; set; } = false; + public bool IsBanned { get; set; } = false; + public string HashedPassword { get; set; } + public List Policies { get; set; } = new() { ClientModels.Policies.IsUser }; + public List Contacts { get; set; } = new(); + public RootUserSettings Settings { get; set; } = new(); + + public string ResetPasswordToken { get; set; } + public DateTime? ResetPasswordTokenSentAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + public DateTime? DeletedAt { get; set; } + } + + public class ContactItem + { + public string Type { get; set; } + public string Contact { get; set; } + public string Note { get; set; } + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + } + + public class RootUserSettings + { + public string LanguageCode { get; set; } = "en-GB"; + + public short IconsThemeIndexColour { get; set; } = 25; + public short LightThemeIndexColour { get; set; } = 25; + public short DarkThemeIndexColour { get; set; } = 215; + public bool PreferSystemTheming { get; set; } = true; + public bool ThemeIsDarkMode { get; set; } = false; + public bool ThemeIsDarkGray { get; set; } = false; + public bool ThemeIsLightGray { get; set; } = true; + } +} diff --git a/SocialPub/Models/User/RootUserNote.cs b/SocialPub/Models/User/RootUserNote.cs new file mode 100644 index 0000000..b60d648 --- /dev/null +++ b/SocialPub/Models/User/RootUserNote.cs @@ -0,0 +1,14 @@ +using MongoDB.Entities; + +namespace SocialPub.Models.User +{ + public class RootUserNote : Entity + { + public string RootUserId { get; set; } + public string AvatarUserId { get; set; } + public string Note { get; set; } + + public string CreationDate { get; set; } + public string UpdateDate { get; set; } + } +} diff --git a/SocialPub/Models/User/UserPolicyType.cs b/SocialPub/Models/User/UserPolicyType.cs new file mode 100644 index 0000000..e296cd6 --- /dev/null +++ b/SocialPub/Models/User/UserPolicyType.cs @@ -0,0 +1,9 @@ +namespace SocialPub.Models.User +{ + public enum UserPolicyType + { + IsUser, + IsModerator, + IsAdmin + } +} diff --git a/SocialPub/Models/socialpub.cs b/SocialPub/Models/socialpub.cs new file mode 100644 index 0000000..6441670 --- /dev/null +++ b/SocialPub/Models/socialpub.cs @@ -0,0 +1,24 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Entities; + +namespace SocialPub.Models.Data +{ + [BsonIgnoreExtraElements] + public class socialpub : Entity + { + public DateTime Timestamp { get; set; } + public string Level { get; set; } + public string RenderedMessage { get; set; } + + [BsonIgnoreIfDefault, BsonIgnoreIfNull] + public DbLogProperties Properties { get; set; } + [BsonIgnoreIfDefault, BsonIgnoreIfNull] + public string Exception { get; set; } + } + + [BsonIgnoreExtraElements] + public class DbLogProperties + { + public string SourceContext { get; set; } + } +} \ No newline at end of file diff --git a/SocialPub/Program.cs b/SocialPub/Program.cs new file mode 100644 index 0000000..8c92b82 --- /dev/null +++ b/SocialPub/Program.cs @@ -0,0 +1,142 @@ +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.Options; + +using MongoDB.Driver; +using MongoDB.Entities; + +using Serilog; + +using SocialPub.Data; +using SocialPub.Extensions; +using SocialPub.Middleware; +using SocialPub.Models; +using SocialPub.Models.Data; +using SocialPub.Services; +using SocialPub.StaticServices; + +try +{ + var builder = WebApplication.CreateBuilder(args); + builder.WebHost.ConfigureKestrel(serverOptions => + { + if (builder.Environment.IsProduction()) + { + serverOptions.ListenLocalhost(6970 + //, options => + //{ + // options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + //} + ); + serverOptions.UseSystemd(); + serverOptions.AddServerHeader = false; + } + }); + builder.Host.UseSerilog((context, config) => + { + config.ReadFrom.Configuration(context.Configuration); + }); + + try + { + builder.Services.socialPubAppSettingsConfiguration(builder.Configuration) + .socialPubWorkersConfiguration() + .socialPubAuthServicesConfiguration(builder.Configuration) + .socialPubInternalizationConfiguration(builder.Configuration) + .socialPubOptimizationConfiguration() + .socialPubDataBaseConfiguration() + .socialPubServicesConfiguration() + .socialPubHTTPSignature(builder.Configuration) + .socialPubCORSConfiguration() + .socialPubMiddlewareConfiguration(); + } + catch (Exception ex) + { + Log.ForContext().Fatal(ex, "{0}.{1}()", nameof(Program), "ConfigureServices"); + throw; + } + + try + { + var mongoSettings = builder.Configuration.GetSection(nameof(MongoSettings)).Get(); + await DB.InitAsync(mongoSettings.Database, MongoClientSettings.FromConnectionString(mongoSettings.ConnectionString)); + var logsConnectionString = builder.Configuration.GetLogsConnectionString(); + await DB.InitAsync(mongoSettings.LogsDatabase, MongoClientSettings.FromConnectionString(logsConnectionString)); + DB.DatabaseFor(mongoSettings.LogsDatabase); + } + catch (Exception ex) + { + Log.ForContext().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}() DB Instantiation"); + throw; + } + + var app = default(WebApplication); + try + { + app = builder.Build(); + } + catch (Exception ex) + { + Log.ForContext().Fatal(ex, "{0}.{1}()", nameof(Program), "Build"); + throw; + } + + try + { + var localizationService = app.Services.GetService(); + if (app.Environment.IsProduction()) + { + app.UseResponseCompression(); + app.UseForwardedHeaders(new() + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + } + else if(app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + app.UseCors("DefaultCORS"); + + app.UseStaticFiles(); + + app.UseRequestLocalization(await localizationService.Get()); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + //app.UseWhen(context => context.Request.Path.StartsWithSegments("/peasants") || + // context.Request.Path.StartsWithSegments("/users"), + // app => app.UseSignatureVerification().UseDigestVerification()); + + app.MapControllers(); + //app.MapFallbackToFile("index.html"); + } + catch (Exception ex) + { + Log.ForContext().Fatal(ex, "{0}.{1}()", nameof(Program), "Use"); + throw; + } + + Log.ForContext().Information($"Starting collAnon at {nameof(Program)}()"); + try + { + var dbClient = app.Services.GetService(typeof(DbEntities)) as DbEntities; + var passwordHasher = app.Services.GetService(typeof(IPasswordHasher)) as IPasswordHasher; + await dbClient.Init(passwordHasher); + } + catch (Exception ex) + { + Log.ForContext().Warning(ex, $"{nameof(Program)}.{nameof(Program)}() DB Init"); + } + + await app.RunAsync(); +} +catch (Exception ex) +{ + Log.ForContext().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}()"); +} + diff --git a/SocialPub/Properties/launchSettings.json b/SocialPub/Properties/launchSettings.json new file mode 100644 index 0000000..2ba2cf5 --- /dev/null +++ b/SocialPub/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55406", + "sslPort": 44347 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7195;http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SocialPub/Resources/GenericRes.Designer.cs b/SocialPub/Resources/GenericRes.Designer.cs new file mode 100644 index 0000000..42858ba --- /dev/null +++ b/SocialPub/Resources/GenericRes.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// 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 SocialPub.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", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class GenericRes { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal GenericRes() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SocialPub.Resources.GenericRes", typeof(GenericRes).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Forbidden: {0}. + /// + public static string Forbidden___0_ { + get { + return ResourceManager.GetString("Forbidden: {0}", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unauthorized: {0}. + /// + public static string Unauthorized___0_ { + get { + return ResourceManager.GetString("Unauthorized: {0}", resourceCulture); + } + } + } +} diff --git a/SocialPub/Resources/GenericRes.resx b/SocialPub/Resources/GenericRes.resx new file mode 100644 index 0000000..5bf5480 --- /dev/null +++ b/SocialPub/Resources/GenericRes.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Forbidden: {0} + + + Unauthorized: {0} + + \ No newline at end of file diff --git a/SocialPub/Services/ActivityPubClient.cs b/SocialPub/Services/ActivityPubClient.cs new file mode 100644 index 0000000..84f2891 --- /dev/null +++ b/SocialPub/Services/ActivityPubClient.cs @@ -0,0 +1,12 @@ +namespace SocialPub.Services +{ + public class ActivityPubClient : HttpClient + { + //readonly HttpClient client; + + //public ActivityPubClient(HttpClient client) + //{ + // this.client = client; + //} + } +} diff --git a/SocialPub/Services/AppConfigurationService.cs b/SocialPub/Services/AppConfigurationService.cs new file mode 100644 index 0000000..86d8199 --- /dev/null +++ b/SocialPub/Services/AppConfigurationService.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Options; + +using MongoDB.Entities; + +using SocialPub.Models; +using SocialPub.StaticServices; + +namespace SocialPub.Services +{ + public class AppConfigurationService + { + public AppConfiguration AppConfiguration; + readonly DbEntities _dbEntities; + AppConfiguration _appConfigurationFromFile; + public AppConfigurationService(DbEntities dbEntities, IOptionsMonitor appConfiguration) + { + _dbEntities = dbEntities; + _appConfigurationFromFile = appConfiguration.CurrentValue; + } + + public async Task Init() + { + var dbAppConfiguration = await _dbEntities.AppConfiguration.ExecuteFirstAsync(); + if (dbAppConfiguration != default) + { + if (AppConfiguration == default || AppConfiguration.LastUpdateDate != dbAppConfiguration.LastUpdateDate) + AppConfiguration = dbAppConfiguration; + return; + } + + dbAppConfiguration = _appConfigurationFromFile; + await dbAppConfiguration.SaveAsync(); + AppConfiguration = dbAppConfiguration; + } + } +} diff --git a/SocialPub/Services/ClientToServer/Private/IPrivateAvatarUsersService.cs b/SocialPub/Services/ClientToServer/Private/IPrivateAvatarUsersService.cs new file mode 100644 index 0000000..5918c1c --- /dev/null +++ b/SocialPub/Services/ClientToServer/Private/IPrivateAvatarUsersService.cs @@ -0,0 +1,13 @@ +using SocialPub.ClientModels; +using SocialPub.ClientModels.User.Avatar; +using SocialPub.Models.User; + +namespace SocialPub.Services.ClientToServer.Private +{ + public interface IPrivateAvatarUsersService + { + ValueTask UpdateAvatar(UpdateAvatarForm form); + ValueTask InsertAvatar(InsertAvatarForm form); + ValueTask UpdateAvatarSettings(UpdateAvatarSettings form); + } +} diff --git a/SocialPub/Services/ClientToServer/Public/DataService.cs b/SocialPub/Services/ClientToServer/Public/DataService.cs new file mode 100644 index 0000000..49dd2f2 --- /dev/null +++ b/SocialPub/Services/ClientToServer/Public/DataService.cs @@ -0,0 +1,37 @@ +using SocialPub.Models.Data; +using SocialPub.StaticServices; + +namespace SocialPub.Services.ClientToServer.Public +{ + public class DataService : IDataService + { + readonly DbEntities DbEntities; + readonly AppConfigurationService AppConfigurationService; + + public DataService(DbEntities dbCollections, AppConfigurationService appConfigurationService) + { + DbEntities = dbCollections; + AppConfigurationService = appConfigurationService; + } + + public string GetCurrentVersion() => + AppConfigurationService.AppConfiguration.Version; + + public async Task> GetLanguages(CancellationToken cancellationToken) + { + return (await DbEntities.Languages + .Match(l => AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(l.International2Code)) + .ExecuteAsync(cancellationToken)).OrderBy(l => l.NativeName).ToList(); + } + + public async Task IsValidLanguageId(string languageId) + { + var anyLanguageWithId = await DbEntities.Languages.Match(l => l.ID == languageId).ExecuteAnyAsync(); + if (!anyLanguageWithId) + return false; + var language2Code = (await DbEntities.Languages + .Match(l => l.ID == languageId).ExecuteFirstAsync())?.International2Code; + return AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(language2Code); + } + } +} \ No newline at end of file diff --git a/SocialPub/Services/ClientToServer/Public/IDataService.cs b/SocialPub/Services/ClientToServer/Public/IDataService.cs new file mode 100644 index 0000000..bcc47ff --- /dev/null +++ b/SocialPub/Services/ClientToServer/Public/IDataService.cs @@ -0,0 +1,13 @@ +using SocialPub.Models.Data; + +namespace SocialPub.Services.ClientToServer.Public +{ + public interface IDataService + { + string GetCurrentVersion(); + + Task> GetLanguages(CancellationToken cancellationToken); + + Task IsValidLanguageId(string languageId); + } +} \ No newline at end of file diff --git a/SocialPub/Services/ClientToServer/Public/IPublicAvatarUsersService.cs b/SocialPub/Services/ClientToServer/Public/IPublicAvatarUsersService.cs new file mode 100644 index 0000000..2407400 --- /dev/null +++ b/SocialPub/Services/ClientToServer/Public/IPublicAvatarUsersService.cs @@ -0,0 +1,54 @@ +using SocialPub.ClientModels; +using SocialPub.Models.User; +using SocialPub.StaticServices; + +namespace SocialPub.Services.ClientToServer.Public +{ + public interface IPublicAvatarUsersService + { + Task GetAvatar(UserPolicyType userPolicyType, string actor); + Task GetAvatars(UserPolicyType userPolicyType); + Task GetRootAvatars(); + } + + public class PublicAvatarUsersService : IPublicAvatarUsersService + { + readonly DbEntities _dbEntities; + + public PublicAvatarUsersService(DbEntities dbEntities) + { + _dbEntities = dbEntities; + } + + public async Task GetAvatar(UserPolicyType userPolicyType, string actor) + { + var result = new WebResult(); + + var query = _dbEntities.Avatars; + + switch (userPolicyType) + { + case UserPolicyType.IsUser: + query.Match(a => !a.DeletionDate.HasValue && a.UserName == actor); + break; + case UserPolicyType.IsModerator: + query.Match(a => !a.DeletionDate.HasValue && a.UserName == actor); + break; + case UserPolicyType.IsAdmin: + break; + } + + return result; + } + + public Task GetAvatars(UserPolicyType userPolicyType) + { + throw new NotImplementedException(); + } + + public Task GetRootAvatars() + { + throw new NotImplementedException(); + } + } +} diff --git a/SocialPub/Services/IRootUsersService.cs b/SocialPub/Services/IRootUsersService.cs new file mode 100644 index 0000000..7ad8518 --- /dev/null +++ b/SocialPub/Services/IRootUsersService.cs @@ -0,0 +1,34 @@ +#pragma warning disable 8625 + +using SocialPub.ClientModels; +using SocialPub.ClientModels.User; + +namespace SocialPub.Services +{ + public interface IRootUsersService + { + Task LoginAsync(LoginForm loginForm, string invitationCode = default, bool isPasswordRequired = false); + + Task SignUpAsync(LoginForm signUpForm, string invitationCode = default, bool isPasswordRequired = false); + + Task BanUserAsync(UsersIds usersIds); + + Task UnbanUserAsync(UsersIds usersIds); + + Task RemoveUserAsync(UsersIds usersIds); + + Task UpdateUserAsync(UserForm userEmailForm, string userId); + + Task UpdateUserPasswordAsync(UserPasswordForm userPasswordForm, string userId); + + Task GetUserSettingsAsync(string userId, LoginForm loginForm = default); + + Task UpdateUserSettingsAsync(ViewUserSettings userSettings, string userId); + + Task SetupAndSendRecoveryEmail(PasswordRecoveryForm passwordRecoveryForm, string host); + + Task IsValidRecoveryCode(string recoveryCode); + + Task ChangePassword(NewPasswordForm newPasswordForm); + } +} \ No newline at end of file diff --git a/SocialPub/Services/JwtEvents.cs b/SocialPub/Services/JwtEvents.cs new file mode 100644 index 0000000..8f4303e --- /dev/null +++ b/SocialPub/Services/JwtEvents.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Localization; + +using SocialPub.ClientModels; +using SocialPub.Resources; + +using System.Text; +using System.Text.Json; + +namespace SocialPub.Services +{ + public class JwtEvents : JwtBearerEvents + { + ILogger _logger { get; set; } + const string contentType = "application/json"; + + public override async Task AuthenticationFailed(AuthenticationFailedContext context) + { + try + { + var localizer = context.HttpContext.RequestServices.GetRequiredService>(); + var webResult = new WebResult().Invalidate(localizer["Unauthorized: {0}", context.Exception], StatusCodes.Status401Unauthorized); + context.Response.ContentType = contentType; + await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult))); + } + catch (Exception ex) + { + _logger = context.HttpContext.RequestServices.GetRequiredService>(); + _logger.LogError(ex, "Error at AuthenticationFailed()"); + } + } + + public override async Task Forbidden(ForbiddenContext context) + { + try + { + var localizer = context.HttpContext.RequestServices.GetRequiredService>(); + var webResult = new WebResult().Invalidate(localizer["Forbidden: {0}", context.Result.None ? "N/A" : context.Result.Failure?.Message ?? "N/A"], StatusCodes.Status403Forbidden); + context.Response.ContentType = contentType; + await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult))); + } + catch (Exception ex) + { + _logger = context.HttpContext.RequestServices.GetRequiredService>(); + _logger.LogError(ex, "Error at AuthenticationFailed()"); + } + } + } +} \ No newline at end of file diff --git a/SocialPub/Services/RequestLocalizationOptionsService.cs b/SocialPub/Services/RequestLocalizationOptionsService.cs new file mode 100644 index 0000000..473cfa2 --- /dev/null +++ b/SocialPub/Services/RequestLocalizationOptionsService.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Localization; + +using SocialPub.StaticServices; + +using System.Globalization; + +namespace SocialPub.Services +{ + public class RequestLocalizationOptionsService + { + RequestLocalizationOptions RequestLocalizationOptions { get; set; } + readonly AppConfigurationService AppConfigurationService; + + public RequestLocalizationOptionsService(AppConfigurationService appConfigurationService) + { + AppConfigurationService = appConfigurationService; + } + + public async Task Get() + { + if (RequestLocalizationOptions != default) return RequestLocalizationOptions; + if (AppConfigurationService.AppConfiguration == default) + await AppConfigurationService.Init(); + + RequestLocalizationOptions = new RequestLocalizationOptions(); + var cultures = AppConfigurationService + .AppConfiguration + .SupportedLanguages + .Select(sl => new CultureInfo(sl)) + .ToArray(); + RequestLocalizationOptions.DefaultRequestCulture = new(cultures.First(), cultures.First()); + RequestLocalizationOptions.SupportedCultures = cultures; + RequestLocalizationOptions.SupportedUICultures = cultures; + RequestLocalizationOptions.RequestCultureProviders = new IRequestCultureProvider[] { + new AcceptLanguageHeaderRequestCultureProvider { Options = RequestLocalizationOptions } + }; + RequestLocalizationOptions.ApplyCurrentCultureToResponseHeaders = true; + + return RequestLocalizationOptions; + } + } +} diff --git a/SocialPub/Services/RootUsersService.cs b/SocialPub/Services/RootUsersService.cs new file mode 100644 index 0000000..f7c1507 --- /dev/null +++ b/SocialPub/Services/RootUsersService.cs @@ -0,0 +1,577 @@ +using MailKit.Net.Smtp; + +using Microsoft.Extensions.Localization; + +using MimeKit; + +using MongoDB.Driver; +using MongoDB.Entities; + +using PasswordGenerator; + +using SocialPub.ClientModels; +using SocialPub.ClientModels.User; +using SocialPub.Models; +using SocialPub.Models.User; +using SocialPub.Resources; +using SocialPub.StaticServices; + +using System.Globalization; + +#pragma warning disable 8603 +#pragma warning disable 8625 + +namespace SocialPub.Services +{ + public class RootUsersService : IRootUsersService + { + readonly DbEntities DbEntities; + readonly IPasswordHasher PasswordHasher; + readonly IStringLocalizer Localizer; + readonly ILogger Logger; + readonly AppConfigurationService AppConfigurationService; + readonly AuthTokenManager AuthTokenManager; + + public RootUsersService( + IStringLocalizer localizer, + ILogger logger, + IPasswordHasher passwordHasher, + DbEntities dbEntities, + AppConfigurationService appConfigurationService, + AuthTokenManager authTokenManager) + { + DbEntities = dbEntities; + AuthTokenManager = authTokenManager; + PasswordHasher = passwordHasher; + Localizer = localizer; + Logger = logger; + AppConfigurationService = appConfigurationService; + } + + public async Task SignUpAsync(LoginForm signUpForm, string invitationCode = default, + bool isPasswordRequired = false) + { + var result = new WebResult(); + try + { + signUpForm.UserName = signUpForm.UserName.ToLower(); + if (await DbEntities.RootUsers.Match(u => u.UserName == signUpForm.UserName).ExecuteAnyAsync()) + return result.Invalidate(Localizer["Username '{0}' already taken.", signUpForm.UserName]); + + var signUpPasswordHashed = PasswordHasher.Hash(signUpForm.Password); + var newUser = new RootUser + { + UserName = signUpForm.UserName, + HashedPassword = signUpPasswordHashed + }; + if (signUpForm.UserName == "admin") + { + newUser.Policies.Clear(); + newUser.Policies.Add(Policies.IsAdmin); + newUser.Policies.Add(Policies.IsUser); + newUser.Policies.Add(Policies.IsModerator); + } + + await newUser.SaveAsync(); + var cultureLanguage = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; + var language = await DbEntities.Languages.Match(l => l.International2Code == cultureLanguage).ExecuteFirstAsync(); + var newRootUserSettings = new RootUserSettings + { + RootUserId = newUser.ID, + LanguageCode = language?.International2Code ?? "en", + LightThemeIndexColour = signUpForm.LightThemeIndexColour, + DarkThemeIndexColour = signUpForm.DarkThemeIndexColour, + IconsThemeIndexColour = signUpForm.IconsThemeIndexColour, + ThemeIsDarkGray = signUpForm.ThemeIsDarkGray, + ThemeIsLightGray = signUpForm.ThemeIsLightGray, + PreferSystemTheming = signUpForm.PreferSystemTheming, + ThemeIsDarkMode = signUpForm.ThemeIsDarkMode + }; + await newRootUserSettings.SaveAsync(); + + //if (!string.IsNullOrEmpty(invitationCode)) + //{ + // if (isPasswordRequired) + // result = await DiscussionService.InviteUserToDiscussion(new PwDiscussionPreviewForm + // { + // InvitationCode = invitationCode, + // Password = signUpForm.InvitationPassword + // }, newUser.ID); + // else + // result = await DiscussionService.InviteUserToDiscussion(new NoPwDiscussionPreviewForm + // { + // InvitationCode = invitationCode, + // }, newUser.ID); + // if (!result.IsValid) + // return result; + //} + + result.Data = (newUser, new ViewUserSettings + { + LanguageCode = newRootUserSettings.LanguageCode, + LightThemeIndexColour = newRootUserSettings.LightThemeIndexColour, + ThemeIsDarkMode = newRootUserSettings.ThemeIsDarkMode, + IconsThemeIndexColour = newRootUserSettings.IconsThemeIndexColour, + ThemeIsDarkGray = newRootUserSettings.ThemeIsDarkGray, + ThemeIsLightGray = newRootUserSettings.ThemeIsLightGray, + DarkThemeIndexColour = newRootUserSettings.DarkThemeIndexColour, + PreferSystemTheming = newRootUserSettings.PreferSystemTheming + }); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(SignUpAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task LoginAsync(LoginForm loginForm, string invitationCode = default, + bool isPasswordRequired = false) + { + var result = new WebResult(); + try + { + loginForm.UserName = loginForm.UserName.ToLower(); + if (!await DbEntities.RootUsers.Match(u => u.UserName == loginForm.UserName && u.DeletionDate == null) + .ExecuteAnyAsync()) + return result.Invalidate(Localizer["Username '{0}' not found.", loginForm.UserName]); + + var user = await DbEntities.RootUsers.Match(u => u.UserName == loginForm.UserName).ExecuteFirstAsync(); + if (user.IsBanned) + return result.Invalidate(Localizer["User '{0}' banned.", user.UserName]); + + var (verified, needsUpgrade) = PasswordHasher.Check(user.HashedPassword, loginForm.Password); + + if (!verified) + return result.Invalidate(Localizer["Wrong password."]); + + if (needsUpgrade) + result.ErrorMessage = Localizer["Needs upgrade!"]; + + var userSettingsResult = await GetUserSettingsAsync(user.ID, loginForm); + var userSettings = (ViewUserSettings)userSettingsResult.Data; + + //if (!string.IsNullOrEmpty(invitationCode)) + //{ + // if (isPasswordRequired) + // result = await DiscussionService.InviteUserToDiscussion(new PwDiscussionPreviewForm + // { + // InvitationCode = invitationCode, + // Password = loginForm.InvitationPassword + // }, user.ID); + // else + // result = await DiscussionService.InviteUserToDiscussion(new NoPwDiscussionPreviewForm + // { + // InvitationCode = invitationCode, + // }, user.ID); + // if (!result.IsValid) + // return result; + //} + + result.Data = (user, userSettings); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(LoginAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task UpdateUserAsync(UserForm userForm, string userId) + { + var result = new WebResult(); + try + { + var currentUser = await DbEntities.RootUsers.Match(u => u.ID == userId).ExecuteFirstAsync(); + if (!string.IsNullOrEmpty(userForm.Email) && currentUser.Email != userForm.Email) + { + var emailAlreadyUsed = await DbEntities.RootUsers.Match(u => u.Email == userForm.Email).ExecuteAnyAsync(); + if (emailAlreadyUsed) + return result.Invalidate(Localizer["Email '{0}' already taken.", userForm.Email]); + } + + await DB.Update() + .Match(u => u.ID == userId) + .Modify(u => u.Email, userForm.Email) + .ExecuteAsync(); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task UpdateUserSettingsAsync(ViewUserSettings userSettings, string userId) + { + var result = new WebResult(); + try + { + var isSupportedLanguage = AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(userSettings.LanguageCode); + if (!isSupportedLanguage) + return result.Invalidate(Localizer["Language code '{0}' unsupported.", userSettings.LanguageCode]); + + var languageCodeExists = await DbEntities.Languages + .Match(l => l.International2Code == userSettings.LanguageCode).ExecuteAnyAsync(); + if (!languageCodeExists) + return result.Invalidate(Localizer["Language code '{0}' doesn't exist.", userSettings.LanguageCode]); + + var language = await DbEntities.Languages.Match(l => l.International2Code == userSettings.LanguageCode) + .ExecuteFirstAsync(); + _ = await DB.Update() + .Match(u => u.RootUserId == userId) + .Modify(us => us.LanguageCode, language.International2Code) + .Modify(us => us.LightThemeIndexColour, userSettings.LightThemeIndexColour) + .Modify(us => us.DarkThemeIndexColour, userSettings.DarkThemeIndexColour) + .Modify(us => us.PreferSystemTheming, userSettings.PreferSystemTheming) + .Modify(us => us.ThemeIsDarkMode, userSettings.ThemeIsDarkMode) + .Modify(us => us.ThemeIsDarkGray, userSettings.ThemeIsDarkGray) + .Modify(us => us.ThemeIsLightGray, userSettings.ThemeIsLightGray) + .Modify(us => us.IconsThemeIndexColour, userSettings.IconsThemeIndexColour) + .ExecuteAsync(); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserSettingsAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task UpdateUserPasswordAsync(UserPasswordForm userPasswordForm, string userId) + { + var result = new WebResult(); + try + { + var user = await DbEntities.RootUsers.Match(u => u.ID == userId && u.DeletionDate == null).ExecuteFirstAsync(); + + if (user == null) + return result.Invalidate(Localizer["Username '{0}' not found.", userId]); + + var (verified, needsUpgrade) = PasswordHasher.Check(user.HashedPassword, userPasswordForm.OldPassword); + + if (!verified) + return result.Invalidate(Localizer["Wrong password."]); + + var newPasswordHashed = PasswordHasher.Hash(userPasswordForm.NewPassword); + user.HashedPassword = newPasswordHashed; + await user.SaveAsync(); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserPasswordAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task RemoveUserAsync(UsersIds usersIds) + { + var result = new WebResult(); + try + { + var users = await DbEntities.RootUsers.Match(u => usersIds.UserIdList.Contains(u.ID) && u.DeletionDate == default).ExecuteAsync(); + if (users == null || users.Count == 0) + return result.Invalidate(Localizer["User already deleted."]); + + foreach (var user in users) + { + user.Email = Localizer["Deleted user"]; + user.HashedPassword = null; + user.Policies.Clear(); + user.IsBanned = false; + user.IsEmailValidated = false; + //user.TempSecret = null; + user.UserName = Localizer["Deleted user"]; + user.DeletionDate = DateTime.UtcNow; + + await user.SaveAsync(); + } + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(RemoveUserAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task BanUserAsync(UsersIds usersIds) + { + var result = new WebResult(); + try + { + await DB.Update() + .Match(u => usersIds.UserIdList.Contains(u.ID)) + .Modify(u => u.IsBanned, true) + .ExecuteAsync(); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(BanUserAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task UnbanUserAsync(UsersIds usersIds) + { + var result = new WebResult(); + try + { + await DB.Update() + .Match(u => usersIds.UserIdList.Contains(u.ID)) + .Modify(u => u.IsBanned, false) + .ExecuteAsync(); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UnbanUserAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task GetUserSettingsAsync(string userId, LoginForm loginForm = default) + { + var result = new WebResult(); + try + { + var userSettings = await DbEntities.RootUsersSettings.Match(u => u.RootUserId == userId).ExecuteFirstAsync(); + if (loginForm != default && (loginForm.ThemeIsDarkMode != userSettings.ThemeIsDarkMode)) + { + userSettings.ThemeIsDarkMode = loginForm.ThemeIsDarkMode; + await userSettings.SaveAsync(); + } + + result.Data = new ViewUserSettings + { + LanguageCode = userSettings.LanguageCode, + LightThemeIndexColour = userSettings.LightThemeIndexColour, + DarkThemeIndexColour = userSettings.DarkThemeIndexColour, + PreferSystemTheming = userSettings.PreferSystemTheming, + ThemeIsDarkMode = userSettings.ThemeIsDarkMode, + IconsThemeIndexColour = userSettings.IconsThemeIndexColour, + ThemeIsDarkGray = userSettings.ThemeIsDarkGray, + ThemeIsLightGray = userSettings.ThemeIsLightGray + }; + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(GetUserSettingsAsync)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task SetupAndSendRecoveryEmail(PasswordRecoveryForm passwordRecoveryForm, string host) + { + var result = new WebResult(); + try + { + var usernameExists = false; + if (passwordRecoveryForm.IsEmailDisabled) + { + if (!await DbEntities.RootUsers.Match(u => u.UserName == passwordRecoveryForm.UserName && u.DeletionDate == null) + .ExecuteAnyAsync()) + return result.Invalidate(Localizer["Username '{0}' not found.", passwordRecoveryForm.UserName], + StatusCodes.Status404NotFound); + usernameExists = true; + } + else + { + if (!await DbEntities.RootUsers.Match(u => u.Email == passwordRecoveryForm.Email && u.DeletionDate == null) + .ExecuteAnyAsync()) + return result.Invalidate(Localizer["Username '{0}' not found.", passwordRecoveryForm.UserName], + StatusCodes.Status404NotFound); + } + + var user = default(RootUser); + if (usernameExists) + user = await DbEntities.RootUsers.Match(u => u.UserName == passwordRecoveryForm.UserName).ExecuteFirstAsync(); + else + user = await DbEntities.RootUsers.Match(u => u.Email == passwordRecoveryForm.Email).ExecuteFirstAsync(); + + if (string.IsNullOrEmpty(user.Email)) + return result.Invalidate(Localizer["This User doesn't have an email, no way to recover."], + StatusCodes.Status423Locked); + + var emailRecovery = await DbEntities.EmailRecoveries + .Match(er => er.RootUserId == user.ID).ExecuteFirstAsync(); + + if (emailRecovery is null) + { + emailRecovery = new() + { + RootUserId = user.ID + }; + await emailRecovery.SaveAsync(); + } + + var recoveryCodeGenerator = new Password(true, true, true, false, 127); + emailRecovery.RecoveryCode = recoveryCodeGenerator.Next(); + await emailRecovery.SaveAsync(); + + using (var smtpClient = new SmtpClient()) + { + try + { + await smtpClient.ConnectAsync(AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer, AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpPort, + AppConfigurationService.AppConfiguration.EmailConfiguration.UseSSL); + if (!smtpClient.IsConnected) + { + Logger.LogError($"Failed to connect to the SMTP server({AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer})."); + return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.ServiceNotAvailable); + } + } + catch (Exception ex) + { + Logger.LogError(ex, $"Error at connection to the SMTP server({AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer})."); + return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.ServiceNotAvailable, exception: ex); + } + + try + { + await smtpClient.AuthenticateAsync(AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername, AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpPassword); + if (!smtpClient.IsAuthenticated) + { + Logger.LogError($"Failed SMTP authentication of {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}."); + return result.Invalidate(Localizer["Failed to send email."], + (int)SmtpStatusCode.TemporaryAuthenticationFailure); + } + } + catch (Exception ex) + { + Logger.LogError(ex, $"Failed SMTP authentication of {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}."); + return result.Invalidate(Localizer["Failed to send email."], + (int)SmtpStatusCode.TemporaryAuthenticationFailure, exception: ex); + } + + try + { + var toParsed = await smtpClient.VerifyAsync(user.Email); + if (toParsed == null) + return result.Invalidate($"Invalid email of {user.Email}.", (int)SmtpStatusCode.MailboxUnavailable); + } + catch (OperationCanceledException ex) + { + Logger.LogWarning( + $"SMTP operation canceled exception at email verification of {user.Email}. Exception=[{ex.Message}]"); + } + catch (SmtpCommandException ex) + { + Logger.LogWarning( + $"SMTP command exception at email verification of {user.Email}. Exception=[{ex.Message}]"); + } + catch (SmtpProtocolException ex) + { + Logger.LogWarning( + $"SMTP protocol exception at email verification of {user.Email}. Exception=[{ex.Message}]"); + } + catch (Exception ex) + { + Logger.LogWarning($"General exception at email verification of {user.Email}. Exception=[{ex.Message}]"); + } + + var message = new MimeMessage(); + message.From.Add(new MailboxAddress(Localizer["Eugene - collAnon support"], AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername)); + message.To.Add(MailboxAddress.Parse(user.Email)); + message.Subject = Localizer["SocialPub - Password recovery link"]; + message.Body = new TextPart("plain") + { + Text = string.Format(Localizer[@"Hey {0}, + +Eugene from collAnon, following is the password recovery link: +{1} + +-- Eugene"], user.UserName, $"{host}/password-recovery?rc={emailRecovery.RecoveryCode}") + }; + try + { + await smtpClient.SendAsync(message); + } + catch (Exception ex) + { + Logger.LogError(ex, $"Error at email sending to {user.Email} from {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}."); + return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.TransactionFailed, exception: ex); + } + + await smtpClient.DisconnectAsync(quit: true); + } + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(SetupAndSendRecoveryEmail)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task IsValidRecoveryCode(string recoveryCode) + { + var result = new WebResult(); + try + { + var isValidRecoveryCode = await DbEntities.EmailRecoveries + .Match(er => er.RecoveryCode == recoveryCode).ExecuteAnyAsync(); + + result.Data = isValidRecoveryCode; + + return result; + } + catch (Exception ex) + { + return result.Invalidate(ex.Message, exception: ex); + } + } + + public async Task ChangePassword(NewPasswordForm newPasswordForm) + { + var result = new WebResult(); + try + { + var isValidRecoveryCode = await DbEntities.EmailRecoveries + .Match(er => er.RecoveryCode == newPasswordForm.RecoveryCode).ExecuteAnyAsync(); + if (!isValidRecoveryCode) + return result.Invalidate(Localizer["Invalid recovery code."], StatusCodes.Status404NotFound); + + var emailRecovery = await DbEntities.EmailRecoveries + .Match(er => er.RecoveryCode == newPasswordForm.RecoveryCode) + .Project(er => er.Include(nameof(EmailRecovery.RootUserId))) + .ExecuteFirstAsync(); + + if (emailRecovery is null || emailRecovery.RootUserId is null) + return result.Invalidate(Localizer["User not found."]); + + var newHashedPassword = PasswordHasher.Hash(newPasswordForm.NewPassword); + + _ = await DB.Update() + .Match(u => u.ID == emailRecovery.RootUserId && u.DeletionDate == null) + .Modify(u => u.HashedPassword, newHashedPassword) + .ExecuteAsync(); + + _ = await DB.DeleteAsync(er => er.RecoveryCode == newPasswordForm.RecoveryCode); + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(ChangePassword)}()"); + return result.Invalidate(ex.Message, exception: ex); + } + } + + } +} \ No newline at end of file diff --git a/SocialPub/SocialPub.csproj b/SocialPub/SocialPub.csproj new file mode 100644 index 0000000..cba5892 --- /dev/null +++ b/SocialPub/SocialPub.csproj @@ -0,0 +1,56 @@ + + + + net7.0 + + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + GenericRes.resx + + + + + + PublicResXFileCodeGenerator + GenericRes.Designer.cs + + + + diff --git a/SocialPub/StaticServices/AuthTokenManager.cs b/SocialPub/StaticServices/AuthTokenManager.cs new file mode 100644 index 0000000..c0385e6 --- /dev/null +++ b/SocialPub/StaticServices/AuthTokenManager.cs @@ -0,0 +1,55 @@ +using Microsoft.IdentityModel.Tokens; + +using SocialPub.ClientModels; +using SocialPub.ClientModels.User; +using SocialPub.Models.User; + +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace SocialPub.StaticServices +{ + public class AuthTokenManager + { + readonly IConfiguration Configuration; + + public AuthTokenManager(IConfiguration configuration) + { + Configuration = configuration; + } + + public JwtUser GenerateToken(RootUser user, ViewUserSettings userSettings) + { + var expiration = DateTime.UtcNow.AddHours(int.Parse(Configuration["AppConfiguration:Jwt:HoursTimeout"])); + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AppConfiguration:Jwt:Key"])); + var jwtUser = new JwtUser + { + UserId = user.ID, + Email = user.Email, + Username = user.UserName, + Expiration = expiration.Ticks, + Policies = user.Policies, + UserSettings = userSettings + }; + var claims = new List + { + new(ClaimTypes.UserData, user.ID), + new(ClaimTypes.Name, user.UserName) + }; + + claims.Add(new(Policies.IsUser, $"{user.Policies.Contains(Policies.IsUser)}".ToLower())); + claims.Add(new(Policies.IsModerator, $"{user.Policies.Contains(Policies.IsModerator)}".ToLower())); + claims.Add(new(Policies.IsAdmin, $"{user.Policies.Contains(Policies.IsAdmin)}".ToLower())); + + var token = new JwtSecurityToken(issuer: Configuration["AppConfiguration:Jwt:Issuer"], audience: Configuration["AppConfiguration:Jwt:Audience"], + claims: claims, + expires: expiration, + signingCredentials: new(securityKey, SecurityAlgorithms.HmacSha512) + ); + var tokenHandler = new JwtSecurityTokenHandler(); + jwtUser.Token = tokenHandler.WriteToken(token); + return jwtUser; + } + } +} \ No newline at end of file diff --git a/SocialPub/StaticServices/DbEntities.cs b/SocialPub/StaticServices/DbEntities.cs new file mode 100644 index 0000000..073af8f --- /dev/null +++ b/SocialPub/StaticServices/DbEntities.cs @@ -0,0 +1,28 @@ +using MongoDB.Entities; + +using SocialPub.Models; +using SocialPub.Models.Data; +using SocialPub.Models.Post; +using SocialPub.Models.User; + +namespace SocialPub.StaticServices +{ + public class DbEntities + { + public Find RootUsers { get { return DB.Find(); } } + public Find RootUsersSettings { get { return DB.Find(); } } + public Find RootUserNotes { get { return DB.Find(); } } + + public Find AppConfiguration { get { return DB.Find(); } } + public Find Languages { get { return DB.Find(); } } + public Find EmailRecoveries { get { return DB.Find(); } } + + public Find Posts { get { return DB.Find(); } } + public Find DmPosts { get { return DB.Find(); } } + + public Find Avatars { get { return DB.Find(); } } + public Find AvatarSettings { get { return DB.Find(); } } + + public Find RootToAvatars { get { return DB.Find(); } } + } +} diff --git a/SocialPub/StaticServices/IPasswordHasher.cs b/SocialPub/StaticServices/IPasswordHasher.cs new file mode 100644 index 0000000..8ece2b2 --- /dev/null +++ b/SocialPub/StaticServices/IPasswordHasher.cs @@ -0,0 +1,9 @@ +namespace SocialPub.StaticServices +{ + public interface IPasswordHasher + { + string Hash(string password); + + (bool verified, bool needsUpgrade) Check(string hash, string password); + } +} \ No newline at end of file diff --git a/SocialPub/StaticServices/PasswordHasher.cs b/SocialPub/StaticServices/PasswordHasher.cs new file mode 100644 index 0000000..5230aa9 --- /dev/null +++ b/SocialPub/StaticServices/PasswordHasher.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.Options; + +using System.Security.Cryptography; + +namespace SocialPub.StaticServices +{ + public sealed class PasswordHasher : IPasswordHasher + { + private const int SaltSize = 16; + private const int KeySize = 32; + private HashingOptions Options { get; } + + public PasswordHasher(IOptionsMonitor options) + { + Options = options.CurrentValue; + } + + public (bool verified, bool needsUpgrade) Check(string hash, string password) + { + var parts = hash.Split('~', 3); + + if (parts.Length != 3) + throw new FormatException("Unexpected hash format."); + + var iterations = Convert.ToInt32(parts[2]); + var salt = Convert.FromBase64String(parts[1]); + var key = Convert.FromBase64String(parts[0]); + + var needsUpgrade = iterations != Options.Iterations; + + using (var algorithm = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA512)) + { + var keyToCheck = algorithm.GetBytes(KeySize); + var verified = keyToCheck.SequenceEqual(key); + return (verified, needsUpgrade); + } + } + + public string Hash(string password) + { + using (var algorithm = new Rfc2898DeriveBytes(password, SaltSize, Options.Iterations, HashAlgorithmName.SHA512)) + { + var key = Convert.ToBase64String(algorithm.GetBytes(KeySize)); + var salt = Convert.ToBase64String(algorithm.Salt); + return $"{key}~{salt}~{Options.Iterations}"; + } + } + } + + public sealed class HashingOptions + { + public int Iterations { get; set; } = 9789; + } +} \ No newline at end of file diff --git a/SocialPub/appsettings.Development.json b/SocialPub/appsettings.Development.json new file mode 100644 index 0000000..42d1560 --- /dev/null +++ b/SocialPub/appsettings.Development.json @@ -0,0 +1,95 @@ +{ + "MongoSettings": { + "Database": "socialpub", + "LogsDatabase": "logs", + "ConnectionString": "mongodb://localhost:27017" + }, + "AppConfiguration": { + "Version": "0.0.0", + "MaxAllowedUploadFiles": 3, + "MaxAllowedFileSize": 2097152, + "ValidDiscussionFilesTypes": ".odt,.docx,.pdf,.xlsx,.ods,.odp,.pptx,.png,.jpg,.jpeg", + "SupportedLanguages": [ + "en", + "it", + "de", + "fr", + "es", + "ja", + "ru", + "zh", + "bg", + "cs", + "da", + "nl", + "et", + "fi", + "el", + "hu", + "lv", + "lt", + "pl", + "pt", + "ro", + "sk", + "sl", + "sv" + ], + "EmailConfiguration": { + "SmtpServer": "mail.privateemail.com", + "SmtpPort": 465, + "UseSSL": true, + "SmtpUsername": "support@collanon.app", + "SmtpPassword": "c4kXUJFQeKC2dVQbZqxZ" + }, + "HashingOptions": { + "Iterations": 10101 + }, + "Jwt": { + "Key": "ITNN8mPfS2ivOqr1eRWK0Rac3sRAchQdG8BUy0pK4vQ3", + "Issuer": "https://localhost", + "Audience": "https://localhost", + "HoursTimeout": 24 + } + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "https://0.0.0.0:7195", + "Protocols": "Http2" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "MongoDB", + "Args": { + "databaseUrl": "mongodb://localhost:27017/logs", + "collectionName": "collanonquick", + "cappedMaxSizeMb": "1024", + "cappedMaxDocuments": "10000" + } + }, + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {NewLine}{Exception}" + } + } + ] + } +} diff --git a/SocialPub/appsettings.Production.json b/SocialPub/appsettings.Production.json new file mode 100644 index 0000000..08b8e3e --- /dev/null +++ b/SocialPub/appsettings.Production.json @@ -0,0 +1,65 @@ +{ + "MongoSettings": { + "Database": "socialpub", + "LogsDatabase": "logs", + "ConnectionString": "mongodb://localhost:27017" + }, + "AppConfiguration": { + "Version": "0.0.0", + "MaxAllowedUploadFiles": 3, + "MaxAllowedFileSize": 2097152, + "ValidDiscussionFilesTypes": ".odt,.docx,.pdf,.xlsx,.ods,.odp,.pptx,.png,.jpg,.jpeg", + "SupportedLanguages": [ + "en", "it", "de", "fr", "es", "ja", "ru", "zh", "bg", "cs", "da", "nl", "et", "fi", "el", "hu", "lv", "lt", "pl", "pt", "ro", "sk", "sl", "sv" + ], + "EmailConfiguration": { + "SmtpServer": "mail.privateemail.com", + "SmtpPort": 465, + "UseSSL": true, + "SmtpUsername": "support@collanon.app", + "SmtpPassword": "c4kXUJFQeKC2dVQbZqxZ" + }, + "HashingOptions": { + "Iterations": 10101 + }, + "Jwt": { + "Key": "ITNN8mPfS2ivOqr1eRWK0Rac3sRAchQdG8BUy0pK4vQ3", + "Issuer": "https://localhost", + "Audience": "https://localhost", + "HoursTimeout": 365 + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "MongoDB", + "Args": { + "databaseUrl": "mongodb://localhost:27017/logs", + "collectionName": "socialpub", + "cappedMaxSizeMb": "1024", + "cappedMaxDocuments": "10000" + } + }, + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {NewLine}{Exception}" + } + } + ] + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/SocialPub/appsettings.json b/SocialPub/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/SocialPub/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}