Compare commits
	
		
			3 Commits
		
	
	
		
			b1543f5b08
			...
			30db6ad9f1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 30db6ad9f1 | |||
| 24f1a3a9e9 | |||
| f7e926ad24 | 
@@ -1,4 +1,5 @@
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
			
		||||
 | 
			
		||||
namespace DysonNetwork.Sphere.Activity;
 | 
			
		||||
 | 
			
		||||
@@ -6,6 +7,7 @@ public enum ActivityVisibility
 | 
			
		||||
{
 | 
			
		||||
    Public,
 | 
			
		||||
    Friends,
 | 
			
		||||
    Selected
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class Activity : ModelBase
 | 
			
		||||
@@ -14,8 +16,11 @@ public class Activity : ModelBase
 | 
			
		||||
    [MaxLength(1024)] public string Type { get; set; } = null!;
 | 
			
		||||
    [MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
 | 
			
		||||
    public ActivityVisibility Visibility { get; set; } = ActivityVisibility.Public;
 | 
			
		||||
    public Dictionary<string, object> Meta =  new();
 | 
			
		||||
    [Column(TypeName = "jsonb")] public Dictionary<string, object> Meta = new();
 | 
			
		||||
    [Column(TypeName = "jsonb")] public ICollection<long> UsersVisible = new List<long>();
 | 
			
		||||
 | 
			
		||||
    public long AccountId { get; set; }
 | 
			
		||||
    public Account.Account Account { get; set; } = null!;
 | 
			
		||||
 | 
			
		||||
    [NotMapped] public object? Data { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,21 +13,22 @@ public class ActivityController(AppDatabase db, ActivityService act, Relationshi
 | 
			
		||||
    {
 | 
			
		||||
        HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
 | 
			
		||||
        var currentUser = currentUserValue as Account.Account;
 | 
			
		||||
        var userFriends = await rels.ListAccountFriends(currentUser!);
 | 
			
		||||
        var userFriends = currentUser is null ? null : await rels.ListAccountFriends(currentUser);
 | 
			
		||||
 | 
			
		||||
        var totalCount = await db.Activities
 | 
			
		||||
            .FilterWithVisibility(currentUser, userFriends)
 | 
			
		||||
            .CountAsync();
 | 
			
		||||
        var posts = await db.Activities
 | 
			
		||||
        var activities = await db.Activities
 | 
			
		||||
            .Include(e => e.Account)
 | 
			
		||||
            .FilterWithVisibility(currentUser, userFriends)
 | 
			
		||||
            .OrderByDescending(e => e.CreatedAt)
 | 
			
		||||
            .Skip(offset)
 | 
			
		||||
            .Take(take)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        activities = await act.LoadActivityData(activities, currentUser, userFriends);
 | 
			
		||||
 | 
			
		||||
        Response.Headers["X-Total"] = totalCount.ToString();
 | 
			
		||||
 | 
			
		||||
        return Ok(posts); 
 | 
			
		||||
        return Ok(activities); 
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,60 @@
 | 
			
		||||
using DysonNetwork.Sphere.Post;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
 | 
			
		||||
namespace DysonNetwork.Sphere.Activity;
 | 
			
		||||
 | 
			
		||||
public class ActivityService(AppDatabase db)
 | 
			
		||||
{
 | 
			
		||||
    public async Task<List<Activity>> LoadActivityData(List<Activity> input, Account.Account? currentUser,
 | 
			
		||||
        List<long> userFriends)
 | 
			
		||||
    {
 | 
			
		||||
        if (input.Count == 0) return input;
 | 
			
		||||
 | 
			
		||||
        var postsId = input
 | 
			
		||||
            .Where(e => e.ResourceIdentifier.StartsWith("posts/"))
 | 
			
		||||
            .Select(e => long.Parse(e.ResourceIdentifier.Split("/").Last()))
 | 
			
		||||
            .Distinct()
 | 
			
		||||
            .ToList();
 | 
			
		||||
 | 
			
		||||
        if (postsId.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var posts = await db.Posts.Where(e => postsId.Contains(e.Id))
 | 
			
		||||
                .Include(e => e.Publisher)
 | 
			
		||||
                .Include(e => e.Publisher.Picture)
 | 
			
		||||
                .Include(e => e.Publisher.Background)
 | 
			
		||||
                .Include(e => e.ThreadedPost)
 | 
			
		||||
                .Include(e => e.ForwardedPost)
 | 
			
		||||
                .Include(e => e.Attachments)
 | 
			
		||||
                .Include(e => e.Categories)
 | 
			
		||||
                .Include(e => e.Tags)
 | 
			
		||||
                .FilterWithVisibility(currentUser, userFriends)
 | 
			
		||||
                .ToListAsync();
 | 
			
		||||
            posts = PostService.TruncatePostContent(posts);
 | 
			
		||||
 | 
			
		||||
            var postsDict = posts.ToDictionary(p => p.Id);
 | 
			
		||||
 | 
			
		||||
            for (var idx = 0; idx < input.Count; idx++)
 | 
			
		||||
            {
 | 
			
		||||
                var resourceIdentifier = input[idx].ResourceIdentifier;
 | 
			
		||||
                if (!resourceIdentifier.StartsWith("posts/")) continue;
 | 
			
		||||
                var postId = long.Parse(resourceIdentifier.Split("/").Last());
 | 
			
		||||
                if (postsDict.TryGetValue(postId, out var post) && input[idx].Data is null)
 | 
			
		||||
                {
 | 
			
		||||
                    input[idx].Data = post;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return input;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<Activity> CreateActivity(
 | 
			
		||||
        Account.Account user,
 | 
			
		||||
        string type,
 | 
			
		||||
        string identifier,
 | 
			
		||||
        ActivityVisibility visibility = ActivityVisibility.Public
 | 
			
		||||
        ActivityVisibility visibility = ActivityVisibility.Public,
 | 
			
		||||
        List<long>? visibleUsers = null
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        var activity = new Activity
 | 
			
		||||
@@ -18,6 +63,7 @@ public class ActivityService(AppDatabase db)
 | 
			
		||||
            ResourceIdentifier = identifier,
 | 
			
		||||
            Visibility = visibility,
 | 
			
		||||
            AccountId = user.Id,
 | 
			
		||||
            UsersVisible = visibleUsers ?? []
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        db.Activities.Add(activity);
 | 
			
		||||
@@ -31,8 +77,26 @@ public class ActivityService(AppDatabase db)
 | 
			
		||||
        if (post.Visibility is PostVisibility.Unlisted or PostVisibility.Private) return;
 | 
			
		||||
 | 
			
		||||
        var identifier = $"posts/{post.Id}";
 | 
			
		||||
        await CreateActivity(user, "posts.new", identifier,
 | 
			
		||||
            post.Visibility == PostVisibility.Friends ? ActivityVisibility.Friends : ActivityVisibility.Public);
 | 
			
		||||
        if (post.RepliedPostId is not null)
 | 
			
		||||
        {
 | 
			
		||||
            var ogPost = await db.Posts.Where(e => e.Id == post.RepliedPostId).Include(e => e.Publisher)
 | 
			
		||||
                .FirstOrDefaultAsync();
 | 
			
		||||
            if (ogPost == null) return;
 | 
			
		||||
            await CreateActivity(
 | 
			
		||||
                user,
 | 
			
		||||
                "posts.new.replies",
 | 
			
		||||
                identifier,
 | 
			
		||||
                ActivityVisibility.Selected,
 | 
			
		||||
                [ogPost.Publisher.AccountId!.Value]
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CreateActivity(
 | 
			
		||||
            user,
 | 
			
		||||
            "posts.new",
 | 
			
		||||
            identifier,
 | 
			
		||||
            post.Visibility == PostVisibility.Friends ? ActivityVisibility.Friends : ActivityVisibility.Public
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -41,14 +105,13 @@ public static class ActivityQueryExtensions
 | 
			
		||||
    public static IQueryable<Activity> FilterWithVisibility(this IQueryable<Activity> source,
 | 
			
		||||
        Account.Account? currentUser, List<long> userFriends)
 | 
			
		||||
    {
 | 
			
		||||
        var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
 | 
			
		||||
 | 
			
		||||
        if (currentUser is null)
 | 
			
		||||
            return source.Where(e => e.Visibility == ActivityVisibility.Public);
 | 
			
		||||
 | 
			
		||||
        return source
 | 
			
		||||
            .Where(e => e.Visibility != ActivityVisibility.Friends ||
 | 
			
		||||
                        userFriends.Contains(e.AccountId) ||
 | 
			
		||||
                        e.AccountId == currentUser.Id);
 | 
			
		||||
                        e.AccountId == currentUser.Id)
 | 
			
		||||
            .Where(e => e.Visibility != ActivityVisibility.Selected || e.UsersVisible.Contains(currentUser.Id));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,11 +20,11 @@ public class AppDatabase(
 | 
			
		||||
    IConfiguration configuration
 | 
			
		||||
) : DbContext(options)
 | 
			
		||||
{
 | 
			
		||||
    public DbSet<PermissionNode> PermissionNodes { get; set; } = null!;
 | 
			
		||||
    public DbSet<PermissionGroup> PermissionGroups { get; set; } = null!;
 | 
			
		||||
    public DbSet<PermissionGroupMember> PermissionGroupMembers { get; set; } = null!;
 | 
			
		||||
    public DbSet<PermissionNode> PermissionNodes { get; set; }
 | 
			
		||||
    public DbSet<PermissionGroup> PermissionGroups { get; set; }
 | 
			
		||||
    public DbSet<PermissionGroupMember> PermissionGroupMembers { get; set; }
 | 
			
		||||
 | 
			
		||||
    public DbSet<Account.MagicSpell> MagicSpells { get; set; } = null!;
 | 
			
		||||
    public DbSet<Account.MagicSpell> MagicSpells { get; set; }
 | 
			
		||||
    public DbSet<Account.Account> Accounts { get; set; }
 | 
			
		||||
    public DbSet<Account.Profile> AccountProfiles { get; set; }
 | 
			
		||||
    public DbSet<Account.AccountContact> AccountContacts { get; set; }
 | 
			
		||||
@@ -48,6 +48,9 @@ public class AppDatabase(
 | 
			
		||||
    public DbSet<Post.PostCategory> PostCategories { get; set; }
 | 
			
		||||
    public DbSet<Post.PostCollection> PostCollections { get; set; }
 | 
			
		||||
 | 
			
		||||
    public DbSet<Realm.Realm> Realms { get; set; }
 | 
			
		||||
    public DbSet<Realm.RealmMember> RealmMembers { get; set; }
 | 
			
		||||
 | 
			
		||||
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
    {
 | 
			
		||||
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
 | 
			
		||||
@@ -155,18 +158,29 @@ public class AppDatabase(
 | 
			
		||||
            .WithMany(c => c.Posts)
 | 
			
		||||
            .UsingEntity(j => j.ToTable("post_collection_links"));
 | 
			
		||||
        
 | 
			
		||||
        modelBuilder.Entity<Realm.RealmMember>()
 | 
			
		||||
            .HasKey(pm => new { pm.RealmId, pm.AccountId });
 | 
			
		||||
        modelBuilder.Entity<Realm.RealmMember>()
 | 
			
		||||
            .HasOne(pm => pm.Realm)
 | 
			
		||||
            .WithMany(p => p.Members)
 | 
			
		||||
            .HasForeignKey(pm => pm.RealmId)
 | 
			
		||||
            .OnDelete(DeleteBehavior.Cascade);
 | 
			
		||||
        modelBuilder.Entity<Post.PublisherMember>()
 | 
			
		||||
            .HasOne(pm => pm.Account)
 | 
			
		||||
            .WithMany()
 | 
			
		||||
            .HasForeignKey(pm => pm.AccountId)
 | 
			
		||||
            .OnDelete(DeleteBehavior.Cascade);
 | 
			
		||||
 | 
			
		||||
        // Automatically apply soft-delete filter to all entities inheriting BaseModel
 | 
			
		||||
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
 | 
			
		||||
        {
 | 
			
		||||
            if (typeof(ModelBase).IsAssignableFrom(entityType.ClrType))
 | 
			
		||||
            {
 | 
			
		||||
                var method = typeof(AppDatabase)
 | 
			
		||||
                    .GetMethod(nameof(SetSoftDeleteFilter),
 | 
			
		||||
                        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
 | 
			
		||||
                    .MakeGenericMethod(entityType.ClrType);
 | 
			
		||||
            if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue;
 | 
			
		||||
            var method = typeof(AppDatabase)
 | 
			
		||||
                .GetMethod(nameof(SetSoftDeleteFilter),
 | 
			
		||||
                    System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
 | 
			
		||||
                .MakeGenericMethod(entityType.ClrType);
 | 
			
		||||
 | 
			
		||||
                method.Invoke(null, [modelBuilder]);
 | 
			
		||||
            }
 | 
			
		||||
            method.Invoke(null, [modelBuilder]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ public class PermissionService(AppDatabase db)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        var permission = await db.PermissionNodes
 | 
			
		||||
            .Where(n => n.GroupId == null || groupsId.Contains(n.GroupId.Value))
 | 
			
		||||
            .Where(n => n.Key == key && (n.GroupId != null || n.Actor == actor) && n.Area == area)
 | 
			
		||||
            .Where(n => (n.Key == key || n.Key == "*") && (n.GroupId != null || n.Actor == actor) && n.Area == area)
 | 
			
		||||
            .Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
 | 
			
		||||
            .Where(n => n.AffectedAt == null || n.AffectedAt >= now)
 | 
			
		||||
            .FirstOrDefaultAsync();
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ public class Post : ModelBase
 | 
			
		||||
    public Post? ForwardedPost { get; set; }
 | 
			
		||||
    public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
 | 
			
		||||
    
 | 
			
		||||
    public NpgsqlTsVector SearchVector { get; set; }
 | 
			
		||||
    [JsonIgnore] public NpgsqlTsVector SearchVector { get; set; }
 | 
			
		||||
 | 
			
		||||
    public Publisher Publisher { get; set; } = null!;
 | 
			
		||||
    public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
 | 
			
		||||
@@ -59,7 +59,8 @@ public class Post : ModelBase
 | 
			
		||||
    public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
 | 
			
		||||
    public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
 | 
			
		||||
 | 
			
		||||
    public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
 | 
			
		||||
    [JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
 | 
			
		||||
    [NotMapped] public bool IsTruncated = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class PostTag : ModelBase
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
 | 
			
		||||
            .Skip(offset)
 | 
			
		||||
            .Take(take)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        posts = PostService.TruncatePostContent(posts);
 | 
			
		||||
 | 
			
		||||
        Response.Headers["X-Total"] = totalCount.ToString();
 | 
			
		||||
 | 
			
		||||
@@ -101,6 +102,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
 | 
			
		||||
            .Skip(offset)
 | 
			
		||||
            .Take(take)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        posts = PostService.TruncatePostContent(posts);
 | 
			
		||||
 | 
			
		||||
        Response.Headers["X-Total"] = totalCount.ToString();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using DysonNetwork.Sphere.Activity;
 | 
			
		||||
using DysonNetwork.Sphere.Storage;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
@@ -7,6 +8,64 @@ namespace DysonNetwork.Sphere.Post;
 | 
			
		||||
 | 
			
		||||
public class PostService(AppDatabase db, FileService fs, ActivityService act)
 | 
			
		||||
{
 | 
			
		||||
    public static List<Post> TruncatePostContent(List<Post> input)
 | 
			
		||||
    {
 | 
			
		||||
        // This truncate post content is designed for quill delta
 | 
			
		||||
        const int maxLength = 256;
 | 
			
		||||
        foreach (var item in input)
 | 
			
		||||
        {
 | 
			
		||||
            if (item.Content is not { RootElement: var rootElement }) continue;
 | 
			
		||||
 | 
			
		||||
            if (rootElement.ValueKind != JsonValueKind.Array) continue;
 | 
			
		||||
            var totalLength = 0;
 | 
			
		||||
            var truncatedArrayElements = new List<JsonElement>();
 | 
			
		||||
 | 
			
		||||
            foreach (var element in rootElement.EnumerateArray())
 | 
			
		||||
            {
 | 
			
		||||
                if (element is { ValueKind: JsonValueKind.Object } &&
 | 
			
		||||
                    element.TryGetProperty("insert", out var insertProperty))
 | 
			
		||||
                {
 | 
			
		||||
                    if (insertProperty is { ValueKind: JsonValueKind.String })
 | 
			
		||||
                    {
 | 
			
		||||
                        var textContent = insertProperty.GetString()!;
 | 
			
		||||
                        if (totalLength + textContent.Length <= maxLength)
 | 
			
		||||
                        {
 | 
			
		||||
                            truncatedArrayElements.Add(element);
 | 
			
		||||
                            totalLength += textContent.Length;
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            var remainingLength = maxLength - totalLength;
 | 
			
		||||
                            if (remainingLength > 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                using var truncatedElementDocument =
 | 
			
		||||
                                    JsonDocument.Parse(
 | 
			
		||||
                                        $@"{{ ""insert"": ""{textContent.Substring(0, remainingLength)}"" }}"
 | 
			
		||||
                                    );
 | 
			
		||||
                                truncatedArrayElements.Add(truncatedElementDocument.RootElement.Clone());
 | 
			
		||||
                                totalLength = maxLength;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        truncatedArrayElements.Add(element);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    truncatedArrayElements.Add(element);
 | 
			
		||||
 | 
			
		||||
                if (totalLength >= maxLength)
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var newDocument = JsonDocument.Parse(JsonSerializer.Serialize(truncatedArrayElements));
 | 
			
		||||
            item.Content = newDocument;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return input;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<Post> PostAsync(
 | 
			
		||||
        Account.Account user,
 | 
			
		||||
        Post post,
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ public enum PublisherMemberRole
 | 
			
		||||
    Viewer = 25
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class PublisherMember : ModelBase
 | 
			
		||||
public class PublisherMember
 | 
			
		||||
{
 | 
			
		||||
    public long PublisherId { get; set; }
 | 
			
		||||
    [JsonIgnore] public Publisher Publisher { get; set; } = null!;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								DysonNetwork.Sphere/Realm/Realm.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								DysonNetwork.Sphere/Realm/Realm.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using DysonNetwork.Sphere.Storage;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
 | 
			
		||||
namespace DysonNetwork.Sphere.Realm;
 | 
			
		||||
 | 
			
		||||
public class Realm : ModelBase
 | 
			
		||||
{
 | 
			
		||||
    public long Id { get; set; }
 | 
			
		||||
    [MaxLength(1024)] public string Name { get; set; } = string.Empty;
 | 
			
		||||
    [MaxLength(4096)] public string Description { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
    public CloudFile? Picture { get; set; }
 | 
			
		||||
    public CloudFile? Background { get; set; }
 | 
			
		||||
    
 | 
			
		||||
    [JsonIgnore] public ICollection<RealmMember> Members { get; set; } = new List<RealmMember>();
 | 
			
		||||
 | 
			
		||||
    public long AccountId { get; set; }
 | 
			
		||||
    [JsonIgnore] public Account.Account Account { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public enum RealmMemberRole
 | 
			
		||||
{
 | 
			
		||||
    Owner = 100,
 | 
			
		||||
    Moderator = 50,
 | 
			
		||||
    Normal = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class RealmMember : ModelBase
 | 
			
		||||
{
 | 
			
		||||
    public long RealmId { get; set; }
 | 
			
		||||
    [JsonIgnore] public Realm  Realm { get; set; } = null!;
 | 
			
		||||
    public long AccountId { get; set; }
 | 
			
		||||
    [JsonIgnore] public Account.Account Account { get; set; } = null!;
 | 
			
		||||
    
 | 
			
		||||
    public RealmMemberRole Role { get; set; }
 | 
			
		||||
    public Instant? JoinedAt { get; set; }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								DysonNetwork.Sphere/Realm/RealmController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								DysonNetwork.Sphere/Realm/RealmController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
 | 
			
		||||
namespace DysonNetwork.Sphere.Realm;
 | 
			
		||||
 | 
			
		||||
[ApiController]
 | 
			
		||||
[Route("/realms")]
 | 
			
		||||
public class RealmController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								DysonNetwork.Sphere/Realm/RealmService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								DysonNetwork.Sphere/Realm/RealmService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
namespace DysonNetwork.Sphere.Realm;
 | 
			
		||||
 | 
			
		||||
public class RealmService(AppDatabase db)
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user