R1 cleanup: collAnon and dead code gone, docs current, every peer's scenario in one sweep
Build / Build (push) Successful in 1m7s

Starts the completion program (docs/ROADMAP.md, "Owner decisions on finishing everything", 2026-10-17).

- Dead code: the commented-out collAnon blocks (discussions, seeding, admin user lists, HTTP/3, the old signature
  middleware), the empty PrivaPubWorkersConfiguration, PublicAvatarUsersService and UserPolicyType, unused helpers,
  the second IPasswordHasher registration, the PasswordGenerator package, SignUpAsync/LoginAsync's unused invitation
  parameters. "Starting PrivaPub"; a signed-in login is sent to "/", not to "/discussions".
- Stored fields nothing read: RootUser.Contacts and ResetPasswordToken*, the local Avatar.AvatarServer,
  EmailRecovery.RecoveryCode/RequestIP/CreationDate, MongoSettings.LogsDatabase, and AppConfiguration's
  upload/discussion keys, RequiresFirstTimeSetup and Version (old documents still load: MongoDB.Entities ignores extra
  elements). DmPost's indexes are no longer made. 49 collAnon keys leave FieldsNameResource in every language.
- /clientapi/data/current-version answers the build's ref. UsersIds is a list that can't throw, and the admin routes
  leave the administrator out without mutating the request.
- deploy.yml checks SecureMode on a name no persona can have, and the browser redirect on the directory's first local
  persona: @thepra is the administrator's own now.
- Town: decePub's e2e remnants (expect, client.jsonl, the decePub column) and town/tests go; the dead gaps/load
  commands too. Mobilizon no longer keeps an event for a deleted test.
- tools/pasture/sweep.sh runs every peer's scenario, batch by batch, as CLAUDE.md asks before a change to what
  PrivaPub sends.
- Docs: CLAUDE.md's repo map, route table (every frozen route) and stale lines; ROADMAP's status (setup, v1.25.0,
  v1.26.0, the program C), the 2026-10-17 decisions, and dated notes where earlier text no longer holds; FEDERATION.md's
  FEP list (7888, f228, 7628, ae0c and 8967 supported) and the deploy's persona.

Verified: dotnet build PrivaPub.sln -c Release (0 errors, no new warnings); bash -n on the touched scripts; py_compile
on the town; the pasture's gts, mastodon and lemmy scenarios on this build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-17 02:56:57 +02:00
1 parent 6727e1817a
commit ff5e635dda
71 files changed
+238 -4767

No files matched your search

