diff --git a/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs b/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs index b54ebdd4..64564b27 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs @@ -365,7 +365,7 @@ public class AccountController( .FilterIncomingBlocks(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Accounts); return Ok(res); } diff --git a/Iceshrimp.Backend/Controllers/Mastodon/ConversationsController.cs b/Iceshrimp.Backend/Controllers/Mastodon/ConversationsController.cs index 0865b083..7e131d31 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/ConversationsController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/ConversationsController.cs @@ -64,7 +64,7 @@ public class ConversationsController( .DistinctBy(p => p.Id) .ToList(); - var notes = await noteRenderer.RenderManyAsync(conversations.Select(p => p.LastNote), user, accounts); + var notes = await noteRenderer.RenderManyAsync(conversations.Select(p => p.LastNote), user, accounts: accounts); var res = conversations.Select(p => new ConversationEntity { @@ -136,7 +136,7 @@ public class ConversationsController( { Id = conversation.Id, Unread = conversation.Unread, - LastStatus = await noteRenderer.RenderAsync(conversation.LastNote, user, noteRendererDto), + LastStatus = await noteRenderer.RenderAsync(conversation.LastNote, user, data: noteRendererDto), Accounts = accounts }; diff --git a/Iceshrimp.Backend/Controllers/Mastodon/FilterController.cs b/Iceshrimp.Backend/Controllers/Mastodon/FilterController.cs new file mode 100644 index 00000000..de5ae87e --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/FilterController.cs @@ -0,0 +1,279 @@ +using System.Net.Mime; +using Iceshrimp.Backend.Controllers.Mastodon.Attributes; +using Iceshrimp.Backend.Controllers.Mastodon.Renderers; +using Iceshrimp.Backend.Controllers.Mastodon.Schemas; +using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; +using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; +using Iceshrimp.Backend.Core.Extensions; +using Iceshrimp.Backend.Core.Middleware; +using Iceshrimp.Backend.Core.Queues; +using Iceshrimp.Backend.Core.Services; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.EntityFrameworkCore; + +namespace Iceshrimp.Backend.Controllers.Mastodon; + +[MastodonApiController] +[Route("/api/v2/filters")] +[Authenticate] +[EnableRateLimiting("sliding")] +[EnableCors("mastodon")] +[Produces(MediaTypeNames.Application.Json)] +public class FilterController(DatabaseContext db, QueueService queueSvc) : ControllerBase +{ + [HttpGet] + [Authorize("read:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + public async Task GetFilters() + { + var user = HttpContext.GetUserOrFail(); + var filters = await db.Filters.Where(p => p.User == user).ToListAsync(); + var res = filters.Select(FilterRenderer.RenderOne); + + return Ok(res); + } + + [HttpGet("{id:long}")] + [Authorize("read:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task GetFilter(long id) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == id).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + return Ok(FilterRenderer.RenderOne(filter)); + } + + [HttpPost] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + public async Task CreateFilter([FromHybrid] FilterSchemas.CreateFilterRequest request) + { + var user = HttpContext.GetUserOrFail(); + var action = request.Action switch + { + "warn" => Filter.FilterAction.Warn, + "hide" => Filter.FilterAction.Hide, + _ => throw GracefulException.BadRequest($"Unknown action: {request.Action}") + }; + + var context = request.Context.Select(p => p switch + { + "home" => Filter.FilterContext.Home, + "notifications" => Filter.FilterContext.Notifications, + "public" => Filter.FilterContext.Public, + "thread" => Filter.FilterContext.Threads, + "account" => Filter.FilterContext.Accounts, + _ => throw GracefulException.BadRequest($"Unknown filter context: {p}") + }); + + var contextList = context.ToList(); + if (contextList.Contains(Filter.FilterContext.Home)) + contextList.Add(Filter.FilterContext.Lists); + + DateTime? expiry = request.ExpiresIn.HasValue + ? DateTime.UtcNow + TimeSpan.FromSeconds(request.ExpiresIn.Value) + : null; + + var keywords = request.Keywords.Select(p => p.WholeWord ? $"\"{p.Keyword}\"" : p.Keyword).ToList(); + + var filter = new Filter + { + Name = request.Title, + User = user, + Contexts = contextList, + Action = action, + Expiry = expiry, + Keywords = keywords + }; + + db.Add(filter); + await db.SaveChangesAsync(); + + if (expiry.HasValue) + { + var data = new FilterExpiryJobData { FilterId = filter.Id }; + await queueSvc.BackgroundTaskQueue.ScheduleAsync(data, expiry.Value); + } + + return Ok(FilterRenderer.RenderOne(filter)); + } + + [HttpPut("{id:long}")] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task UpdateFilter(long id, [FromHybrid] FilterSchemas.UpdateFilterRequest request) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.FirstOrDefaultAsync(p => p.User == user && p.Id == id) ?? + throw GracefulException.RecordNotFound(); + + var action = request.Action switch + { + "warn" => Filter.FilterAction.Warn, + "hide" => Filter.FilterAction.Hide, + _ => throw GracefulException.BadRequest($"Unknown action: {request.Action}") + }; + + var context = request.Context.Select(p => p switch + { + "home" => Filter.FilterContext.Home, + "notifications" => Filter.FilterContext.Notifications, + "public" => Filter.FilterContext.Public, + "thread" => Filter.FilterContext.Threads, + "account" => Filter.FilterContext.Accounts, + _ => throw GracefulException.BadRequest($"Unknown filter context: {p}") + }); + + var contextList = context.ToList(); + if (contextList.Contains(Filter.FilterContext.Home)) + contextList.Add(Filter.FilterContext.Lists); + + DateTime? expiry = request.ExpiresIn.HasValue + ? DateTime.UtcNow + TimeSpan.FromSeconds(request.ExpiresIn.Value) + : null; + + foreach (var kw in request.Keywords.Where(p => p is { Id: not null, Destroy: false })) + filter.Keywords[int.Parse(kw.Id!.Split('-')[1])] = kw.WholeWord ? $"\"{kw.Keyword}\"" : kw.Keyword; + + var destroy = request.Keywords.Where(p => p is { Id: not null, Destroy: true }).Select(p => p.Id); + var @new = request.Keywords.Where(p => p.Id == null) + .Select(p => p.WholeWord ? $"\"{p.Keyword}\"" : p.Keyword) + .ToList(); + + var keywords = filter.Keywords.Where((_, i) => !destroy.Contains(i.ToString())).Concat(@new).ToList(); + + filter.Name = request.Title; + filter.Contexts = contextList; + filter.Action = action; + filter.Expiry = expiry; + filter.Keywords = keywords; + + db.Update(filter); + await db.SaveChangesAsync(); + + if (expiry.HasValue) + { + var data = new FilterExpiryJobData { FilterId = filter.Id }; + await queueSvc.BackgroundTaskQueue.ScheduleAsync(data, expiry.Value); + } + + return Ok(FilterRenderer.RenderOne(filter)); + } + + [HttpDelete("{id:long}")] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(object))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task DeleteFilter(long id) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == id).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + db.Remove(filter); + await db.SaveChangesAsync(); + + return Ok(new object()); + } + + [HttpGet("{id:long}/keywords")] + [Authorize("read:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task GetFilterKeywords(long id) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == id).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + return Ok(filter.Keywords.Select((p, i) => new FilterKeyword(p, filter.Id, i))); + } + + [HttpPost("{id:long}/keywords")] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FilterKeyword))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task AddFilterKeyword( + long id, [FromHybrid] FilterSchemas.FilterKeywordsAttributes request + ) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == id).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + var keyword = request.WholeWord ? $"\"{request.Keyword}\"" : request.Keyword; + filter.Keywords.Add(keyword); + + db.Update(keyword); + await db.SaveChangesAsync(); + + return Ok(new FilterKeyword(keyword, filter.Id, filter.Keywords.Count - 1)); + } + + [HttpGet("keywords/{filterId:long}-{keywordId:int}")] + [Authorize("read:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FilterKeyword))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task GetFilterKeyword(long filterId, int keywordId) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == filterId).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + if (filter.Keywords.Count < keywordId) + throw GracefulException.RecordNotFound(); + + return Ok(new FilterKeyword(filter.Keywords[keywordId], filter.Id, keywordId)); + } + + [HttpPut("keywords/{filterId:long}-{keywordId:int}")] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FilterKeyword))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task UpdateFilterKeyword( + long filterId, int keywordId, [FromHybrid] FilterSchemas.FilterKeywordsAttributes request + ) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == filterId).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + if (filter.Keywords.Count < keywordId) + throw GracefulException.RecordNotFound(); + + filter.Keywords[keywordId] = request.WholeWord ? $"\"{request.Keyword}\"" : request.Keyword; + db.Update(filter); + await db.SaveChangesAsync(); + + return Ok(new FilterKeyword(filter.Keywords[keywordId], filter.Id, keywordId)); + } + + [HttpDelete("keywords/{filterId:long}-{keywordId:int}")] + [Authorize("write:filters")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(object))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))] + public async Task DeleteFilterKeyword(long filterId, int keywordId) + { + var user = HttpContext.GetUserOrFail(); + var filter = await db.Filters.Where(p => p.User == user && p.Id == filterId).FirstOrDefaultAsync() ?? + throw GracefulException.RecordNotFound(); + + if (filter.Keywords.Count < keywordId) + throw GracefulException.RecordNotFound(); + + filter.Keywords.RemoveAt(keywordId); + db.Update(filter); + await db.SaveChangesAsync(); + + return Ok(new object()); + } + + //TODO: status filters (first: what are they even for?) +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/FilterRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/FilterRenderer.cs new file mode 100644 index 00000000..9b26c222 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/FilterRenderer.cs @@ -0,0 +1,32 @@ +using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; +using Iceshrimp.Backend.Core.Database.Tables; +using Iceshrimp.Backend.Core.Extensions; + +namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers; + +public static class FilterRenderer +{ + public static FilterEntity RenderOne(Filter filter) + { + var context = filter.Contexts.Select(c => c switch + { + Filter.FilterContext.Home => "home", + Filter.FilterContext.Lists => "home", + Filter.FilterContext.Threads => "thread", + Filter.FilterContext.Notifications => "notifications", + Filter.FilterContext.Accounts => "account", + Filter.FilterContext.Public => "public", + _ => throw new ArgumentOutOfRangeException(nameof(c)) + }); + + return new FilterEntity + { + Id = filter.Id.ToString(), + Keywords = filter.Keywords.Select((p, i) => new FilterKeyword(p, filter.Id, i)).ToList(), + Context = context.Distinct().ToList(), + Title = filter.Name, + ExpiresAt = filter.Expiry?.ToStringIso8601Like(), + FilterAction = filter.Action == Filter.FilterAction.Hide ? "hide" : "warn" + }; + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs index 63564ed5..e6e360c0 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs @@ -3,6 +3,7 @@ using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; +using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; using Iceshrimp.Backend.Core.Services; using Microsoft.EntityFrameworkCore; @@ -19,14 +20,16 @@ public class NoteRenderer( EmojiService emojiSvc ) { - public async Task RenderAsync(Note note, User? user, NoteRendererDto? data = null, int recurse = 2) + public async Task RenderAsync( + Note note, User? user, Filter.FilterContext? filterContext = null, NoteRendererDto? data = null, int recurse = 2 + ) { var uri = note.Uri ?? note.GetPublicUri(config.Value); var renote = note is { Renote: not null, IsQuote: false } && recurse > 0 - ? await RenderAsync(note.Renote, user, data, 0) + ? await RenderAsync(note.Renote, user, filterContext, data, 0) : null; var quote = note is { Renote: not null, IsQuote: true } && recurse > 0 - ? await RenderAsync(note.Renote, user, data, --recurse) + ? await RenderAsync(note.Renote, user, filterContext, data, --recurse) : null; var text = note.Text; string? quoteUri = null; @@ -83,6 +86,10 @@ public class NoteRenderer( ? (data?.Polls ?? await GetPolls([note], user)).FirstOrDefault(p => p.Id == note.Id) : null; + var filters = data?.Filters ?? await GetFilters(user, filterContext); + var filtered = FilterHelper.IsFiltered([note, note.Reply, note.Renote, note.Renote?.Renote], filters); + var filterResult = GetFilterResult(filtered); + var res = new StatusEntity { Id = note.Id, @@ -113,12 +120,21 @@ public class NoteRenderer( Attachments = attachments, Emojis = noteEmoji, Poll = poll, - Reactions = reactions + Reactions = reactions, + Filtered = filterResult }; return res; } + private static List GetFilterResult((Filter filter, string keyword)? filtered) + { + if (filtered == null) return []; + var (filter, keyword) = filtered.Value; + + return [new FilterResultEntity { Filter = FilterRenderer.RenderOne(filter), KeywordMatches = [keyword] }]; + } + private async Task> GetMentions(List notes) { if (notes.Count == 0) return []; @@ -252,8 +268,14 @@ public class NoteRenderer( .ToListAsync(); } + private async Task> GetFilters(User? user, Filter.FilterContext? filterContext) + { + if (filterContext == null) return []; + return await db.Filters.Where(p => p.User == user && p.Contexts.Contains(filterContext.Value)).ToListAsync(); + } + public async Task> RenderManyAsync( - IEnumerable notes, User? user, List? accounts = null + IEnumerable notes, User? user, Filter.FilterContext? filterContext = null, List? accounts = null ) { var noteList = notes.SelectMany(p => [p, p.Renote]) @@ -261,7 +283,7 @@ public class NoteRenderer( .Cast() .DistinctBy(p => p.Id) .ToList(); - + if (noteList.Count == 0) return []; var data = new NoteRendererDto @@ -275,10 +297,11 @@ public class NoteRenderer( PinnedNotes = await GetPinnedNotes(noteList, user), Renotes = await GetRenotes(noteList, user), Emoji = await GetEmoji(noteList), - Reactions = await GetReactions(noteList, user) + Reactions = await GetReactions(noteList, user), + Filters = await GetFilters(user, filterContext) }; - return await noteList.Select(p => RenderAsync(p, user, data)).AwaitAllAsync(); + return await noteList.Select(p => RenderAsync(p, user, filterContext, data)).AwaitAllAsync(); } public class NoteRendererDto @@ -293,6 +316,7 @@ public class NoteRenderer( public List? Polls; public List? Reactions; public List? Renotes; + public List? Filters; public bool Source; } diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs index 9a7f36d4..21704446 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs @@ -18,7 +18,8 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe var note = targetNote != null ? statuses?.FirstOrDefault(p => p.Id == targetNote.Id) ?? - await noteRenderer.RenderAsync(targetNote, user, new NoteRenderer.NoteRendererDto { Accounts = accounts }) + await noteRenderer.RenderAsync(targetNote, user, Filter.FilterContext.Notifications, + new NoteRenderer.NoteRendererDto { Accounts = accounts }) : null; var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ?? @@ -59,7 +60,8 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe .Select(p => p.Note?.Renote) .Where(p => p != null)) .Cast() - .DistinctBy(p => p.Id), user, accounts); + .DistinctBy(p => p.Id), + user, Filter.FilterContext.Notifications, accounts); return await notificationList .Select(p => RenderAsync(p, user, accounts, notes)) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterEntity.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterEntity.cs new file mode 100644 index 00000000..d9691f14 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterEntity.cs @@ -0,0 +1,29 @@ +using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; + +namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; + +public class FilterEntity +{ + [J("id")] public required string Id { get; set; } + [J("title")] public required string Title { get; set; } + [J("context")] public required List Context { get; set; } + [J("expires_at")] public required string? ExpiresAt { get; set; } + [J("filter_action")] public required string FilterAction { get; set; } + [J("keywords")] public required List Keywords { get; set; } + + [J("statuses")] public object[] Statuses => []; //TODO +} + +public class FilterKeyword +{ + public FilterKeyword(string keyword, long filterId, int keywordId) + { + Id = $"{filterId}-{keywordId}"; + WholeWord = keyword.StartsWith('"') && keyword.EndsWith('"') && keyword.Length > 2; + Keyword = WholeWord ? keyword[1..^1] : keyword; + } + + [J("id")] public string Id { get; } + [J("keyword")] public string Keyword { get; } + [J("whole_word")] public bool WholeWord { get; } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterResult.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterResult.cs new file mode 100644 index 00000000..e6e650b6 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/FilterResult.cs @@ -0,0 +1,11 @@ +using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; + +namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; + +public class FilterResultEntity +{ + [J("filter")] public required FilterEntity Filter { get; set; } + [J("keyword_matches")] public required List KeywordMatches { get; set; } + + [J("status_matches")] public List StatusMatches => []; //TODO +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/StatusEntity.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/StatusEntity.cs index dba267f2..600e7825 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/StatusEntity.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/StatusEntity.cs @@ -41,13 +41,13 @@ public class StatusEntity : IEntity [J("poll")] public required PollEntity? Poll { get; set; } - [J("mentions")] public required List Mentions { get; set; } - [J("media_attachments")] public required List Attachments { get; set; } - [J("emojis")] public required List Emojis { get; set; } - [J("reactions")] public required List Reactions { get; set; } + [J("filtered")] public required List Filtered { get; set; } + [J("mentions")] public required List Mentions { get; set; } + [J("media_attachments")] public required List Attachments { get; set; } + [J("emojis")] public required List Emojis { get; set; } + [J("reactions")] public required List Reactions { get; set; } [J("tags")] public object[] Tags => []; //FIXME - [J("filtered")] public object[] Filtered => []; //FIXME [J("card")] public object? Card => null; //FIXME [J("application")] public object? Application => null; //FIXME diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/FilterSchemas.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/FilterSchemas.cs new file mode 100644 index 00000000..3afefcc6 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/FilterSchemas.cs @@ -0,0 +1,56 @@ +using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; +using JR = System.Text.Json.Serialization.JsonRequiredAttribute; +using B = Microsoft.AspNetCore.Mvc.BindPropertyAttribute; + +namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas; + +public class FilterSchemas +{ + public class CreateFilterRequest + { + [B(Name = "title")] [J("title")] [JR] public required string Title { get; set; } + + [B(Name = "context")] + [J("context")] + [JR] + public required List Context { get; set; } + + [B(Name = "filter_action")] + [J("filter_action")] + [JR] + public required string Action { get; set; } + + [B(Name = "expires_in")] + [J("expires_in")] + public long? ExpiresIn { get; set; } + + [B(Name = "keywords_attributes")] + [J("keywords_attributes")] + public List Keywords { get; set; } = []; + } + + public class UpdateFilterRequest : CreateFilterRequest + { + [B(Name = "keywords_attributes")] + [J("keywords_attributes")] + public new List Keywords { get; set; } = []; + } + + public class FilterKeywordsAttributes + { + [B(Name = "keyword")] + [J("keyword")] + [JR] + public required string Keyword { get; set; } + + [B(Name = "whole_word")] + [J("whole_word")] + public bool WholeWord { get; set; } = false; + } + + public class UpdateFilterKeywordsAttributes : FilterKeywordsAttributes + { + [B(Name = "id")] [J("id")] public string? Id { get; set; } + [B(Name = "_destroy")] [J("_destroy")] public bool Destroy { get; set; } = false; + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs b/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs index a36db501..57a85c15 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs @@ -87,7 +87,7 @@ public class StatusController( .FilterBlocked(user) .FilterMuted(user) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Threads); var descendants = await db.NoteDescendants(id, maxDepth, maxDescendants) .Where(p => !p.IsQuote || p.RenoteId != id) @@ -96,7 +96,7 @@ public class StatusController( .FilterBlocked(user) .FilterMuted(user) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Threads); var res = new StatusContext { Ancestors = ancestors, Descendants = descendants }; @@ -483,7 +483,7 @@ public class StatusController( var note = await db.Notes.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id && p.User == user) ?? throw GracefulException.RecordNotFound(); - var res = await noteRenderer.RenderAsync(note, user, new NoteRenderer.NoteRendererDto { Source = true }); + var res = await noteRenderer.RenderAsync(note, user, data: new NoteRenderer.NoteRendererDto { Source = true }); await noteSvc.DeleteNoteAsync(note); return Ok(res); diff --git a/Iceshrimp.Backend/Controllers/Mastodon/TimelineController.cs b/Iceshrimp.Backend/Controllers/Mastodon/TimelineController.cs index 9d2e9958..a568e1c3 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/TimelineController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/TimelineController.cs @@ -42,7 +42,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, C .FilterMuted(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Home); return Ok(res); } @@ -64,7 +64,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, C .FilterMuted(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Public); return Ok(res); } @@ -86,7 +86,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, C .FilterMuted(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Public); return Ok(res); } @@ -108,7 +108,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, C .FilterMuted(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) - .RenderAllForMastodonAsync(noteRenderer, user); + .RenderAllForMastodonAsync(noteRenderer, user, Filter.FilterContext.Lists); return Ok(res); } diff --git a/Iceshrimp.Backend/Core/Database/DatabaseContext.cs b/Iceshrimp.Backend/Core/Database/DatabaseContext.cs index 202a830c..5801dcd6 100644 --- a/Iceshrimp.Backend/Core/Database/DatabaseContext.cs +++ b/Iceshrimp.Backend/Core/Database/DatabaseContext.cs @@ -88,6 +88,7 @@ public class DatabaseContext(DbContextOptions options) public virtual DbSet CacheStore { get; init; } = null!; public virtual DbSet Jobs { get; init; } = null!; public virtual DbSet Workers { get; init; } = null!; + public virtual DbSet Filters { get; init; } = null!; public virtual DbSet DataProtectionKeys { get; init; } = null!; public static NpgsqlDataSource GetDataSource(Config.DatabaseSection? config) @@ -117,6 +118,8 @@ public class DatabaseContext(DbContextOptions options) dataSourceBuilder.MapEnum(); dataSourceBuilder.MapEnum(); dataSourceBuilder.MapEnum(); + dataSourceBuilder.MapEnum(); + dataSourceBuilder.MapEnum(); dataSourceBuilder.EnableDynamicJson(); @@ -142,6 +145,8 @@ public class DatabaseContext(DbContextOptions options) .HasPostgresEnum() .HasPostgresEnum() .HasPostgresEnum() + .HasPostgresEnum() + .HasPostgresEnum() .HasPostgresExtension("pg_trgm"); modelBuilder @@ -975,90 +980,7 @@ public class DatabaseContext(DbContextOptions options) }); modelBuilder.Entity(); - - modelBuilder.Entity(entity => - { - entity.Property(e => e.AlsoKnownAs).HasComment("URIs the user is known as too"); - entity.Property(e => e.AvatarBlurhash).HasComment("The blurhash of the avatar DriveFile"); - entity.Property(e => e.AvatarId).HasComment("The ID of avatar DriveFile."); - entity.Property(e => e.AvatarUrl).HasComment("The URL of the avatar DriveFile"); - entity.Property(e => e.BannerBlurhash).HasComment("The blurhash of the banner DriveFile"); - entity.Property(e => e.BannerId).HasComment("The ID of banner DriveFile."); - entity.Property(e => e.BannerUrl).HasComment("The URL of the banner DriveFile"); - entity.Property(e => e.CreatedAt).HasComment("The created date of the User."); - entity.Property(e => e.DriveCapacityOverrideMb).HasComment("Overrides user drive capacity limit"); - entity.Property(e => e.Emojis).HasDefaultValueSql("'{}'::character varying[]"); - entity.Property(e => e.Featured) - .HasComment("The featured URL of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.FollowersCount) - .HasDefaultValue(0) - .HasComment("The count of followers."); - entity.Property(e => e.FollowersUri) - .HasComment("The URI of the user Follower Collection. It will be null if the origin of the user is local."); - entity.Property(e => e.FollowingCount) - .HasDefaultValue(0) - .HasComment("The count of following."); - entity.Property(e => e.HideOnlineStatus).HasDefaultValue(false); - entity.Property(e => e.Host) - .HasComment("The host of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.Inbox) - .HasComment("The inbox URL of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.IsAdmin) - .HasDefaultValue(false) - .HasComment("Whether the User is the admin."); - entity.Property(e => e.IsBot) - .HasDefaultValue(false) - .HasComment("Whether the User is a bot."); - entity.Property(e => e.IsCat) - .HasDefaultValue(false) - .HasComment("Whether the User is a cat."); - entity.Property(e => e.IsDeleted) - .HasDefaultValue(false) - .HasComment("Whether the User is deleted."); - entity.Property(e => e.IsExplorable) - .HasDefaultValue(true) - .HasComment("Whether the User is explorable."); - entity.Property(e => e.IsLocked) - .HasDefaultValue(false) - .HasComment("Whether the User is locked."); - entity.Property(e => e.IsModerator) - .HasDefaultValue(false) - .HasComment("Whether the User is a moderator."); - entity.Property(e => e.IsSilenced) - .HasDefaultValue(false) - .HasComment("Whether the User is silenced."); - entity.Property(e => e.IsSuspended) - .HasDefaultValue(false) - .HasComment("Whether the User is suspended."); - entity.Property(e => e.MovedToUri).HasComment("The URI of the new account of the User"); - entity.Property(e => e.DisplayName).HasComment("The name of the User."); - entity.Property(e => e.NotesCount) - .HasDefaultValue(0) - .HasComment("The count of notes."); - entity.Property(e => e.SharedInbox) - .HasComment("The sharedInbox URL of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.SpeakAsCat) - .HasDefaultValue(true) - .HasComment("Whether to speak as a cat if isCat."); - entity.Property(e => e.Tags).HasDefaultValueSql("'{}'::character varying[]"); - entity.Property(e => e.Token) - .IsFixedLength() - .HasComment("The native access token of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.UpdatedAt).HasComment("The updated date of the User."); - entity.Property(e => e.Uri) - .HasComment("The URI of the User. It will be null if the origin of the user is local."); - entity.Property(e => e.Username).HasComment("The username of the User."); - entity.Property(e => e.UsernameLower).HasComment("The username (lowercased) of the User."); - - entity.HasOne(d => d.Avatar) - .WithOne(p => p.UserAvatar) - .OnDelete(DeleteBehavior.SetNull); - - entity.HasOne(d => d.Banner) - .WithOne(p => p.UserBanner) - .OnDelete(DeleteBehavior.SetNull); - }); - + modelBuilder.Entity(entity => { entity.Property(e => e.CreatedAt).HasComment("The created date of the UserGroup."); @@ -1256,6 +1178,8 @@ public class DatabaseContext(DbContextOptions options) entity.Property(e => e.QueuedAt).HasDefaultValueSql("now()"); entity.HasOne().WithMany().HasForeignKey(d => d.WorkerId).OnDelete(DeleteBehavior.SetNull); }); + + modelBuilder.ApplyConfigurationsFromAssembly(typeof(DatabaseContext).Assembly); } public async Task ReloadEntityAsync(object entity) diff --git a/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.Designer.cs b/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.Designer.cs new file mode 100644 index 00000000..1b7b328d --- /dev/null +++ b/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.Designer.cs @@ -0,0 +1,6090 @@ +// +using System; +using System.Collections.Generic; +using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Iceshrimp.Backend.Core.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240331190007_AddFilterTable")] + partial class AddFilterTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "antenna_src_enum", new[] { "home", "all", "users", "list", "group", "instances" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "filter_action_enum", new[] { "warn", "hide" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "filter_context_enum", new[] { "home", "lists", "threads", "notifications", "accounts", "public" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "job_status", new[] { "queued", "delayed", "running", "completed", "failed" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "marker_type_enum", new[] { "home", "notifications" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "note_visibility_enum", new[] { "public", "home", "followers", "specified" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "notification_type_enum", new[] { "follow", "mention", "reply", "renote", "quote", "like", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "edit", "bite" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "page_visibility_enum", new[] { "public", "followers", "specified" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "push_subscription_policy_enum", new[] { "all", "followed", "follower", "none" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "relay_status_enum", new[] { "requesting", "accepted", "rejected" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "user_profile_ffvisibility_enum", new[] { "public", "followers", "private" }); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "pg_trgm"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AbuseUserReport", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AssigneeId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("assigneeId"); + + b.Property("Comment") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("comment"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the AbuseUserReport."); + + b.Property("Forwarded") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("forwarded"); + + b.Property("ReporterHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("reporterHost") + .HasComment("[Denormalized]"); + + b.Property("ReporterId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("reporterId"); + + b.Property("Resolved") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("resolved"); + + b.Property("TargetUserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("targetUserHost") + .HasComment("[Denormalized]"); + + b.Property("TargetUserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("targetUserId"); + + b.HasKey("Id"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ReporterHost"); + + b.HasIndex("ReporterId"); + + b.HasIndex("Resolved"); + + b.HasIndex("TargetUserHost"); + + b.HasIndex("TargetUserId"); + + b.ToTable("abuse_user_report"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AllowedInstance", b => + { + b.Property("Host") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("host"); + + b.Property("IsImported") + .HasColumnType("boolean") + .HasColumnName("imported"); + + b.HasKey("Host"); + + b.ToTable("allowed_instance"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Announcement", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Announcement."); + + b.Property("ImageUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("imageUrl"); + + b.Property("IsGoodNews") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isGoodNews"); + + b.Property("ShowPopup") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("showPopup"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the Announcement."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.ToTable("announcement"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AnnouncementRead", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AnnouncementId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("announcementId"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the AnnouncementRead."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("AnnouncementId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "AnnouncementId") + .IsUnique(); + + b.ToTable("announcement_read"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Antenna", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CaseSensitive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("caseSensitive"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Antenna."); + + b.Property>>("ExcludeKeywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("excludeKeywords") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("Expression") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("expression"); + + b.Property>("Instances") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("instances") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property>>("Keywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("keywords") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the Antenna."); + + b.Property("Notify") + .HasColumnType("boolean") + .HasColumnName("notify"); + + b.Property("Source") + .HasColumnType("antenna_src_enum") + .HasColumnName("src"); + + b.Property("UserGroupMemberId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userGroupMemberId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.Property("UserListId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userListId"); + + b.Property>("Users") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(1024)[]") + .HasColumnName("users") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("WithFile") + .HasColumnType("boolean") + .HasColumnName("withFile"); + + b.Property("WithReplies") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("withReplies"); + + b.HasKey("Id"); + + b.HasIndex("UserGroupMemberId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserListId"); + + b.ToTable("antenna"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AttestationChallenge", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("Challenge") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("challenge") + .HasComment("Hex-encoded sha256 hash of the challenge."); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The date challenge was created for expiry purposes."); + + b.Property("RegistrationChallenge") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("registrationChallenge") + .HasComment("Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication."); + + b.HasKey("Id", "UserId"); + + b.HasIndex("Challenge"); + + b.HasIndex("UserId"); + + b.ToTable("attestation_challenge"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Bite", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("TargetBiteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("targetBiteId"); + + b.Property("TargetNoteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("targetNoteId"); + + b.Property("TargetUserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("targetUserId"); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri"); + + b.Property("UserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("userHost"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("TargetBiteId"); + + b.HasIndex("TargetNoteId"); + + b.HasIndex("TargetUserId"); + + b.HasIndex("Uri"); + + b.HasIndex("UserHost"); + + b.HasIndex("UserId"); + + b.ToTable("bite"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.BlockedInstance", b => + { + b.Property("Host") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("host"); + + b.Property("IsImported") + .HasColumnType("boolean") + .HasColumnName("imported"); + + b.Property("Reason") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("reason"); + + b.HasKey("Host"); + + b.ToTable("blocked_instance"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Blocking", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("BlockeeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blockeeId") + .HasComment("The blockee user ID."); + + b.Property("BlockerId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blockerId") + .HasComment("The blocker user ID."); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Blocking."); + + b.HasKey("Id"); + + b.HasIndex("BlockeeId"); + + b.HasIndex("BlockerId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("BlockerId", "BlockeeId") + .IsUnique(); + + b.ToTable("blocking"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.CacheEntry", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("key"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry"); + + b.Property("Ttl") + .HasColumnType("interval") + .HasColumnName("ttl"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Key"); + + b.HasIndex("Expiry"); + + b.ToTable("cache_store"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Channel", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("BannerId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("bannerId") + .HasComment("The ID of banner Channel."); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Channel."); + + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("description") + .HasComment("The description of the Channel."); + + b.Property("LastNotedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastNotedAt"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the Channel."); + + b.Property("NotesCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("notesCount") + .HasComment("The count of notes."); + + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.Property("UsersCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("usersCount") + .HasComment("The count of users."); + + b.HasKey("Id"); + + b.HasIndex("BannerId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("LastNotedAt"); + + b.HasIndex("NotesCount"); + + b.HasIndex("UserId"); + + b.HasIndex("UsersCount"); + + b.ToTable("channel"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ChannelFollowing", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the ChannelFollowing."); + + b.Property("FolloweeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followeeId") + .HasComment("The followee channel ID."); + + b.Property("FollowerId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followerId") + .HasComment("The follower user ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FolloweeId"); + + b.HasIndex("FollowerId"); + + b.HasIndex("FollowerId", "FolloweeId") + .IsUnique(); + + b.ToTable("channel_following"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ChannelNotePin", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("ChannelId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("channelId"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the ChannelNotePin."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("NoteId"); + + b.HasIndex("ChannelId", "NoteId") + .IsUnique(); + + b.ToTable("channel_note_pin"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Clip", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Clip."); + + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("description") + .HasComment("The description of the Clip."); + + b.Property("IsPublic") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isPublic"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the Clip."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("clip"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ClipNote", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("ClipId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("clipId") + .HasComment("The clip ID."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId") + .HasComment("The note ID."); + + b.HasKey("Id"); + + b.HasIndex("ClipId"); + + b.HasIndex("NoteId"); + + b.HasIndex("NoteId", "ClipId") + .IsUnique(); + + b.ToTable("clip_note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFile", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AccessKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("accessKey"); + + b.Property("Blurhash") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("blurhash") + .HasComment("The BlurHash string."); + + b.Property("Comment") + .HasColumnType("text") + .HasColumnName("comment") + .HasComment("The comment of the DriveFile."); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the DriveFile."); + + b.Property("FolderId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("folderId") + .HasComment("The parent folder ID. If null, it means the DriveFile is located in root."); + + b.Property("IsLink") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isLink") + .HasComment("Whether the DriveFile is direct link to remote server."); + + b.Property("IsSensitive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isSensitive") + .HasComment("Whether the DriveFile is NSFW."); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name") + .HasComment("The file name of the DriveFile."); + + b.Property("Properties") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("properties") + .HasDefaultValueSql("'{}'::jsonb") + .HasComment("The any properties of the DriveFile. For example, it includes image width/height."); + + b.Property>("RequestHeaders") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("requestHeaders") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("RequestIp") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("requestIp"); + + b.Property("Sha256") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("sha256") + .HasComment("The SHA256 hash of the DriveFile."); + + b.Property("Size") + .HasColumnType("integer") + .HasColumnName("size") + .HasComment("The file size (bytes) of the DriveFile."); + + b.Property("Src") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("src"); + + b.Property("StoredInternal") + .HasColumnType("boolean") + .HasColumnName("storedInternal"); + + b.Property("ThumbnailAccessKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("thumbnailAccessKey"); + + b.Property("ThumbnailUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("thumbnailUrl") + .HasComment("The URL of the thumbnail of the DriveFile."); + + b.Property("Type") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("type") + .HasComment("The content type (MIME) of the DriveFile."); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri") + .HasComment("The URI of the DriveFile. it will be null when the DriveFile is local."); + + b.Property("Url") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("url") + .HasComment("The URL of the DriveFile."); + + b.Property("UserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("userHost") + .HasComment("The host of owner. It will be null if the user in local."); + + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.Property("WebpublicAccessKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("webpublicAccessKey"); + + b.Property("WebpublicType") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("webpublicType"); + + b.Property("WebpublicUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("webpublicUrl") + .HasComment("The URL of the webpublic of the DriveFile."); + + b.HasKey("Id"); + + b.HasIndex("AccessKey"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FolderId"); + + b.HasIndex("IsLink"); + + b.HasIndex("IsSensitive"); + + b.HasIndex("Sha256"); + + b.HasIndex("ThumbnailAccessKey"); + + b.HasIndex("Type"); + + b.HasIndex("Uri"); + + b.HasIndex("UserHost"); + + b.HasIndex("UserId"); + + b.HasIndex("WebpublicAccessKey"); + + b.HasIndex("UserId", "FolderId", "Id"); + + b.ToTable("drive_file"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFolder", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the DriveFolder."); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the DriveFolder."); + + b.Property("ParentId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("parentId") + .HasComment("The parent folder ID. If null, it means the DriveFolder is located in root."); + + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ParentId"); + + b.HasIndex("UserId"); + + b.ToTable("drive_folder"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Emoji", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property>("Aliases") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("aliases") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Category") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("category"); + + b.Property("Height") + .HasColumnType("integer") + .HasColumnName("height") + .HasComment("Image height"); + + b.Property("Host") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("host"); + + b.Property("License") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("license"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("OriginalUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("originalUrl"); + + b.Property("PublicUrl") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("publicUrl") + .HasDefaultValueSql("''::character varying"); + + b.Property("Type") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt"); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri"); + + b.Property("Width") + .HasColumnType("integer") + .HasColumnName("width") + .HasComment("Image width"); + + b.HasKey("Id"); + + b.HasIndex("Host"); + + b.HasIndex("Name"); + + b.HasIndex("Name", "Host") + .IsUnique(); + + b.ToTable("emoji"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Filter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("filter_action_enum") + .HasColumnName("action"); + + b.Property>("Contexts") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("filter_context_enum[]") + .HasColumnName("contexts") + .HasDefaultValueSql("'{}'::public.filter_context_enum[]"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry"); + + b.Property>("Keywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text[]") + .HasColumnName("keywords") + .HasDefaultValueSql("'{}'::varchar[]"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("user_id") + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.HasIndex("user_id"); + + b.ToTable("filter"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the FollowRequest."); + + b.Property("FolloweeHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeHost") + .HasComment("[Denormalized]"); + + b.Property("FolloweeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followeeId") + .HasComment("The followee user ID."); + + b.Property("FolloweeInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeInbox") + .HasComment("[Denormalized]"); + + b.Property("FolloweeSharedInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeSharedInbox") + .HasComment("[Denormalized]"); + + b.Property("FollowerHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerHost") + .HasComment("[Denormalized]"); + + b.Property("FollowerId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followerId") + .HasComment("The follower user ID."); + + b.Property("FollowerInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerInbox") + .HasComment("[Denormalized]"); + + b.Property("FollowerSharedInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerSharedInbox") + .HasComment("[Denormalized]"); + + b.Property("RequestId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("requestId") + .HasComment("id of Follow Activity."); + + b.HasKey("Id"); + + b.HasIndex("FolloweeId"); + + b.HasIndex("FollowerId"); + + b.HasIndex("FollowerId", "FolloweeId") + .IsUnique(); + + b.ToTable("follow_request"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Following", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Following."); + + b.Property("FolloweeHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeHost") + .HasComment("[Denormalized]"); + + b.Property("FolloweeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followeeId") + .HasComment("The followee user ID."); + + b.Property("FolloweeInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeInbox") + .HasComment("[Denormalized]"); + + b.Property("FolloweeSharedInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followeeSharedInbox") + .HasComment("[Denormalized]"); + + b.Property("FollowerHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerHost") + .HasComment("[Denormalized]"); + + b.Property("FollowerId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followerId") + .HasComment("The follower user ID."); + + b.Property("FollowerInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerInbox") + .HasComment("[Denormalized]"); + + b.Property("FollowerSharedInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followerSharedInbox") + .HasComment("[Denormalized]"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FolloweeHost"); + + b.HasIndex("FolloweeId"); + + b.HasIndex("FollowerHost"); + + b.HasIndex("FollowerId"); + + b.HasIndex("FollowerId", "FolloweeId") + .IsUnique(); + + b.ToTable("following"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.GalleryLike", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("PostId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("postId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "PostId") + .IsUnique(); + + b.ToTable("gallery_like"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.GalleryPost", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the GalleryPost."); + + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("description"); + + b.Property>("FileIds") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("fileIds") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("IsSensitive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isSensitive") + .HasComment("Whether the post is sensitive."); + + b.Property("LikedCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("likedCount"); + + b.Property>("Tags") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("tags") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the GalleryPost."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The ID of author."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FileIds"); + + b.HasIndex("IsSensitive"); + + b.HasIndex("LikedCount"); + + b.HasIndex("Tags"); + + b.HasIndex("UpdatedAt"); + + b.HasIndex("UserId"); + + b.ToTable("gallery_post"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Hashtag", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("hashtag"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Instance", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CaughtAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("caughtAt") + .HasComment("The caught date of the Instance."); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("FaviconUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("faviconUrl"); + + b.Property("Host") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("host") + .HasComment("The host of the Instance."); + + b.Property("IconUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("iconUrl"); + + b.Property("IncomingFollows") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("incomingFollows"); + + b.Property("InfoUpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("infoUpdatedAt"); + + b.Property("IsNotResponding") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isNotResponding"); + + b.Property("IsSuspended") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isSuspended"); + + b.Property("LastCommunicatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastCommunicatedAt"); + + b.Property("LatestRequestReceivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("latestRequestReceivedAt"); + + b.Property("LatestRequestSentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("latestRequestSentAt"); + + b.Property("LatestStatus") + .HasColumnType("integer") + .HasColumnName("latestStatus"); + + b.Property("MaintainerEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("maintainerEmail"); + + b.Property("MaintainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("maintainerName"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("NotesCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("notesCount") + .HasComment("The count of the notes of the Instance."); + + b.Property("OpenRegistrations") + .HasColumnType("boolean") + .HasColumnName("openRegistrations"); + + b.Property("OutgoingFollows") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("outgoingFollows"); + + b.Property("SoftwareName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("softwareName") + .HasComment("The software of the Instance."); + + b.Property("SoftwareVersion") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("softwareVersion"); + + b.Property("ThemeColor") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("themeColor"); + + b.Property("UsersCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("usersCount") + .HasComment("The count of the users of the Instance."); + + b.HasKey("Id"); + + b.HasIndex("CaughtAt"); + + b.HasIndex("Host") + .IsUnique(); + + b.HasIndex("IsSuspended"); + + b.ToTable("instance"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text") + .HasColumnName("data"); + + b.Property("DelayedUntil") + .HasColumnType("timestamp with time zone") + .HasColumnName("delayed_until"); + + b.Property("ExceptionMessage") + .HasColumnType("text") + .HasColumnName("exception_message"); + + b.Property("ExceptionSource") + .HasColumnType("text") + .HasColumnName("exception_source"); + + b.Property("FinishedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("finished_at"); + + b.Property("Queue") + .IsRequired() + .HasColumnType("text") + .HasColumnName("queue"); + + b.Property("QueuedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("queued_at") + .HasDefaultValueSql("now()"); + + b.Property("RetryCount") + .HasColumnType("integer") + .HasColumnName("retry_count"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("job_status") + .HasDefaultValue(Job.JobStatus.Queued) + .HasColumnName("status"); + + b.Property("WorkerId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("worker_id"); + + b.HasKey("Id"); + + b.HasIndex("DelayedUntil"); + + b.HasIndex("FinishedAt"); + + b.HasIndex("Queue"); + + b.HasIndex("Status"); + + b.HasIndex("WorkerId"); + + b.ToTable("jobs"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Marker", b => + { + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("Type") + .HasMaxLength(32) + .HasColumnType("marker_type_enum") + .HasColumnName("type"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastUpdated"); + + b.Property("Position") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("position"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("version"); + + b.HasKey("UserId", "Type"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Type") + .IsUnique(); + + b.ToTable("marker"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.MessagingMessage", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the MessagingMessage."); + + b.Property("FileId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("fileId"); + + b.Property("GroupId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("groupId") + .HasComment("The recipient group ID."); + + b.Property("IsRead") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isRead"); + + b.Property>("Reads") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("reads") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("RecipientId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("recipientId") + .HasComment("The recipient user ID."); + + b.Property("Text") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("text"); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The sender user ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FileId"); + + b.HasIndex("GroupId"); + + b.HasIndex("RecipientId"); + + b.HasIndex("UserId"); + + b.ToTable("messaging_message"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Meta", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property>("AllowedHosts") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("allowedHosts") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("AutofollowedAccount") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("autofollowedAccount"); + + b.Property("BackgroundImageUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("backgroundImageUrl"); + + b.Property("BannerUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("bannerUrl"); + + b.Property>("BlockedHosts") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("blockedHosts") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("CacheRemoteFiles") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("cacheRemoteFiles"); + + b.Property>("CustomMotd") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("customMOTD") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property>("CustomSplashIcons") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("customSplashIcons") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("DeeplAuthKey") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("deeplAuthKey"); + + b.Property("DeeplIsPro") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("deeplIsPro"); + + b.Property("DefaultDarkTheme") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("defaultDarkTheme"); + + b.Property("DefaultLightTheme") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("defaultLightTheme"); + + b.Property("DefaultReaction") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("defaultReaction") + .HasDefaultValueSql("'⭐'::character varying"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("description"); + + b.Property("DisableGlobalTimeline") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("disableGlobalTimeline"); + + b.Property("DisableLocalTimeline") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("disableLocalTimeline"); + + b.Property("DisableRecommendedTimeline") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("disableRecommendedTimeline"); + + b.Property("DisableRegistration") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("disableRegistration"); + + b.Property("DiscordClientId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("discordClientId"); + + b.Property("DiscordClientSecret") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("discordClientSecret"); + + b.Property("DonationLink") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("donationLink"); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("email"); + + b.Property("EmailRequiredForSignup") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("emailRequiredForSignup"); + + b.Property("EnableActiveEmailValidation") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("enableActiveEmailValidation"); + + b.Property("EnableDiscordIntegration") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableDiscordIntegration"); + + b.Property("EnableEmail") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableEmail"); + + b.Property("EnableGithubIntegration") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableGithubIntegration"); + + b.Property("EnableHcaptcha") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableHcaptcha"); + + b.Property("EnableIdenticonGeneration") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("enableIdenticonGeneration"); + + b.Property("EnableIpLogging") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableIpLogging"); + + b.Property("EnableRecaptcha") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableRecaptcha"); + + b.Property("EnableServerMachineStats") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableServerMachineStats"); + + b.Property("ErrorImageUrl") + .ValueGeneratedOnAdd() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("errorImageUrl") + .HasDefaultValueSql("'/static-assets/badges/error.png'::character varying"); + + b.Property>("ExperimentalFeatures") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("experimentalFeatures") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("FeedbackUrl") + .ValueGeneratedOnAdd() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("feedbackUrl") + .HasDefaultValueSql("'https://iceshrimp.dev/iceshrimp/iceshrimp/issues/new'::character varying"); + + b.Property("GithubClientId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("githubClientId"); + + b.Property("GithubClientSecret") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("githubClientSecret"); + + b.Property("HcaptchaSecretKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hcaptchaSecretKey"); + + b.Property("HcaptchaSiteKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hcaptchaSiteKey"); + + b.Property>("HiddenTags") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("hiddenTags") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("IconUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("iconUrl"); + + b.Property>("Langs") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(64)[]") + .HasColumnName("langs") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("LibreTranslateApiKey") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("libreTranslateApiKey"); + + b.Property("LibreTranslateApiUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("libreTranslateApiUrl"); + + b.Property("LocalDriveCapacityMb") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1024) + .HasColumnName("localDriveCapacityMb") + .HasComment("Drive capacity of a local user (MB)"); + + b.Property("LogoImageUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("logoImageUrl"); + + b.Property("MaintainerEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("maintainerEmail"); + + b.Property("MaintainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("maintainerName"); + + b.Property("MascotImageUrl") + .ValueGeneratedOnAdd() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("mascotImageUrl") + .HasDefaultValueSql("'/static-assets/badges/info.png'::character varying"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("ObjectStorageAccessKey") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageAccessKey"); + + b.Property("ObjectStorageBaseUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageBaseUrl"); + + b.Property("ObjectStorageBucket") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageBucket"); + + b.Property("ObjectStorageEndpoint") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageEndpoint"); + + b.Property("ObjectStoragePort") + .HasColumnType("integer") + .HasColumnName("objectStoragePort"); + + b.Property("ObjectStoragePrefix") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStoragePrefix"); + + b.Property("ObjectStorageRegion") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageRegion"); + + b.Property("ObjectStorageS3ForcePathStyle") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("objectStorageS3ForcePathStyle"); + + b.Property("ObjectStorageSecretKey") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("objectStorageSecretKey"); + + b.Property("ObjectStorageSetPublicRead") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("objectStorageSetPublicRead"); + + b.Property("ObjectStorageUseProxy") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("objectStorageUseProxy"); + + b.Property("ObjectStorageUseSsl") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("objectStorageUseSSL"); + + b.Property("PinnedClipId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("pinnedClipId"); + + b.Property>("PinnedPages") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(512)[]") + .HasColumnName("pinnedPages") + .HasDefaultValueSql("'{/featured,/channels,/explore,/pages,/about-iceshrimp}'::character varying[]"); + + b.Property>("PinnedUsers") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("pinnedUsers") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("PrivateMode") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("privateMode"); + + b.Property("RecaptchaSecretKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("recaptchaSecretKey"); + + b.Property("RecaptchaSiteKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("recaptchaSiteKey"); + + b.Property>("RecommendedInstances") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("recommendedInstances") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("RemoteDriveCapacityMb") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(32) + .HasColumnName("remoteDriveCapacityMb") + .HasComment("Drive capacity of a remote user (MB)"); + + b.Property("RepositoryUrl") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("repositoryUrl") + .HasDefaultValueSql("'https://iceshrimp.dev/iceshrimp/iceshrimp'::character varying"); + + b.Property("SecureMode") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("secureMode"); + + b.Property>("SilencedHosts") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("silencedHosts") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("SmtpHost") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("smtpHost"); + + b.Property("SmtpPass") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("smtpPass"); + + b.Property("SmtpPort") + .HasColumnType("integer") + .HasColumnName("smtpPort"); + + b.Property("SmtpSecure") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("smtpSecure"); + + b.Property("SmtpUser") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("smtpUser"); + + b.Property("SummalyProxy") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("summalyProxy"); + + b.Property("SwPrivateKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("swPrivateKey"); + + b.Property("SwPublicKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("swPublicKey"); + + b.Property("ThemeColor") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("themeColor"); + + b.Property("ToSurl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("ToSUrl"); + + b.Property("UseObjectStorage") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("useObjectStorage"); + + b.HasKey("Id"); + + b.ToTable("meta"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.MetaStoreEntry", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("key"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Key"); + + b.HasIndex("Key", "Value"); + + b.ToTable("meta_store"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ModerationLog", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the ModerationLog."); + + b.Property("Info") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("info"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("type"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("moderation_log"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Muting", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Muting."); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiresAt"); + + b.Property("MuteeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("muteeId") + .HasComment("The mutee user ID."); + + b.Property("MuterId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("muterId") + .HasComment("The muter user ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("MuteeId"); + + b.HasIndex("MuterId"); + + b.HasIndex("MuterId", "MuteeId") + .IsUnique(); + + b.ToTable("muting"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Note", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property>("AttachedFileTypes") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("attachedFileTypes") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("ChannelId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("channelId") + .HasComment("The ID of source channel."); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Note."); + + b.Property("Cw") + .HasColumnType("text") + .HasColumnName("cw"); + + b.Property>("Emojis") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("emojis") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property>("FileIds") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("fileIds") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("HasPoll") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("hasPoll"); + + b.Property("LikeCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("likeCount"); + + b.Property("LocalOnly") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("localOnly"); + + b.Property>("MentionedRemoteUsers") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("mentionedRemoteUsers") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property>("Mentions") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("mentions") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property>("Reactions") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("reactions") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("RenoteCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0) + .HasColumnName("renoteCount"); + + b.Property("RenoteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("renoteId") + .HasComment("The ID of renote target."); + + b.Property("RenoteUserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("renoteUserHost") + .HasComment("[Denormalized]"); + + b.Property("RenoteUserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("renoteUserId") + .HasComment("[Denormalized]"); + + b.Property("RepliesCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0) + .HasColumnName("repliesCount"); + + b.Property("ReplyId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("replyId") + .HasComment("The ID of reply target."); + + b.Property("ReplyUserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("replyUserHost") + .HasComment("[Denormalized]"); + + b.Property("ReplyUserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("replyUserId") + .HasComment("[Denormalized]"); + + b.Property("Score") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("score"); + + b.Property>("Tags") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("tags") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Text") + .HasColumnType("text") + .HasColumnName("text"); + + b.Property("ThreadId") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("threadId"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the Note."); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri") + .HasComment("The URI of a note. it will be null when the note is local."); + + b.Property("Url") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("url") + .HasComment("The human readable url of a note. it will be null when the note is local."); + + b.Property("UserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("userHost") + .HasComment("[Denormalized]"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The ID of author."); + + b.Property("Visibility") + .HasColumnType("note_visibility_enum") + .HasColumnName("visibility"); + + b.Property>("VisibleUserIds") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("visibleUserIds") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.HasKey("Id"); + + b.HasIndex("AttachedFileTypes"); + + b.HasIndex("ChannelId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FileIds"); + + b.HasIndex("Mentions"); + + b.HasIndex("RenoteId"); + + b.HasIndex("ReplyId"); + + b.HasIndex("Tags"); + + b.HasIndex("ThreadId"); + + b.HasIndex("Uri") + .IsUnique(); + + b.HasIndex("Url"); + + b.HasIndex("UserHost"); + + b.HasIndex("UserId"); + + b.HasIndex("VisibleUserIds"); + + b.HasIndex("CreatedAt", "UserId"); + + b.HasIndex("Id", "UserHost"); + + b.HasIndex("UserId", "Id"); + + b.HasIndex(new[] { "Cw" }, "GIN_TRGM_note_cw"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "Cw" }, "GIN_TRGM_note_cw"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex(new[] { "Cw" }, "GIN_TRGM_note_cw"), new[] { "gin_trgm_ops" }); + + b.HasIndex(new[] { "Text" }, "GIN_TRGM_note_text"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "Text" }, "GIN_TRGM_note_text"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex(new[] { "Text" }, "GIN_TRGM_note_text"), new[] { "gin_trgm_ops" }); + + b.HasIndex(new[] { "Mentions" }, "GIN_note_mentions"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "Mentions" }, "GIN_note_mentions"), "gin"); + + b.HasIndex(new[] { "Tags" }, "GIN_note_tags"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "Tags" }, "GIN_note_tags"), "gin"); + + b.HasIndex(new[] { "VisibleUserIds" }, "GIN_note_visibleUserIds"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "VisibleUserIds" }, "GIN_note_visibleUserIds"), "gin"); + + b.ToTable("note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteBookmark", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the NoteBookmark."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("note_bookmark"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteEdit", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Cw") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("cw"); + + b.Property>("FileIds") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("fileIds") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId") + .HasComment("The ID of note."); + + b.Property("Text") + .HasColumnType("text") + .HasColumnName("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the Note."); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.ToTable("note_edit"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteLike", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("note_like"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteReaction", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the NoteReaction."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("Reaction") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)") + .HasColumnName("reaction"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId", "Reaction") + .IsUnique(); + + b.ToTable("note_reaction"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteThreadMuting", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("ThreadId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("threadId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("ThreadId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "ThreadId") + .IsUnique(); + + b.ToTable("note_thread_muting"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteUnread", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("IsMentioned") + .HasColumnType("boolean") + .HasColumnName("isMentioned"); + + b.Property("IsSpecified") + .HasColumnType("boolean") + .HasColumnName("isSpecified"); + + b.Property("NoteChannelId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteChannelId") + .HasComment("[Denormalized]"); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("NoteUserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteUserId") + .HasComment("[Denormalized]"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("IsMentioned"); + + b.HasIndex("IsSpecified"); + + b.HasIndex("NoteChannelId"); + + b.HasIndex("NoteId"); + + b.HasIndex("NoteUserId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("note_unread"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteWatching", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the NoteWatching."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId") + .HasComment("The target Note ID."); + + b.Property("NoteUserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteUserId") + .HasComment("[Denormalized]"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The watcher ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("NoteId"); + + b.HasIndex("NoteUserId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("note_watching"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Notification", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AppAccessTokenId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("appAccessTokenId"); + + b.Property("BiteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("biteId"); + + b.Property("Choice") + .HasColumnType("integer") + .HasColumnName("choice"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Notification."); + + b.Property("CustomBody") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("customBody"); + + b.Property("CustomHeader") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("customHeader"); + + b.Property("CustomIcon") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("customIcon"); + + b.Property("FollowRequestId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("followRequestId"); + + b.Property("IsRead") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isRead") + .HasComment("Whether the notification was read."); + + b.Property("MastoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("masto_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MastoId")); + + b.Property("NoteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("NotifieeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("notifieeId") + .HasComment("The ID of recipient user of the Notification."); + + b.Property("NotifierId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("notifierId") + .HasComment("The ID of sender user of the Notification."); + + b.Property("Reaction") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("reaction"); + + b.Property("Type") + .HasColumnType("notification_type_enum") + .HasColumnName("type") + .HasComment("The type of the Notification."); + + b.Property("UserGroupInvitationId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userGroupInvitationId"); + + b.HasKey("Id"); + + b.HasIndex("AppAccessTokenId"); + + b.HasIndex("BiteId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FollowRequestId"); + + b.HasIndex("IsRead"); + + b.HasIndex("MastoId"); + + b.HasIndex("NoteId"); + + b.HasIndex("NotifieeId"); + + b.HasIndex("NotifierId"); + + b.HasIndex("Type"); + + b.HasIndex("UserGroupInvitationId"); + + b.ToTable("notification"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.OauthApp", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("clientId") + .HasComment("The client id of the OAuth application"); + + b.Property("ClientSecret") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("clientSecret") + .HasComment("The client secret of the OAuth application"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the OAuth application"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the OAuth application"); + + b.Property>("RedirectUris") + .IsRequired() + .HasColumnType("character varying(512)[]") + .HasColumnName("redirectUris") + .HasComment("The redirect URIs of the OAuth application"); + + b.Property>("Scopes") + .IsRequired() + .HasColumnType("character varying(64)[]") + .HasColumnName("scopes") + .HasComment("The scopes requested by the OAuth application"); + + b.Property("Website") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("website") + .HasComment("The website of the OAuth application"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("oauth_app"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.OauthToken", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Active") + .HasColumnType("boolean") + .HasColumnName("active") + .HasComment("Whether or not the token has been activated"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("appId"); + + b.Property("AutoDetectQuotes") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("autoDetectQuotes") + .HasComment("Whether the backend should automatically detect quote posts coming from this client"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("code") + .HasComment("The auth code for the OAuth token"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the OAuth token"); + + b.Property("LastActiveDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastActiveDate"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("redirectUri") + .HasComment("The redirect URI of the OAuth token"); + + b.Property>("Scopes") + .IsRequired() + .HasColumnType("character varying(64)[]") + .HasColumnName("scopes") + .HasComment("The scopes requested by the OAuth token"); + + b.Property("SupportsHtmlFormatting") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("supportsHtmlFormatting") + .HasComment("Whether the client supports HTML inline formatting (bold, italic, strikethrough, ...)"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("token") + .HasComment("The OAuth token"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("AppId"); + + b.HasIndex("Code"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("oauth_token"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Page", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AlignCenter") + .HasColumnType("boolean") + .HasColumnName("alignCenter"); + + b.Property("Content") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("content") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Page."); + + b.Property("EyeCatchingImageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("eyeCatchingImageId"); + + b.Property("Font") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("font"); + + b.Property("HideTitleWhenPinned") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("hideTitleWhenPinned"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("isPublic"); + + b.Property("LikedCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("likedCount"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Script") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(16384) + .HasColumnType("character varying(16384)") + .HasColumnName("script") + .HasDefaultValueSql("''::character varying"); + + b.Property("Summary") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("summary"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the Page."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The ID of author."); + + b.Property("Variables") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("variables") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("Visibility") + .HasColumnType("page_visibility_enum") + .HasColumnName("visibility"); + + b.Property>("VisibleUserIds") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("visibleUserIds") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EyeCatchingImageId"); + + b.HasIndex("Name"); + + b.HasIndex("UpdatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("VisibleUserIds"); + + b.HasIndex("UserId", "Name") + .IsUnique(); + + b.ToTable("page"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PageLike", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("PageId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("pageId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("PageId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "PageId") + .IsUnique(); + + b.ToTable("page_like"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PasswordResetRequest", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("token"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("Token") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("password_reset_request"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Poll", b => + { + b.Property("NoteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property>("Choices") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(256)[]") + .HasColumnName("choices") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiresAt"); + + b.Property("Multiple") + .HasColumnType("boolean") + .HasColumnName("multiple"); + + b.Property("NoteVisibility") + .HasColumnType("note_visibility_enum") + .HasColumnName("noteVisibility") + .HasComment("[Denormalized]"); + + b.Property("UserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("userHost") + .HasComment("[Denormalized]"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("[Denormalized]"); + + b.Property>("Votes") + .IsRequired() + .HasColumnType("integer[]") + .HasColumnName("votes"); + + b.HasKey("NoteId"); + + b.HasIndex("UserHost"); + + b.HasIndex("UserId"); + + b.ToTable("poll"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PollVote", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Choice") + .HasColumnType("integer") + .HasColumnName("choice"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the PollVote."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId", "Choice") + .IsUnique(); + + b.ToTable("poll_vote"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PromoNote", b => + { + b.Property("NoteId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiresAt"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("[Denormalized]"); + + b.HasKey("NoteId"); + + b.HasIndex("UserId"); + + b.ToTable("promo_note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PromoRead", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the PromoRead."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("promo_read"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PushSubscription", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AuthSecret") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("auth"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Endpoint") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("endpoint"); + + b.Property("OauthTokenId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("oauthTokenId"); + + b.Property("Policy") + .ValueGeneratedOnAdd() + .HasColumnType("push_subscription_policy_enum") + .HasDefaultValue(PushSubscription.PushPolicy.All) + .HasColumnName("policy"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("publickey"); + + b.Property>("Types") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(32)[]") + .HasColumnName("types") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("OauthTokenId") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("push_subscription"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RegistrationInvite", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("registration_invite"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RegistryItem", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the RegistryItem."); + + b.Property("Domain") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("domain"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("key") + .HasComment("The key of the RegistryItem."); + + b.Property>("Scope") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(1024)[]") + .HasColumnName("scope") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the RegistryItem."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.Property("Value") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("value") + .HasDefaultValueSql("'{}'::jsonb") + .HasComment("The value of the RegistryItem."); + + b.HasKey("Id"); + + b.HasIndex("Domain"); + + b.HasIndex("Scope"); + + b.HasIndex("UserId"); + + b.ToTable("registry_item"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Relay", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Inbox") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("inbox"); + + b.Property("Status") + .HasColumnType("relay_status_enum") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("Inbox") + .IsUnique(); + + b.ToTable("relay"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RenoteMuting", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Muting."); + + b.Property("MuteeId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("muteeId") + .HasComment("The mutee user ID."); + + b.Property("MuterId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("muterId") + .HasComment("The muter user ID."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("MuteeId"); + + b.HasIndex("MuterId"); + + b.HasIndex("MuterId", "MuteeId") + .IsUnique(); + + b.ToTable("renote_muting"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Session", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Active") + .HasColumnType("boolean") + .HasColumnName("active") + .HasComment("Whether or not the token has been activated (i.e. 2fa has been confirmed)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the OAuth token"); + + b.Property("LastActiveDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastActiveDate"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("token") + .HasComment("The authorization token"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("session"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.SwSubscription", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("AuthSecret") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("auth"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Endpoint") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("endpoint"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("publickey"); + + b.Property("SendReadMessage") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("sendReadMessage"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("sw_subscription"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UsedUsername", b => + { + b.Property("Username") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("username"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.HasKey("Username"); + + b.ToTable("used_username"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.User", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property>("AlsoKnownAs") + .HasColumnType("text[]") + .HasColumnName("alsoKnownAs") + .HasComment("URIs the user is known as too"); + + b.Property("AvatarBlurhash") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("avatarBlurhash") + .HasComment("The blurhash of the avatar DriveFile"); + + b.Property("AvatarId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("avatarId") + .HasComment("The ID of avatar DriveFile."); + + b.Property("AvatarUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("avatarUrl") + .HasComment("The URL of the avatar DriveFile"); + + b.Property("BannerBlurhash") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("bannerBlurhash") + .HasComment("The blurhash of the banner DriveFile"); + + b.Property("BannerId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("bannerId") + .HasComment("The ID of banner DriveFile."); + + b.Property("BannerUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("bannerUrl") + .HasComment("The URL of the banner DriveFile"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the User."); + + b.Property("DisplayName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the User."); + + b.Property("DriveCapacityOverrideMb") + .HasColumnType("integer") + .HasColumnName("driveCapacityOverrideMb") + .HasComment("Overrides user drive capacity limit"); + + b.Property>("Emojis") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("emojis") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Featured") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("featured") + .HasComment("The featured URL of the User. It will be null if the origin of the user is local."); + + b.Property("FollowersCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("followersCount") + .HasComment("The count of followers."); + + b.Property("FollowersUri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("followersUri") + .HasComment("The URI of the user Follower Collection. It will be null if the origin of the user is local."); + + b.Property("FollowingCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("followingCount") + .HasComment("The count of following."); + + b.Property("HideOnlineStatus") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("hideOnlineStatus"); + + b.Property("Host") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("host") + .HasComment("The host of the User. It will be null if the origin of the user is local."); + + b.Property("Inbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("inbox") + .HasComment("The inbox URL of the User. It will be null if the origin of the user is local."); + + b.Property("IsAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isAdmin") + .HasComment("Whether the User is the admin."); + + b.Property("IsBot") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isBot") + .HasComment("Whether the User is a bot."); + + b.Property("IsCat") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isCat") + .HasComment("Whether the User is a cat."); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isDeleted") + .HasComment("Whether the User is deleted."); + + b.Property("IsExplorable") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("isExplorable") + .HasComment("Whether the User is explorable."); + + b.Property("IsLocked") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isLocked") + .HasComment("Whether the User is locked."); + + b.Property("IsModerator") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isModerator") + .HasComment("Whether the User is a moderator."); + + b.Property("IsSilenced") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isSilenced") + .HasComment("Whether the User is silenced."); + + b.Property("IsSuspended") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isSuspended") + .HasComment("Whether the User is suspended."); + + b.Property("LastActiveDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastActiveDate"); + + b.Property("LastFetchedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastFetchedAt"); + + b.Property("MovedToUri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("movedToUri") + .HasComment("The URI of the new account of the User"); + + b.Property("NotesCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("notesCount") + .HasComment("The count of notes."); + + b.Property("SharedInbox") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("sharedInbox") + .HasComment("The sharedInbox URL of the User. It will be null if the origin of the user is local."); + + b.Property("SpeakAsCat") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("speakAsCat") + .HasComment("Whether to speak as a cat if isCat."); + + b.Property>("Tags") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("tags") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Token") + .HasMaxLength(16) + .HasColumnType("character(16)") + .HasColumnName("token") + .IsFixedLength() + .HasComment("The native access token of the User. It will be null if the origin of the user is local."); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedAt") + .HasComment("The updated date of the User."); + + b.Property("Uri") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("uri") + .HasComment("The URI of the User. It will be null if the origin of the user is local."); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("username") + .HasComment("The username of the User."); + + b.Property("UsernameLower") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("usernameLower") + .HasComment("The username (lowercased) of the User."); + + b.HasKey("Id"); + + b.HasIndex("AvatarId") + .IsUnique(); + + b.HasIndex("BannerId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Host"); + + b.HasIndex("IsAdmin"); + + b.HasIndex("IsExplorable"); + + b.HasIndex("IsModerator"); + + b.HasIndex("LastActiveDate"); + + b.HasIndex("Tags"); + + b.HasIndex("Token") + .IsUnique(); + + b.HasIndex("UpdatedAt"); + + b.HasIndex("Uri"); + + b.HasIndex("UsernameLower"); + + b.HasIndex("UsernameLower", "Host") + .IsUnique(); + + b.ToTable("user"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroup", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserGroup."); + + b.Property("IsPrivate") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("isPrivate"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The ID of owner."); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.ToTable("user_group"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupInvitation", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserGroupInvitation."); + + b.Property("UserGroupId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userGroupId") + .HasComment("The group ID."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The user ID."); + + b.HasKey("Id"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "UserGroupId") + .IsUnique(); + + b.ToTable("user_group_invitation"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupMember", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserGroupMember."); + + b.Property("UserGroupId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userGroupId") + .HasComment("The group ID."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The user ID."); + + b.HasKey("Id"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "UserGroupId") + .IsUnique(); + + b.ToTable("user_group_member"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserKeypair", b => + { + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("PrivateKey") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("privateKey"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("publicKey"); + + b.HasKey("UserId"); + + b.ToTable("user_keypair"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserList", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserList."); + + b.Property("HideFromHomeTl") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("hideFromHomeTl") + .HasComment("Whether posts from list members should be hidden from the home timeline."); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the UserList."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_list"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserListMember", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserListMember."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The user ID."); + + b.Property("UserListId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userListId") + .HasComment("The list ID."); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserListId"); + + b.HasIndex("UserId", "UserListId") + .IsUnique(); + + b.ToTable("user_list_member"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserNotePin", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the UserNotePins."); + + b.Property("NoteId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("noteId"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "NoteId") + .IsUnique(); + + b.ToTable("user_note_pin"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserPending", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("email"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("password"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("user_pending"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserProfile", b => + { + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("AlwaysMarkNsfw") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("alwaysMarkNsfw"); + + b.Property("AutoAcceptFollowed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("autoAcceptFollowed"); + + b.Property("Birthday") + .HasMaxLength(10) + .HasColumnType("character(10)") + .HasColumnName("birthday") + .IsFixedLength() + .HasComment("The birthday (YYYY-MM-DD) of the User."); + + b.Property("CarefulBot") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("carefulBot"); + + b.Property("ClientData") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("clientData") + .HasDefaultValueSql("'{}'::jsonb") + .HasComment("The client-specific data of the User."); + + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("description") + .HasComment("The description (bio) of the User."); + + b.Property("Email") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("email") + .HasComment("The email address of the User."); + + b.Property>("EmailNotificationTypes") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("emailNotificationTypes") + .HasDefaultValueSql("'[\"follow\", \"receiveFollowRequest\", \"groupInvited\"]'::jsonb"); + + b.Property("EmailVerified") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("emailVerified"); + + b.Property("EmailVerifyCode") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("emailVerifyCode"); + + b.Property("EnableWordMute") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("enableWordMute"); + + b.Property("FFVisibility") + .ValueGeneratedOnAdd() + .HasColumnType("user_profile_ffvisibility_enum") + .HasDefaultValue(UserProfile.UserProfileFFVisibility.Public) + .HasColumnName("ffVisibility"); + + b.Property("Fields") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("fields") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("InjectFeaturedNote") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("injectFeaturedNote"); + + b.Property("Integrations") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("integrations") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("Lang") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("lang"); + + b.Property("Location") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("location") + .HasComment("The location of the User."); + + b.Property>("Mentions") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("mentions") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("MentionsResolved") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("mentionsResolved"); + + b.Property("ModerationNote") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("moderationNote") + .HasDefaultValueSql("''::character varying"); + + b.Property>("MutedInstances") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("mutedInstances") + .HasDefaultValueSql("'[]'::jsonb") + .HasComment("List of instances muted by the user."); + + b.Property>>("MutedWords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("mutedWords") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property>("MutingNotificationTypes") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("notification_type_enum[]") + .HasColumnName("mutingNotificationTypes") + .HasDefaultValueSql("'{}'::public.notification_type_enum[]"); + + b.Property("NoCrawle") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("noCrawle") + .HasComment("Whether reject index by crawler."); + + b.Property("Password") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("password") + .HasComment("The password hash of the User. It will be null if the origin of the user is local."); + + b.Property("PinnedPageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("pinnedPageId"); + + b.Property("PreventAiLearning") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("preventAiLearning"); + + b.Property("PublicReactions") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("publicReactions"); + + b.Property("ReceiveAnnouncementEmail") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("receiveAnnouncementEmail"); + + b.Property("Room") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("room") + .HasDefaultValueSql("'{}'::jsonb") + .HasComment("The room data of the User."); + + b.Property("SecurityKeysAvailable") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("securityKeysAvailable"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("twoFactorEnabled"); + + b.Property("TwoFactorSecret") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("twoFactorSecret"); + + b.Property("TwoFactorTempSecret") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("twoFactorTempSecret"); + + b.Property("Url") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("url") + .HasComment("Remote URL of the user."); + + b.Property("UsePasswordLessLogin") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("usePasswordLessLogin"); + + b.Property("UserHost") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("userHost") + .HasComment("[Denormalized]"); + + b.HasKey("UserId"); + + b.HasIndex("EnableWordMute"); + + b.HasIndex("PinnedPageId") + .IsUnique(); + + b.HasIndex("UserHost"); + + b.ToTable("user_profile"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserPublickey", b => + { + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("KeyId") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("keyId"); + + b.Property("KeyPem") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("keyPem"); + + b.HasKey("UserId"); + + b.HasIndex("KeyId") + .IsUnique(); + + b.ToTable("user_publickey"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserSecurityKey", b => + { + b.Property("Id") + .HasColumnType("character varying") + .HasColumnName("id") + .HasComment("Variable-length id given to navigator.credentials.get()"); + + b.Property("LastUsed") + .HasColumnType("timestamp with time zone") + .HasColumnName("lastUsed") + .HasComment("The date of the last time the UserSecurityKey was successfully validated."); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasColumnName("name") + .HasComment("User-defined name for this key"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("character varying") + .HasColumnName("publicKey") + .HasComment("Variable-length public key used to verify attestations (hex-encoded)."); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.HasKey("Id"); + + b.HasIndex("PublicKey"); + + b.HasIndex("UserId"); + + b.ToTable("user_security_key"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserSettings", b => + { + b.Property("UserId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId"); + + b.Property("DefaultNoteVisibility") + .ValueGeneratedOnAdd() + .HasColumnType("note_visibility_enum") + .HasDefaultValue(Note.NoteVisibility.Public) + .HasColumnName("defaultNoteVisibility"); + + b.Property("PrivateMode") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("privateMode"); + + b.HasKey("UserId"); + + b.ToTable("user_settings"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Webhook", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("id"); + + b.Property("Active") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("active"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdAt") + .HasComment("The created date of the Antenna."); + + b.Property("LatestSentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("latestSentAt"); + + b.Property("LatestStatus") + .HasColumnType("integer") + .HasColumnName("latestStatus"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name") + .HasComment("The name of the Antenna."); + + b.Property>("On") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("character varying(128)[]") + .HasColumnName("on") + .HasDefaultValueSql("'{}'::character varying[]"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("secret"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("url"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("userId") + .HasComment("The owner ID."); + + b.HasKey("Id"); + + b.HasIndex("Active"); + + b.HasIndex("On"); + + b.HasIndex("UserId"); + + b.ToTable("webhook"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Worker", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("id"); + + b.Property("Heartbeat") + .HasColumnType("timestamp with time zone") + .HasColumnName("heartbeat"); + + b.HasKey("Id"); + + b.HasIndex("Heartbeat"); + + b.HasIndex("Id"); + + b.ToTable("worker"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AbuseUserReport", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Assignee") + .WithMany("AbuseUserReportAssignees") + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Reporter") + .WithMany("AbuseUserReportReporters") + .HasForeignKey("ReporterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "TargetUser") + .WithMany("AbuseUserReportTargetUsers") + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignee"); + + b.Navigation("Reporter"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AnnouncementRead", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Announcement", "Announcement") + .WithMany("AnnouncementReads") + .HasForeignKey("AnnouncementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("AnnouncementReads") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Announcement"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Antenna", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserGroupMember", "UserGroupMember") + .WithMany("Antennas") + .HasForeignKey("UserGroupMemberId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Antennas") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserList", "UserList") + .WithMany("Antennas") + .HasForeignKey("UserListId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + + b.Navigation("UserGroupMember"); + + b.Navigation("UserList"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.AttestationChallenge", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("AttestationChallenges") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Bite", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Bite", "TargetBite") + .WithMany() + .HasForeignKey("TargetBiteId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "TargetNote") + .WithMany() + .HasForeignKey("TargetNoteId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TargetBite"); + + b.Navigation("TargetNote"); + + b.Navigation("TargetUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Blocking", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Blockee") + .WithMany("IncomingBlocks") + .HasForeignKey("BlockeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Blocker") + .WithMany("OutgoingBlocks") + .HasForeignKey("BlockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blockee"); + + b.Navigation("Blocker"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Channel", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFile", "Banner") + .WithMany("Channels") + .HasForeignKey("BannerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Channels") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Banner"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ChannelFollowing", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Channel", "Followee") + .WithMany("ChannelFollowings") + .HasForeignKey("FolloweeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Follower") + .WithMany("ChannelFollowings") + .HasForeignKey("FollowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Followee"); + + b.Navigation("Follower"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ChannelNotePin", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Channel", "Channel") + .WithMany("ChannelNotePins") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("ChannelNotePins") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Clip", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Clips") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ClipNote", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Clip", "Clip") + .WithMany("ClipNotes") + .HasForeignKey("ClipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("ClipNotes") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Clip"); + + b.Navigation("Note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFile", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFolder", "Folder") + .WithMany("DriveFiles") + .HasForeignKey("FolderId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("DriveFiles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Folder"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFolder", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFolder", "Parent") + .WithMany("InverseParent") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("DriveFolders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Parent"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Filter", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Filters") + .HasForeignKey("user_id") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Followee") + .WithMany("IncomingFollowRequests") + .HasForeignKey("FolloweeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Follower") + .WithMany("OutgoingFollowRequests") + .HasForeignKey("FollowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Followee"); + + b.Navigation("Follower"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Following", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Followee") + .WithMany("IncomingFollowRelationships") + .HasForeignKey("FolloweeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Follower") + .WithMany("OutgoingFollowRelationships") + .HasForeignKey("FollowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Followee"); + + b.Navigation("Follower"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.GalleryLike", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.GalleryPost", "Post") + .WithMany("GalleryLikes") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("GalleryLikes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.GalleryPost", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("GalleryPosts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Job", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Worker", null) + .WithMany() + .HasForeignKey("WorkerId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Marker", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Markers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.MessagingMessage", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFile", "File") + .WithMany("MessagingMessages") + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserGroup", "Group") + .WithMany("MessagingMessages") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Recipient") + .WithMany("MessagingMessageRecipients") + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("MessagingMessageUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Group"); + + b.Navigation("Recipient"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.ModerationLog", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("ModerationLogs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Muting", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Mutee") + .WithMany("IncomingMutes") + .HasForeignKey("MuteeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Muter") + .WithMany("OutgoingMutes") + .HasForeignKey("MuterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Mutee"); + + b.Navigation("Muter"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Note", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Channel", "Channel") + .WithMany("Notes") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Renote") + .WithMany("InverseRenote") + .HasForeignKey("RenoteId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Reply") + .WithMany("InverseReply") + .HasForeignKey("ReplyId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Notes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + + b.Navigation("Renote"); + + b.Navigation("Reply"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteBookmark", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteBookmarks") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteBookmarks") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteEdit", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteEdits") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteLike", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteLikes") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteLikes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteReaction", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteReactions") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteReactions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteThreadMuting", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteThreadMutings") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteUnread", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteUnreads") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteUnreads") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteWatching", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("NoteWatchings") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("NoteWatchings") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Notification", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Bite", "Bite") + .WithMany() + .HasForeignKey("BiteId"); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", "FollowRequest") + .WithMany("Notifications") + .HasForeignKey("FollowRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("Notifications") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Notifiee") + .WithMany("NotificationNotifiees") + .HasForeignKey("NotifieeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Notifier") + .WithMany("NotificationNotifiers") + .HasForeignKey("NotifierId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserGroupInvitation", "UserGroupInvitation") + .WithMany("Notifications") + .HasForeignKey("UserGroupInvitationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Bite"); + + b.Navigation("FollowRequest"); + + b.Navigation("Note"); + + b.Navigation("Notifiee"); + + b.Navigation("Notifier"); + + b.Navigation("UserGroupInvitation"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.OauthToken", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.OauthApp", "App") + .WithMany("OauthTokens") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("OauthTokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Page", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFile", "EyeCatchingImage") + .WithMany("Pages") + .HasForeignKey("EyeCatchingImageId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Pages") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EyeCatchingImage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PageLike", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Page", "Page") + .WithMany("PageLikes") + .HasForeignKey("PageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("PageLikes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Page"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PasswordResetRequest", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("PasswordResetRequests") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Poll", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithOne("Poll") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.Poll", "NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PollVote", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("PollVotes") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("PollVotes") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PromoNote", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithOne("PromoNote") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.PromoNote", "NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PromoRead", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("PromoReads") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("PromoReads") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.PushSubscription", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.OauthToken", "OauthToken") + .WithOne("PushSubscription") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.PushSubscription", "OauthTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("PushSubscriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OauthToken"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RegistryItem", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("RegistryItems") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RenoteMuting", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Mutee") + .WithMany("RenoteMutingMutees") + .HasForeignKey("MuteeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Muter") + .WithMany("RenoteMutingMuters") + .HasForeignKey("MuterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Mutee"); + + b.Navigation("Muter"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Session", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Sessions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.SwSubscription", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("SwSubscriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.User", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFile", "Avatar") + .WithOne("UserAvatar") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.User", "AvatarId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.DriveFile", "Banner") + .WithOne("UserBanner") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.User", "BannerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Avatar"); + + b.Navigation("Banner"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroup", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserGroups") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupInvitation", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserGroup", "UserGroup") + .WithMany("UserGroupInvitations") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserGroupInvitations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("UserGroup"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupMember", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserGroup", "UserGroup") + .WithMany("UserGroupMembers") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserGroupMemberships") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("UserGroup"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserKeypair", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithOne("UserKeypair") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.UserKeypair", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserList", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserLists") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserListMember", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserListMembers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.UserList", "UserList") + .WithMany("UserListMembers") + .HasForeignKey("UserListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("UserList"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserNotePin", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Note", "Note") + .WithMany("UserNotePins") + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserNotePins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Note"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserProfile", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.Page", "PinnedPage") + .WithOne("UserProfile") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.UserProfile", "PinnedPageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithOne("UserProfile") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.UserProfile", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PinnedPage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserPublickey", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithOne("UserPublickey") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.UserPublickey", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserSecurityKey", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("UserSecurityKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserSettings", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithOne("UserSettings") + .HasForeignKey("Iceshrimp.Backend.Core.Database.Tables.UserSettings", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Webhook", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Webhooks") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Announcement", b => + { + b.Navigation("AnnouncementReads"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Channel", b => + { + b.Navigation("ChannelFollowings"); + + b.Navigation("ChannelNotePins"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Clip", b => + { + b.Navigation("ClipNotes"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFile", b => + { + b.Navigation("Channels"); + + b.Navigation("MessagingMessages"); + + b.Navigation("Pages"); + + b.Navigation("UserAvatar"); + + b.Navigation("UserBanner"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.DriveFolder", b => + { + b.Navigation("DriveFiles"); + + b.Navigation("InverseParent"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", b => + { + b.Navigation("Notifications"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.GalleryPost", b => + { + b.Navigation("GalleryLikes"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Note", b => + { + b.Navigation("ChannelNotePins"); + + b.Navigation("ClipNotes"); + + b.Navigation("InverseRenote"); + + b.Navigation("InverseReply"); + + b.Navigation("NoteBookmarks"); + + b.Navigation("NoteEdits"); + + b.Navigation("NoteLikes"); + + b.Navigation("NoteReactions"); + + b.Navigation("NoteUnreads"); + + b.Navigation("NoteWatchings"); + + b.Navigation("Notifications"); + + b.Navigation("Poll"); + + b.Navigation("PollVotes"); + + b.Navigation("PromoNote"); + + b.Navigation("PromoReads"); + + b.Navigation("UserNotePins"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.OauthApp", b => + { + b.Navigation("OauthTokens"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.OauthToken", b => + { + b.Navigation("PushSubscription"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Page", b => + { + b.Navigation("PageLikes"); + + b.Navigation("UserProfile"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.User", b => + { + b.Navigation("AbuseUserReportAssignees"); + + b.Navigation("AbuseUserReportReporters"); + + b.Navigation("AbuseUserReportTargetUsers"); + + b.Navigation("AnnouncementReads"); + + b.Navigation("Antennas"); + + b.Navigation("AttestationChallenges"); + + b.Navigation("ChannelFollowings"); + + b.Navigation("Channels"); + + b.Navigation("Clips"); + + b.Navigation("DriveFiles"); + + b.Navigation("DriveFolders"); + + b.Navigation("Filters"); + + b.Navigation("GalleryLikes"); + + b.Navigation("GalleryPosts"); + + b.Navigation("IncomingBlocks"); + + b.Navigation("IncomingFollowRelationships"); + + b.Navigation("IncomingFollowRequests"); + + b.Navigation("IncomingMutes"); + + b.Navigation("Markers"); + + b.Navigation("MessagingMessageRecipients"); + + b.Navigation("MessagingMessageUsers"); + + b.Navigation("ModerationLogs"); + + b.Navigation("NoteBookmarks"); + + b.Navigation("NoteLikes"); + + b.Navigation("NoteReactions"); + + b.Navigation("NoteThreadMutings"); + + b.Navigation("NoteUnreads"); + + b.Navigation("NoteWatchings"); + + b.Navigation("Notes"); + + b.Navigation("NotificationNotifiees"); + + b.Navigation("NotificationNotifiers"); + + b.Navigation("OauthTokens"); + + b.Navigation("OutgoingBlocks"); + + b.Navigation("OutgoingFollowRelationships"); + + b.Navigation("OutgoingFollowRequests"); + + b.Navigation("OutgoingMutes"); + + b.Navigation("PageLikes"); + + b.Navigation("Pages"); + + b.Navigation("PasswordResetRequests"); + + b.Navigation("PollVotes"); + + b.Navigation("PromoReads"); + + b.Navigation("PushSubscriptions"); + + b.Navigation("RegistryItems"); + + b.Navigation("RenoteMutingMutees"); + + b.Navigation("RenoteMutingMuters"); + + b.Navigation("Sessions"); + + b.Navigation("SwSubscriptions"); + + b.Navigation("UserGroupInvitations"); + + b.Navigation("UserGroupMemberships"); + + b.Navigation("UserGroups"); + + b.Navigation("UserKeypair"); + + b.Navigation("UserListMembers"); + + b.Navigation("UserLists"); + + b.Navigation("UserNotePins"); + + b.Navigation("UserProfile"); + + b.Navigation("UserPublickey"); + + b.Navigation("UserSecurityKeys"); + + b.Navigation("UserSettings"); + + b.Navigation("Webhooks"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroup", b => + { + b.Navigation("MessagingMessages"); + + b.Navigation("UserGroupInvitations"); + + b.Navigation("UserGroupMembers"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupInvitation", b => + { + b.Navigation("Notifications"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserGroupMember", b => + { + b.Navigation("Antennas"); + }); + + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.UserList", b => + { + b.Navigation("Antennas"); + + b.Navigation("UserListMembers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.cs b/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.cs new file mode 100644 index 00000000..5be8dbd7 --- /dev/null +++ b/Iceshrimp.Backend/Core/Database/Migrations/20240331190007_AddFilterTable.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using Iceshrimp.Backend.Core.Database.Tables; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Iceshrimp.Backend.Core.Database.Migrations +{ + /// + public partial class AddFilterTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances") + .Annotation("Npgsql:Enum:filter_action_enum", "warn,hide") + .Annotation("Npgsql:Enum:filter_context_enum", "home,lists,threads,notifications,accounts,public") + .Annotation("Npgsql:Enum:job_status", "queued,delayed,running,completed,failed") + .Annotation("Npgsql:Enum:marker_type_enum", "home,notifications") + .Annotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified") + .Annotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit,bite") + .Annotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified") + .Annotation("Npgsql:Enum:push_subscription_policy_enum", "all,followed,follower,none") + .Annotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected") + .Annotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private") + .Annotation("Npgsql:PostgresExtension:pg_trgm", ",,") + .OldAnnotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances") + .OldAnnotation("Npgsql:Enum:job_status", "queued,delayed,running,completed,failed") + .OldAnnotation("Npgsql:Enum:marker_type_enum", "home,notifications") + .OldAnnotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified") + .OldAnnotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit,bite") + .OldAnnotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified") + .OldAnnotation("Npgsql:Enum:push_subscription_policy_enum", "all,followed,follower,none") + .OldAnnotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected") + .OldAnnotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private") + .OldAnnotation("Npgsql:PostgresExtension:pg_trgm", ",,"); + + migrationBuilder.CreateTable( + name: "filter", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "character varying(32)", nullable: true), + name = table.Column(type: "text", nullable: false), + expiry = table.Column(type: "timestamp with time zone", nullable: true), + keywords = table.Column>(type: "text[]", nullable: false, defaultValueSql: "'{}'::varchar[]"), + contexts = table.Column>(type: "filter_context_enum[]", nullable: false, defaultValueSql: "'{}'::public.filter_context_enum[]"), + action = table.Column(type: "filter_action_enum", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_filter", x => x.id); + table.ForeignKey( + name: "FK_filter_user_user_id", + column: x => x.user_id, + principalTable: "user", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_filter_user_id", + table: "filter", + column: "user_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "filter"); + + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances") + .Annotation("Npgsql:Enum:job_status", "queued,delayed,running,completed,failed") + .Annotation("Npgsql:Enum:marker_type_enum", "home,notifications") + .Annotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified") + .Annotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit,bite") + .Annotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified") + .Annotation("Npgsql:Enum:push_subscription_policy_enum", "all,followed,follower,none") + .Annotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected") + .Annotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private") + .Annotation("Npgsql:PostgresExtension:pg_trgm", ",,") + .OldAnnotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances") + .OldAnnotation("Npgsql:Enum:filter_action_enum", "warn,hide") + .OldAnnotation("Npgsql:Enum:filter_context_enum", "home,lists,threads,notifications,accounts,public") + .OldAnnotation("Npgsql:Enum:job_status", "queued,delayed,running,completed,failed") + .OldAnnotation("Npgsql:Enum:marker_type_enum", "home,notifications") + .OldAnnotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified") + .OldAnnotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit,bite") + .OldAnnotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified") + .OldAnnotation("Npgsql:Enum:push_subscription_policy_enum", "all,followed,follower,none") + .OldAnnotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected") + .OldAnnotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private") + .OldAnnotation("Npgsql:PostgresExtension:pg_trgm", ",,"); + } + } +} diff --git a/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs b/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs index 86755cca..26932a37 100644 --- a/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -23,6 +23,8 @@ namespace Iceshrimp.Backend.Core.Database.Migrations .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "antenna_src_enum", new[] { "home", "all", "users", "list", "group", "instances" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "filter_action_enum", new[] { "warn", "hide" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "filter_context_enum", new[] { "home", "lists", "threads", "notifications", "accounts", "public" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "job_status", new[] { "queued", "delayed", "running", "completed", "failed" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "marker_type_enum", new[] { "home", "notifications" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "note_visibility_enum", new[] { "public", "home", "followers", "specified" }); @@ -1044,6 +1046,52 @@ namespace Iceshrimp.Backend.Core.Database.Migrations b.ToTable("emoji"); }); + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Filter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("filter_action_enum") + .HasColumnName("action"); + + b.Property>("Contexts") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("filter_context_enum[]") + .HasColumnName("contexts") + .HasDefaultValueSql("'{}'::public.filter_context_enum[]"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry"); + + b.Property>("Keywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text[]") + .HasColumnName("keywords") + .HasDefaultValueSql("'{}'::varchar[]"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("user_id") + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.HasIndex("user_id"); + + b.ToTable("filter"); + }); + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", b => { b.Property("Id") @@ -5047,6 +5095,16 @@ namespace Iceshrimp.Backend.Core.Database.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.Filter", b => + { + b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "User") + .WithMany("Filters") + .HasForeignKey("user_id") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.FollowRequest", b => { b.HasOne("Iceshrimp.Backend.Core.Database.Tables.User", "Followee") @@ -5901,6 +5959,8 @@ namespace Iceshrimp.Backend.Core.Database.Migrations b.Navigation("DriveFolders"); + b.Navigation("Filters"); + b.Navigation("GalleryLikes"); b.Navigation("GalleryPosts"); diff --git a/Iceshrimp.Backend/Core/Database/Tables/Filter.cs b/Iceshrimp.Backend/Core/Database/Tables/Filter.cs new file mode 100644 index 00000000..672cf5d8 --- /dev/null +++ b/Iceshrimp.Backend/Core/Database/Tables/Filter.cs @@ -0,0 +1,57 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NpgsqlTypes; + +namespace Iceshrimp.Backend.Core.Database.Tables; + +[Table("filter")] +[Index("user_id")] +public class Filter +{ + [PgName("filter_context_enum")] + public enum FilterContext + { + [PgName("home")] Home, + [PgName("lists")] Lists, + [PgName("threads")] Threads, + [PgName("notifications")] Notifications, + [PgName("accounts")] Accounts, + [PgName("public")] Public, + } + + [PgName("filter_action_enum")] + public enum FilterAction + { + [PgName("warn")] Warn, + [PgName("hide")] Hide, + } + + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column("id")] + public long Id { get; set; } + + [InverseProperty(nameof(User.Filters))] + public User User { get; set; } = null!; + + [Column("name")] public string Name { get; set; } = null!; + [Column("expiry")] public DateTime? Expiry { get; set; } + [Column("keywords")] public List Keywords { get; set; } = []; + [Column("contexts")] public List Contexts { get; set; } = []; + [Column("action")] public FilterAction Action { get; set; } +} + +public class FilterEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder entity) + { + entity.HasOne(p => p.User) + .WithMany(p => p.Filters) + .OnDelete(DeleteBehavior.Cascade) + .HasForeignKey("user_id"); + + entity.Property(p => p.Keywords).HasDefaultValueSql("'{}'::varchar[]"); + entity.Property(p => p.Contexts).HasDefaultValueSql("'{}'::public.filter_context_enum[]"); + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Database/Tables/User.cs b/Iceshrimp.Backend/Core/Database/Tables/User.cs index 7404c03c..ad0236c9 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/User.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/User.cs @@ -4,6 +4,7 @@ using EntityFrameworkCore.Projectables; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Helpers; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Iceshrimp.Backend.Core.Database.Tables; @@ -481,6 +482,9 @@ public class User : IEntity [InverseProperty(nameof(Webhook.User))] public virtual ICollection Webhooks { get; set; } = new List(); + + [InverseProperty(nameof(Filter.User))] + public virtual ICollection Filters { get; set; } = new List(); [NotMapped] public bool? PrecomputedIsBlocking { get; set; } [NotMapped] public bool? PrecomputedIsBlockedBy { get; set; } @@ -619,4 +623,90 @@ public class User : IEntity : throw new Exception("Cannot access PublicUrl for remote user"); public string GetIdenticonUrl(string webDomain) => $"https://{webDomain}/identicon/{Id}"; +} + +public class UserEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder entity) + { + entity.Property(e => e.AlsoKnownAs).HasComment("URIs the user is known as too"); + entity.Property(e => e.AvatarBlurhash).HasComment("The blurhash of the avatar DriveFile"); + entity.Property(e => e.AvatarId).HasComment("The ID of avatar DriveFile."); + entity.Property(e => e.AvatarUrl).HasComment("The URL of the avatar DriveFile"); + entity.Property(e => e.BannerBlurhash).HasComment("The blurhash of the banner DriveFile"); + entity.Property(e => e.BannerId).HasComment("The ID of banner DriveFile."); + entity.Property(e => e.BannerUrl).HasComment("The URL of the banner DriveFile"); + entity.Property(e => e.CreatedAt).HasComment("The created date of the User."); + entity.Property(e => e.DriveCapacityOverrideMb).HasComment("Overrides user drive capacity limit"); + entity.Property(e => e.Emojis).HasDefaultValueSql("'{}'::character varying[]"); + entity.Property(e => e.Featured) + .HasComment("The featured URL of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.FollowersCount) + .HasDefaultValue(0) + .HasComment("The count of followers."); + entity.Property(e => e.FollowersUri) + .HasComment("The URI of the user Follower Collection. It will be null if the origin of the user is local."); + entity.Property(e => e.FollowingCount) + .HasDefaultValue(0) + .HasComment("The count of following."); + entity.Property(e => e.HideOnlineStatus).HasDefaultValue(false); + entity.Property(e => e.Host) + .HasComment("The host of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.Inbox) + .HasComment("The inbox URL of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.IsAdmin) + .HasDefaultValue(false) + .HasComment("Whether the User is the admin."); + entity.Property(e => e.IsBot) + .HasDefaultValue(false) + .HasComment("Whether the User is a bot."); + entity.Property(e => e.IsCat) + .HasDefaultValue(false) + .HasComment("Whether the User is a cat."); + entity.Property(e => e.IsDeleted) + .HasDefaultValue(false) + .HasComment("Whether the User is deleted."); + entity.Property(e => e.IsExplorable) + .HasDefaultValue(true) + .HasComment("Whether the User is explorable."); + entity.Property(e => e.IsLocked) + .HasDefaultValue(false) + .HasComment("Whether the User is locked."); + entity.Property(e => e.IsModerator) + .HasDefaultValue(false) + .HasComment("Whether the User is a moderator."); + entity.Property(e => e.IsSilenced) + .HasDefaultValue(false) + .HasComment("Whether the User is silenced."); + entity.Property(e => e.IsSuspended) + .HasDefaultValue(false) + .HasComment("Whether the User is suspended."); + entity.Property(e => e.MovedToUri).HasComment("The URI of the new account of the User"); + entity.Property(e => e.DisplayName).HasComment("The name of the User."); + entity.Property(e => e.NotesCount) + .HasDefaultValue(0) + .HasComment("The count of notes."); + entity.Property(e => e.SharedInbox) + .HasComment("The sharedInbox URL of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.SpeakAsCat) + .HasDefaultValue(true) + .HasComment("Whether to speak as a cat if isCat."); + entity.Property(e => e.Tags).HasDefaultValueSql("'{}'::character varying[]"); + entity.Property(e => e.Token) + .IsFixedLength() + .HasComment("The native access token of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.UpdatedAt).HasComment("The updated date of the User."); + entity.Property(e => e.Uri) + .HasComment("The URI of the User. It will be null if the origin of the user is local."); + entity.Property(e => e.Username).HasComment("The username of the User."); + entity.Property(e => e.UsernameLower).HasComment("The username (lowercased) of the User."); + + entity.HasOne(d => d.Avatar) + .WithOne(p => p.UserAvatar) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(d => d.Banner) + .WithOne(p => p.UserBanner) + .OnDelete(DeleteBehavior.SetNull); + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 2611e5dd..444def9a 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -457,13 +457,13 @@ public static class QueryableExtensions } public static async Task> RenderAllForMastodonAsync( - this IQueryable notes, NoteRenderer renderer, User? user + this IQueryable notes, NoteRenderer renderer, User? user, Filter.FilterContext? filterContext = null ) { var list = (await notes.ToListAsync()) .EnforceRenoteReplyVisibility() .ToList(); - return (await renderer.RenderManyAsync(list, user)).ToList(); + return (await renderer.RenderManyAsync(list, user, filterContext)).ToList(); } public static async Task> RenderAllForMastodonAsync( diff --git a/Iceshrimp.Backend/Core/Helpers/FilterHelper.cs b/Iceshrimp.Backend/Core/Helpers/FilterHelper.cs new file mode 100644 index 00000000..4e36c8ec --- /dev/null +++ b/Iceshrimp.Backend/Core/Helpers/FilterHelper.cs @@ -0,0 +1,58 @@ +using System.Text.RegularExpressions; +using Iceshrimp.Backend.Core.Database.Tables; + +namespace Iceshrimp.Backend.Core.Helpers; + +public static class FilterHelper +{ + public static (Filter filter, string keyword)? IsFiltered(IEnumerable notes, List filters) + { + if (filters.Count == 0) return null; + + foreach (var note in notes.OfType()) + { + var match = IsFiltered(note, filters); + if (match != null) return match; + } + + return null; + } + + private static (Filter filter, string keyword)? IsFiltered(Note note, List filters) + { + if (filters.Count == 0) return null; + if (note.Text == null && note.Cw == null) return null; + + foreach (var filter in filters) + { + var match = IsFiltered(note, filter); + if (match != null) return (filter, match); + } + + return null; + } + + private static string? IsFiltered(Note note, Filter filter) + { + foreach (var keyword in filter.Keywords) + { + if (keyword.StartsWith('"') && keyword.EndsWith('"')) + { + var pattern = $@"\b{EfHelpers.EscapeRegexQuery(keyword[1..^1])}\b"; + var regex = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(10)); + + if (note.Text != null && regex.IsMatch(note.Text)) + return keyword; + if (note.Cw != null && regex.IsMatch(note.Cw)) + return keyword; + } + else if ((note.Text != null && note.Text.Contains(keyword, StringComparison.InvariantCultureIgnoreCase)) || + note.Cw != null && note.Cw.Contains(keyword, StringComparison.InvariantCultureIgnoreCase)) + { + return keyword; + } + } + + return null; + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs index 400d3df9..fc180f30 100644 --- a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs +++ b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs @@ -36,6 +36,9 @@ public class BackgroundTaskQueue() case MuteExpiryJobData muteExpiryJob: await ProcessMuteExpiry(muteExpiryJob, scope, token); break; + case FilterExpiryJobData filterExpiryJob: + await ProcessFilterExpiry(filterExpiryJob, scope, token); + break; } } @@ -192,11 +195,28 @@ public class BackgroundTaskQueue() var eventSvc = scope.GetRequiredService(); eventSvc.RaiseUserUnmuted(null, muting.Muter, muting.Mutee); } + + private static async Task ProcessFilterExpiry( + FilterExpiryJobData jobData, + IServiceProvider scope, + CancellationToken token + ) + { + var db = scope.GetRequiredService(); + var filter = await db.Filters.FirstOrDefaultAsync(p => p.Id == jobData.FilterId, token); + + if (filter is not { Expiry: not null }) return; + if (filter.Expiry > DateTime.UtcNow + TimeSpan.FromSeconds(30)) return; + + db.Remove(filter); + await db.SaveChangesAsync(token); + } } [JsonDerivedType(typeof(DriveFileDeleteJobData), "driveFileDelete")] [JsonDerivedType(typeof(PollExpiryJobData), "pollExpiry")] [JsonDerivedType(typeof(MuteExpiryJobData), "muteExpiry")] +[JsonDerivedType(typeof(FilterExpiryJobData), "filterExpiry")] public abstract class BackgroundTaskJobData; public class DriveFileDeleteJobData : BackgroundTaskJobData @@ -213,4 +233,9 @@ public class PollExpiryJobData : BackgroundTaskJobData public class MuteExpiryJobData : BackgroundTaskJobData { [JR] [J("muteId")] public required string MuteId { get; set; } +} + +public class FilterExpiryJobData : BackgroundTaskJobData +{ + [JR] [J("filterId")] public required long FilterId { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Tasks/FilterExpiryTask.cs b/Iceshrimp.Backend/Core/Tasks/FilterExpiryTask.cs new file mode 100644 index 00000000..cffc3c8a --- /dev/null +++ b/Iceshrimp.Backend/Core/Tasks/FilterExpiryTask.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Services; +using Microsoft.EntityFrameworkCore; + +namespace Iceshrimp.Backend.Core.Tasks; + +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "Instantiated at runtime by CronService")] +public class FilterExpiryTask : ICronTask +{ + public async Task Invoke(IServiceProvider provider) + { + var db = provider.GetRequiredService(); + await db.Filters.Where(p => p.Expiry != null && p.Expiry < DateTime.UtcNow - TimeSpan.FromMinutes(5)) + .ExecuteDeleteAsync(); + } + + // Midnight + public TimeSpan Trigger => TimeSpan.Zero; + public CronTaskType Type => CronTaskType.Daily; +} \ No newline at end of file