@@ -29,14 +29,20 @@ namespace PrivaPub.Controllers.ClientToServer
RootUsersService = rootUsersService;
}
// an administrator never removes, bans or unbans itself; the request's own list is left as it came
UsersIds Others(UsersIds usersIds)
{
var selfId = User.GetUserId();
return new UsersIds { UserIdList = usersIds.UserIdList.Where(id => id != selfId).ToList() };
}
[HttpDelete, Route("/clientapi/admin/remove/users"), Authorize(Policy = Policies.IsAdmin)]
public async Task<IActionResult> RemoveUsers([Required] UsersIds usersIds)
{
var result = new WebResult();
try
{
usersIds.UserIdList.Remove(User.GetUserId());
result = await RootUsersService.RemoveUserAsync(usersIds);
result = await RootUsersService.RemoveUserAsync(Others(usersIds));
if (!result.IsValid)
return StatusCode(result.StatusCode, result);
@@ -57,13 +63,7 @@ namespace PrivaPub.Controllers.ClientToServer
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);
result = await RootUsersService.BanUserAsync(Others(usersIds));
if (!result.IsValid)
return StatusCode(result.StatusCode, result);
@@ -85,12 +85,7 @@ namespace PrivaPub.Controllers.ClientToServer
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);
result = await RootUsersService.UnbanUserAsync(Others(usersIds));
if (!result.IsValid)
return StatusCode(result.StatusCode, result);
@@ -102,28 +97,5 @@ namespace PrivaPub.Controllers.ClientToServer
return BadRequest(result.Invalidate(Localizer["Something went wrong."]));
}
}
//[HttpGet, Authorize(Policy = Policies.IsAdmin)]
//public async Task<IActionResult> 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(Localizer["Something went wrong."]));
// }
//}
}
}
@@ -92,7 +92,7 @@ namespace PrivaPub.Controllers.ClientToServer
[HttpPost, Route("/clientapi/user/login"), EnableRateLimiting(RateLimiting.Accounts), Authorize(Policy = Policies.IsUser), AllowAnonymous]
public async Task<IActionResult> Login(LoginForm loginForm)
{
if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions");
if (User.Identity?.IsAuthenticated ?? false) return Redirect("/");
var result = new WebResult();
if (!ModelState.IsValid)
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
@@ -132,7 +132,7 @@ namespace PrivaPub.Controllers.ClientToServer
if (personaProblem != default)
return BadRequest(result.Invalidate(personaProblem));
result = await UsersService.SignUpAsync(signUpForm, signUpForm.InvitationCode);
result = await UsersService.SignUpAsync(signUpForm);
if (!result.IsValid)
return StatusCode(result.StatusCode, result);
var (user, userSettings) = ((RootUser, ViewUserSettings))result.Data;
@@ -171,7 +171,7 @@ namespace PrivaPub.Controllers.ClientToServer
if (!invitation.IsValid)
return StatusCode(invitation.StatusCode, invitation);
result = await UsersService.LoginAsync(loginForm, loginForm.InvitationCode);
result = await UsersService.LoginAsync(loginForm);
if (!result.IsValid)
return StatusCode(result.StatusCode, result);
var (user, userSettings) = ((RootUser, ViewUserSettings))result.Data;
@@ -370,25 +370,6 @@ namespace PrivaPub.Controllers.ClientToServer
}
}
//[HttpGet, Authorize(Policy = Policies.IsUser)]
//public async Task<IActionResult> 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(Localizer["Something went wrong."]));
// }
//}
// what the login's media take, every persona's, and its quota when the server sets one
[HttpGet, Route("/clientapi/user/storage"), Authorize(Policy = Policies.IsUser)]
public async Task<IActionResult> GetStorage([FromServices] Microsoft.Extensions.Options.IOptionsMonitor<Domain.Media.MediaOptions> media, CancellationToken token)
+1 -109
View File
@@ -10,68 +10,12 @@ namespace PrivaPub.Data
{
public static class InitDb
{
public static async Task Init(this DbEntities dbClient, IPasswordHasher passwordHasher)
public static async Task Init(this DbEntities dbClient)
{
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;
@@ -157,57 +101,5 @@ namespace PrivaPub.Data
await DB.Default.SaveAsync(bgLang);
}
}
// 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<User>();
// var newBotsUserSettings = new List<UserSettings>();
// 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();
//}
}
}
-21
View File
@@ -1,11 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using PrivaPub.ClientModels;
using PrivaPub.Models.User;
using System.Security.Claims;
namespace PrivaPub.Extensions
{
public static class Extensions
@@ -27,22 +23,5 @@ namespace PrivaPub.Extensions
.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 UserPolicyType GetHighestPolicy(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.FindFirstValue(Policies.IsAdmin) == "true")
return UserPolicyType.IsAdmin;
if (claimsPrincipal.FindFirstValue(Policies.IsModerator) == "true")
return UserPolicyType.IsModerator;
return UserPolicyType.IsUser;
}
}
}
+1 -25
View File
@@ -1,6 +1,4 @@
using PrivaPub.ClientModels;
using System.Security.Claims;
using System.Security.Claims;
namespace PrivaPub.Extensions
{
@@ -10,27 +8,5 @@ namespace PrivaPub.Extensions
{
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<string> GetUserPolicies(this ClaimsPrincipal claimsPrincipal)
{
var policies = new List<string>();
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;
}
}
}
@@ -75,7 +75,7 @@ namespace PrivaPub.Infrastructure.Backup
continue;
}
await roots.UpdateOneAsync(Id(root["_id"]), Copy(root, "HashedPassword", "Email", "IsEmailValidated", "IsBanned", "Policies",
"CredentialsChangedAt", "ResetPasswordToken", "ResetPasswordTokenSentAt"), cancellationToken: token);
"CredentialsChangedAt"), cancellationToken: token);
}
var avatars = database.GetCollection<BsonDocument>("Avatar");
-3
View File
@@ -49,9 +49,6 @@ namespace PrivaPub.Infrastructure.Data
await Plain<Models.Media.MediaAttachment>(token, m => m.ProfileOfAvatarId);
await Plain<Models.Media.MediaAttachment>(token, m => m.TrashedAt);
await Unique<DmPost>(p => p.ObjectURI, Builders<DmPost>.Filter.Gt(p => p.ObjectURI, ""), token);
await Plain<DmPost>(token, p => p.GroupId, p => p.ID);
await Unique<ForeignAvatar>(a => a.ActorURI, Builders<ForeignAvatar>.Filter.Gt(a => a.ActorURI, ""), token);
await Plain<ForeignAvatar>(token, a => a.PublicKeyId);
+1 -10
View File
@@ -44,13 +44,6 @@ namespace PrivaPub.Middleware
.Configure<AppConfiguration>(configuration.GetSection(nameof(AppConfiguration)))
.Configure<PrivaPub.Infrastructure.RegistrationOptions>(configuration.GetSection("Registrations"));
}
public static IServiceCollection PrivaPubWorkersConfiguration(this IServiceCollection service)
{
return service;
//.AddHostedService<DiscussionsWorker>()
//.AddHostedService<GroupsCleanerWorker>()
//.AddHostedService<PoliciesCleanerWorker>();
}
public static IServiceCollection PrivaPubFederationConfiguration(this IServiceCollection service, IConfiguration configuration)
{
service.Configure<FederationOptions>(configuration.GetSection("Federation"));
@@ -228,7 +221,6 @@ namespace PrivaPub.Middleware
.AddTransient<IRootUsersService, RootUsersService>()
.AddScoped<IRootSessions, RootSessions>()
.AddScoped<IRootRemoval, RootRemoval>()
.AddTransient<IPublicAvatarUsersService, PublicAvatarUsersService>()
.AddTransient<IPrivateAvatarUsersService, PrivateAvatarUsersService>()
.AddTransient<IGroupUsersService, GroupUsersService>()
.AddTransient<IPostsService, PostsService>()
@@ -240,8 +232,7 @@ namespace PrivaPub.Middleware
.AddTransient<ITimelineService, TimelineService>()
.AddSingleton<AppConfigurationService>()
.AddHttpContextAccessor()
.AddMemoryCache()
.AddSingleton<IPasswordHasher, PasswordHasher>();
.AddMemoryCache();
}
public static IServiceCollection PrivaPubMiddlewareConfiguration(this IServiceCollection service)
-5
View File
@@ -7,11 +7,6 @@ namespace PrivaPub.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<string> SupportedLanguages { get; set; } = new();
public string BackendBaseAddress { get; set; }
public string FrontendBaseAddress { get; set; }
-1
View File
@@ -3,7 +3,6 @@
public class MongoSettings
{
public string Database { get; set; }
public string LogsDatabase { get; set; }
public string ConnectionString { get; set; }
}
}
-1
View File
@@ -29,7 +29,6 @@ namespace PrivaPub.Models.User
public string PictureURL { get; set; }//icon
public string ThumbnailURL { get; set; }//image
public AvatarType AvatarType { get; set; } = AvatarType.Person;
public AvatarServer AvatarServer { get; set; } = AvatarServer.PrivaPub;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
-3
View File
@@ -5,10 +5,7 @@ namespace PrivaPub.Models.User
public class EmailRecovery : Entity
{
public string RootUserId { get; set; }
public string RecoveryCode { get; set; }//plaintext codes before 2026-10-04; migration _011 removed them, never written now
public string CodeHash { get; set; }//sha-256 of the code, hex; the code itself exists only in the email
public DateTime ExpiresAt { get; set; }
public string RequestIP { get; set; }
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
}
}
-12
View File
@@ -14,12 +14,8 @@ namespace PrivaPub.Models.User
public bool IsBanned { get; set; } = false;
public string HashedPassword { get; set; }
public List<string> Policies { get; set; } = new() { ClientModels.Policies.IsUser };
public List<ContactItem> 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; }
@@ -38,14 +34,6 @@ namespace PrivaPub.Models.User
}
}
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";
-9
View File
@@ -1,9 +0,0 @@
namespace PrivaPub.Models.User
{
public enum UserPolicyType
{
IsUser,
IsModerator,
IsAdmin
}
}
-1
View File
@@ -19,7 +19,6 @@
<PackageReference Include="NetVips.Native.linux-x64" Version="8.18.7" />
<PackageReference Include="OpenIddict.AspNetCore" Version="7.7.1" />
<PackageReference Include="OpenIddict.MongoDb" Version="7.7.1" />
<PackageReference Include="PasswordGenerator" Version="3.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.MongoDB" Version="7.3.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
+3 -14
View File
@@ -32,12 +32,7 @@ try
{
if (builder.Environment.IsProduction())
{
serverOptions.ListenLocalhost(6970
//, options =>
//{
// options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
//}
);
serverOptions.ListenLocalhost(6970);
serverOptions.UseSystemd();
serverOptions.AddServerHeader = false;
}
@@ -50,7 +45,6 @@ try
try
{
builder.Services.PrivaPubAppSettingsConfiguration(builder.Configuration)
.PrivaPubWorkersConfiguration()
.PrivaPubAuthServicesConfiguration(builder.Configuration)
.PrivaPubInternalizationConfiguration(builder.Configuration)
.PrivaPubOptimizationConfiguration()
@@ -206,9 +200,6 @@ try
app.UseAuthentication();
app.UseAuthorization();
//app.UseWhen(context => context.Request.Path.StartsWithSegments("/peasants") ||
// context.Request.Path.StartsWithSegments("/users"),
// app => app.UseSignatureVerification().UseDigestVerification());
app.MapGet("/build.json", () => Results.Json(new
{
@@ -218,7 +209,6 @@ try
}));
app.MapControllers();
app.MapRazorPages();
//app.MapFallbackToFile("index.html");
}
catch (Exception ex)
{
@@ -226,12 +216,11 @@ try
throw;
}
Log.ForContext<Program>().Information($"Starting collAnon at {nameof(Program)}()");
Log.ForContext<Program>().Information($"Starting PrivaPub 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);
await dbClient.Init();
await app.Services.GetRequiredService<PrivaPub.Federation.Moderation.IDomainBlocks>().Reload(CancellationToken.None);
await PrivaPub.Api.Mastodon.Auth.PersonaExchange.EnsureFirstPartyClient(app.Services, CancellationToken.None);
}
@@ -14,8 +14,9 @@ namespace PrivaPub.Services.ClientToServer.Public
AppConfigurationService = appConfigurationService;
}
// the build's ref (its tag, or the branch a manual deploy ran from), as /build.json and NodeInfo name it
public string GetCurrentVersion() =>
AppConfigurationService.AppConfiguration.Version;
string.IsNullOrEmpty(BuildInfo.Ref) ? BuildInfo.Commit : BuildInfo.Ref;
public async Task<IEnumerable<Language>> GetLanguages(CancellationToken cancellationToken)
{
@@ -23,15 +24,5 @@ namespace PrivaPub.Services.ClientToServer.Public
.Match(l => AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(l.International2Code))
.ExecuteAsync(cancellationToken)).OrderBy(l => l.NativeName).ToArray();
}
public async Task<bool> 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);
}
}
}
@@ -7,7 +7,5 @@ namespace PrivaPub.Services.ClientToServer.Public
string GetCurrentVersion();
Task<IEnumerable<Language>> GetLanguages(CancellationToken cancellationToken);
Task<bool> IsValidLanguageId(string languageId);
}
}
@@ -1,174 +0,0 @@
using Microsoft.Extensions.Localization;
using PrivaPub.ClientModels;
using PrivaPub.Models.User;
using PrivaPub.Resources;
using PrivaPub.StaticServices;
namespace PrivaPub.Services.ClientToServer.Public
{
public interface IPublicAvatarUsersService
{
Task<WebResult> GetLocalAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token);
Task<IEnumerable<Avatar>> GetLocalAvatars(UserPolicyType userPolicyType, CancellationToken token);
Task<WebResult> GetForeignAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token);
Task<IEnumerable<ForeignAvatar>> GetForeignAvatars(UserPolicyType userPolicyType, CancellationToken token);
Task<IEnumerable<Avatar>> GetRootAvatars(string rootUserId, CancellationToken token);
}
public class PublicAvatarUsersService : IPublicAvatarUsersService
{
readonly DbEntities _dbEntities;
readonly IStringLocalizer<GenericRes> _localizer;
readonly ILogger<PublicAvatarUsersService> _logger;
public PublicAvatarUsersService(DbEntities dbEntities,
IStringLocalizer<GenericRes> localizer,
ILogger<PublicAvatarUsersService> logger)
{
_dbEntities = dbEntities;
_localizer = localizer;
_logger = logger;
}
public async Task<WebResult> GetLocalAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token)
{
var result = new WebResult();
try
{
var query = _dbEntities.Avatars;
switch (userPolicyType)
{
case UserPolicyType.IsUser:
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
break;
case UserPolicyType.IsModerator:
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
break;
case UserPolicyType.IsAdmin:
query.Match(a => a.UserName == actor);
break;
}
var avatar = await query.ExecuteFirstAsync(token);
if (avatar == default)
return result.Invalidate(_localizer["User '{0}' not found.", actor], StatusCodes.Status404NotFound);
result.Data = avatar;
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetLocalAvatar)}");
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
}
}
public async Task<IEnumerable<Avatar>> GetLocalAvatars(UserPolicyType userPolicyType, CancellationToken token)
{
try
{
var query = _dbEntities.Avatars;
switch (userPolicyType)
{
case UserPolicyType.IsUser:
query.Match(a => !a.DeletionAt.HasValue);
break;
case UserPolicyType.IsModerator:
query.Match(a => !a.DeletionAt.HasValue);
break;
case UserPolicyType.IsAdmin:
break;
}
var avatars = await query.ExecuteAsync(token);
return avatars;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetLocalAvatars)}");
return Enumerable.Empty<Avatar>();
}
}
public async Task<WebResult> GetForeignAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token)
{
var result = new WebResult();
try
{
var query = _dbEntities.ForeignAvatars;
switch (userPolicyType)
{
case UserPolicyType.IsUser:
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
break;
case UserPolicyType.IsModerator:
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
break;
case UserPolicyType.IsAdmin:
query.Match(a => a.UserName == actor);
break;
}
var avatar = await query.ExecuteFirstAsync(token);
if (avatar == default)
return result.Invalidate(_localizer["User '{0}' not found.", actor], StatusCodes.Status404NotFound);
result.Data = avatar;
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetForeignAvatar)}");
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
}
}
public async Task<IEnumerable<ForeignAvatar>> GetForeignAvatars(UserPolicyType userPolicyType, CancellationToken token)
{
try
{
var query = _dbEntities.ForeignAvatars;
switch (userPolicyType)
{
case UserPolicyType.IsUser:
query.Match(a => !a.DeletionAt.HasValue);
break;
case UserPolicyType.IsModerator:
query.Match(a => !a.DeletionAt.HasValue);
break;
case UserPolicyType.IsAdmin:
break;
}
var foreignAvatars = await query.ExecuteAsync(token);
return foreignAvatars;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetForeignAvatars)}");
return Enumerable.Empty<ForeignAvatar>();
}
}
public async Task<IEnumerable<Avatar>> GetRootAvatars(string rootUserId, CancellationToken token)
{
try
{
var rootUser = await _dbEntities.RootUsers.MatchID(rootUserId).ExecuteFirstAsync(token);
if (rootUser == default)
return Enumerable.Empty<Avatar>();
var rootToAvatars = await _dbEntities.RootToAvatars.Match(ra => ra.RootId == rootUserId).ExecuteAsync(token);
if (rootToAvatars.Count == 0)
return Enumerable.Empty<Avatar>();
var avatarIds = rootToAvatars.Select(ra => ra.AvatarId).ToArray();
var avatars = await _dbEntities.Avatars
.Match(a => avatarIds.Contains(a.ID))
.ProjectExcluding(a => a.Settings)
.ExecuteAsync(token);
return avatars;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetRootAvatars)}");
return Enumerable.Empty<Avatar>();
}
}
}
}
-22
View File
@@ -18,7 +18,6 @@ namespace PrivaPub.Services
{
public interface IGroupUsersService
{
Task<WebResult> GetGroup(string actor, CancellationToken token);
Task<WebResult> GetGroups(string rootUserId, string avatarId, CancellationToken token);
Task<WebResult> InsertGroup(string rootUserId, InsertGroupForm form, CancellationToken token);
Task<WebResult> UpdateGroup(string rootUserId, UpdateGroupForm form, CancellationToken token);
@@ -58,27 +57,6 @@ namespace PrivaPub.Services
_logger = logger;
}
public async Task<WebResult> GetGroup(string actor, CancellationToken token)
{
var result = new WebResult();
try
{
var userName = actor?.ToLowerInvariant();
var group = await _dbEntities.Groups
.Match(g => g.UserName == userName && !g.DeletionAt.HasValue)
.ExecuteFirstAsync(token);
if (group == default)
return result.Invalidate(_localizer["Group '{0}' not found.", actor], StatusCodes.Status404NotFound);
result.Data = group;
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(GroupUsersService)}.{nameof(GetGroup)}");
return result.Invalidate(_localizer["Something went wrong."], exception: ex);
}
}
public async Task<WebResult> GetGroups(string rootUserId, string avatarId, CancellationToken token)
{
var result = new WebResult();
+2 -2
View File
@@ -7,9 +7,9 @@ namespace PrivaPub.Services
{
public interface IRootUsersService
{
Task<WebResult> LoginAsync(LoginForm loginForm, string invitationCode = default, bool isPasswordRequired = false);
Task<WebResult> LoginAsync(LoginForm loginForm);
Task<WebResult> SignUpAsync(LoginForm signUpForm, string invitationCode = default, bool isPasswordRequired = false);
Task<WebResult> SignUpAsync(LoginForm signUpForm);
Task<WebResult> BanUserAsync(UsersIds usersIds);
+3 -48
View File
@@ -1,14 +1,8 @@
using MailKit.Net.Smtp;
using Microsoft.Extensions.Localization;
using MimeKit;
using MongoDB.Driver;
using MongoDB.Entities;
using PasswordGenerator;
using PrivaPub.ClientModels;
using PrivaPub.ClientModels.User;
using PrivaPub.Models;
@@ -33,7 +27,6 @@ namespace PrivaPub.Services
readonly IStringLocalizer Localizer;
readonly ILogger<RootUsersService> Logger;
readonly AppConfigurationService AppConfigurationService;
readonly AuthTokenManager AuthTokenManager;
readonly IJobQueue Jobs;
readonly IRootSessions Sessions;
readonly IRootRemoval Removal;
@@ -46,14 +39,12 @@ namespace PrivaPub.Services
ILogger<RootUsersService> logger,
IPasswordHasher passwordHasher,
DbEntities dbEntities,
AppConfigurationService appConfigurationService,
AuthTokenManager authTokenManager)
AppConfigurationService appConfigurationService)
{
Jobs = jobs;
Sessions = sessions;
Removal = removal;
DbEntities = dbEntities;
AuthTokenManager = authTokenManager;
PasswordHasher = passwordHasher;
Localizer = localizer;
Logger = logger;
@@ -66,8 +57,7 @@ namespace PrivaPub.Services
// a hash nobody's password matches, so an unknown login costs the same hashing as a wrong password
string Decoy => _decoy ??= PasswordHasher.Hash(Convert.ToHexString(System.Security.Cryptography.RandomNumberGenerator.GetBytes(16)));
public async Task<WebResult> SignUpAsync(LoginForm signUpForm, string invitationCode = default,
bool isPasswordRequired = false)
public async Task<WebResult> SignUpAsync(LoginForm signUpForm)
{
var result = new WebResult();
try
@@ -98,23 +88,6 @@ namespace PrivaPub.Services
};
await DB.Default.SaveAsync(newUser);
//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, ToViewSettings(newUser.Settings));
return result;
@@ -126,8 +99,7 @@ namespace PrivaPub.Services
}
}
public async Task<WebResult> LoginAsync(LoginForm loginForm, string invitationCode = default,
bool isPasswordRequired = false)
public async Task<WebResult> LoginAsync(LoginForm loginForm)
{
var result = new WebResult();
try
@@ -148,23 +120,6 @@ namespace PrivaPub.Services
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;
+1 -7
View File
@@ -1,18 +1,12 @@
{
"MongoSettings": {
"Database": "PrivaPub",
"LogsDatabase": "logs",
"ConnectionString": "mongodb://localhost:27017"
},
"Backups": {
"Nightly": false
},
"AppConfiguration": {
"RequiresFirstTimeSetup": null,
"Version": "0.0.0",
"MaxAllowedUploadFiles": 3,
"MaxAllowedFileSize": 2097152,
"ValidDiscussionFilesTypes": ".odt,.docx,.pdf,.xlsx,.ods,.odp,.pptx,.png,.jpg,.jpeg",
"SupportedLanguages": [
"en",
"it",
@@ -83,7 +77,7 @@
"Name": "MongoDBCapped",
"Args": {
"databaseUrl": "mongodb://localhost:27017/logs",
"collectionName": "collanonquick",
"collectionName": "PrivaPub",
"cappedMaxSizeMb": "1024",
"cappedMaxDocuments": "10000"
}
-5
View File
@@ -30,16 +30,11 @@
},
"MongoSettings": {
"Database": "PrivaPub",
"LogsDatabase": "logs",
"ConnectionString": "mongodb://127.0.0.1:27022/?directConnection=true"
},
"AppConfiguration": {
"Version": "0.0.0",
"BackendBaseAddress": "https://privapub.thepra.dev",
"FrontendBaseAddress": "https://decepub.thepra.dev",
"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"
],