♻️ Move most of models to the Shared package
This commit is contained in:
193
DysonNetwork.Shared/Models/Account.cs
Normal file
193
DysonNetwork.Shared/Models/Account.cs
Normal file
@ -0,0 +1,193 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using OtpNet;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
[Index(nameof(Name), IsUnique = true)]
|
||||
public class Account : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
||||
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
||||
[MaxLength(32)] public string Language { get; set; } = string.Empty;
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public bool IsSuperuser { get; set; } = false;
|
||||
|
||||
public Profile Profile { get; set; } = null!;
|
||||
public ICollection<AccountContact> Contacts { get; set; } = new List<AccountContact>();
|
||||
public ICollection<Badge> Badges { get; set; } = new List<Badge>();
|
||||
|
||||
[JsonIgnore] public ICollection<AccountAuthFactor> AuthFactors { get; set; } = new List<AccountAuthFactor>();
|
||||
[JsonIgnore] public ICollection<AccountConnection> Connections { get; set; } = new List<AccountConnection>();
|
||||
[JsonIgnore] public ICollection<Session> Sessions { get; set; } = new List<Session>();
|
||||
[JsonIgnore] public ICollection<Challenge> Challenges { get; set; } = new List<Challenge>();
|
||||
|
||||
[JsonIgnore] public ICollection<Relationship> OutgoingRelationships { get; set; } = new List<Relationship>();
|
||||
[JsonIgnore] public ICollection<Relationship> IncomingRelationships { get; set; } = new List<Relationship>();
|
||||
|
||||
[JsonIgnore] public ICollection<Subscription> Subscriptions { get; set; } = new List<Subscription>();
|
||||
}
|
||||
|
||||
public abstract class Leveling
|
||||
{
|
||||
public static readonly List<int> ExperiencePerLevel =
|
||||
[
|
||||
0, // Level 0
|
||||
100, // Level 1
|
||||
250, // Level 2
|
||||
500, // Level 3
|
||||
1000, // Level 4
|
||||
2000, // Level 5
|
||||
4000, // Level 6
|
||||
8000, // Level 7
|
||||
16000, // Level 8
|
||||
32000, // Level 9
|
||||
64000, // Level 10
|
||||
128000, // Level 11
|
||||
256000, // Level 12
|
||||
512000, // Level 13
|
||||
1024000 // Level 14
|
||||
];
|
||||
}
|
||||
|
||||
public class Profile : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(256)] public string? FirstName { get; set; }
|
||||
[MaxLength(256)] public string? MiddleName { get; set; }
|
||||
[MaxLength(256)] public string? LastName { get; set; }
|
||||
[MaxLength(4096)] public string? Bio { get; set; }
|
||||
[MaxLength(1024)] public string? Gender { get; set; }
|
||||
[MaxLength(1024)] public string? Pronouns { get; set; }
|
||||
[MaxLength(1024)] public string? TimeZone { get; set; }
|
||||
[MaxLength(1024)] public string? Location { get; set; }
|
||||
public Instant? Birthday { get; set; }
|
||||
public Instant? LastSeenAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
|
||||
[Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SubscriptionReferenceObject? StellarMembership { get; set; }
|
||||
|
||||
public int Experience { get; set; } = 0;
|
||||
[NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1;
|
||||
|
||||
[NotMapped]
|
||||
public double LevelingProgress => Level >= Leveling.ExperiencePerLevel.Count - 1
|
||||
? 100
|
||||
: (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 /
|
||||
(Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]);
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class AccountContact : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public AccountContactType Type { get; set; }
|
||||
public Instant? VerifiedAt { get; set; }
|
||||
public bool IsPrimary { get; set; } = false;
|
||||
[MaxLength(1024)] public string Content { get; set; } = string.Empty;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public enum AccountContactType
|
||||
{
|
||||
Email,
|
||||
PhoneNumber,
|
||||
Address
|
||||
}
|
||||
|
||||
public class AccountAuthFactor : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public AccountAuthFactorType Type { get; set; }
|
||||
[JsonIgnore] [MaxLength(8196)] public string? Secret { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Column(TypeName = "jsonb")]
|
||||
public Dictionary<string, object>? Config { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The trustworthy stands for how safe is this auth factor.
|
||||
/// Basically, it affects how many steps it can complete in authentication.
|
||||
/// Besides, users may need to use some high-trustworthy level auth factors when confirming some dangerous operations.
|
||||
/// </summary>
|
||||
public int Trustworthy { get; set; } = 1;
|
||||
|
||||
public Instant? EnabledAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
|
||||
public AccountAuthFactor HashSecret(int cost = 12)
|
||||
{
|
||||
if (Secret == null) return this;
|
||||
Secret = BCrypt.Net.BCrypt.HashPassword(Secret, workFactor: cost);
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool VerifyPassword(string password)
|
||||
{
|
||||
if (Secret == null)
|
||||
throw new InvalidOperationException("Auth factor with no secret cannot be verified with password.");
|
||||
switch (Type)
|
||||
{
|
||||
case AccountAuthFactorType.Password:
|
||||
case AccountAuthFactorType.PinCode:
|
||||
return BCrypt.Net.BCrypt.Verify(password, Secret);
|
||||
case AccountAuthFactorType.TimedCode:
|
||||
var otp = new Totp(Base32Encoding.ToBytes(Secret));
|
||||
return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5));
|
||||
case AccountAuthFactorType.EmailCode:
|
||||
case AccountAuthFactorType.InAppCode:
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary will be returned to the client and should only be set when it just created.
|
||||
/// Useful for passing the client some data to finishing setup and recovery code.
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public Dictionary<string, object>? CreatedResponse { get; set; }
|
||||
}
|
||||
|
||||
public enum AccountAuthFactorType
|
||||
{
|
||||
Password,
|
||||
EmailCode,
|
||||
InAppCode,
|
||||
TimedCode,
|
||||
PinCode,
|
||||
}
|
||||
|
||||
public class AccountConnection : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(4096)] public string Provider { get; set; } = null!;
|
||||
[MaxLength(8192)] public string ProvidedIdentifier { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; } = new();
|
||||
|
||||
[JsonIgnore] [MaxLength(4096)] public string? AccessToken { get; set; }
|
||||
[JsonIgnore] [MaxLength(4096)] public string? RefreshToken { get; set; }
|
||||
public Instant? LastUsedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Account Account { get; set; } = null!;
|
||||
}
|
65
DysonNetwork.Shared/Models/AccountEvent.cs
Normal file
65
DysonNetwork.Shared/Models/AccountEvent.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum StatusAttitude
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Neutral
|
||||
}
|
||||
|
||||
public class Status : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public StatusAttitude Attitude { get; set; }
|
||||
[NotMapped] public bool IsOnline { get; set; }
|
||||
[NotMapped] public bool IsCustomized { get; set; } = true;
|
||||
public bool IsInvisible { get; set; }
|
||||
public bool IsNotDisturb { get; set; }
|
||||
[MaxLength(1024)] public string? Label { get; set; }
|
||||
public Instant? ClearedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public enum CheckInResultLevel
|
||||
{
|
||||
Worst,
|
||||
Worse,
|
||||
Normal,
|
||||
Better,
|
||||
Best
|
||||
}
|
||||
|
||||
public class CheckInResult : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public CheckInResultLevel Level { get; set; }
|
||||
public decimal? RewardPoints { get; set; }
|
||||
public int? RewardExperience { get; set; }
|
||||
[Column(TypeName = "jsonb")] public ICollection<FortuneTip> Tips { get; set; } = new List<FortuneTip>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class FortuneTip
|
||||
{
|
||||
public bool IsPositive { get; set; }
|
||||
public string Title { get; set; } = null!;
|
||||
public string Content { get; set; } = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method should not be mapped. Used to generate the daily event calendar.
|
||||
/// </summary>
|
||||
public class DailyEventResponse
|
||||
{
|
||||
public Instant Date { get; set; }
|
||||
public CheckInResult? CheckInResult { get; set; }
|
||||
public ICollection<Status> Statuses { get; set; } = new List<Status>();
|
||||
}
|
58
DysonNetwork.Shared/Models/ActionLog.cs
Normal file
58
DysonNetwork.Shared/Models/ActionLog.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Point = NetTopologySuite.Geometries.Point;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public abstract class ActionLogType
|
||||
{
|
||||
public const string NewLogin = "login";
|
||||
public const string ChallengeAttempt = "challenges.attempt";
|
||||
public const string ChallengeSuccess = "challenges.success";
|
||||
public const string ChallengeFailure = "challenges.failure";
|
||||
public const string PostCreate = "posts.create";
|
||||
public const string PostUpdate = "posts.update";
|
||||
public const string PostDelete = "posts.delete";
|
||||
public const string PostReact = "posts.react";
|
||||
public const string MessageCreate = "messages.create";
|
||||
public const string MessageUpdate = "messages.update";
|
||||
public const string MessageDelete = "messages.delete";
|
||||
public const string MessageReact = "messages.react";
|
||||
public const string PublisherCreate = "publishers.create";
|
||||
public const string PublisherUpdate = "publishers.update";
|
||||
public const string PublisherDelete = "publishers.delete";
|
||||
public const string PublisherMemberInvite = "publishers.members.invite";
|
||||
public const string PublisherMemberJoin = "publishers.members.join";
|
||||
public const string PublisherMemberLeave = "publishers.members.leave";
|
||||
public const string PublisherMemberKick = "publishers.members.kick";
|
||||
public const string RealmCreate = "realms.create";
|
||||
public const string RealmUpdate = "realms.update";
|
||||
public const string RealmDelete = "realms.delete";
|
||||
public const string RealmInvite = "realms.invite";
|
||||
public const string RealmJoin = "realms.join";
|
||||
public const string RealmLeave = "realms.leave";
|
||||
public const string RealmKick = "realms.kick";
|
||||
public const string RealmAdjustRole = "realms.role.edit";
|
||||
public const string ChatroomCreate = "chatrooms.create";
|
||||
public const string ChatroomUpdate = "chatrooms.update";
|
||||
public const string ChatroomDelete = "chatrooms.delete";
|
||||
public const string ChatroomInvite = "chatrooms.invite";
|
||||
public const string ChatroomJoin = "chatrooms.join";
|
||||
public const string ChatroomLeave = "chatrooms.leave";
|
||||
public const string ChatroomKick = "chatrooms.kick";
|
||||
public const string ChatroomAdjustRole = "chatrooms.role.edit";
|
||||
}
|
||||
|
||||
public class ActionLog : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(4096)] public string Action { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||
public Point? Location { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
public Guid? SessionId { get; set; }
|
||||
}
|
47
DysonNetwork.Shared/Models/Badge.cs
Normal file
47
DysonNetwork.Shared/Models/Badge.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class Badge : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Type { get; set; } = null!;
|
||||
[MaxLength(1024)] public string? Label { get; set; }
|
||||
[MaxLength(4096)] public string? Caption { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
public BadgeReferenceObject ToReference()
|
||||
{
|
||||
return new BadgeReferenceObject
|
||||
{
|
||||
Id = Id,
|
||||
Type = Type,
|
||||
Label = Label,
|
||||
Caption = Caption,
|
||||
Meta = Meta,
|
||||
ActivatedAt = ActivatedAt,
|
||||
ExpiredAt = ExpiredAt,
|
||||
AccountId = AccountId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class BadgeReferenceObject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Type { get; set; } = null!;
|
||||
public string? Label { get; set; }
|
||||
public string? Caption { get; set; }
|
||||
public Dictionary<string, object>? Meta { get; set; }
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
}
|
143
DysonNetwork.Shared/Models/ChatRoom.cs
Normal file
143
DysonNetwork.Shared/Models/ChatRoom.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum ChatRoomType
|
||||
{
|
||||
Group,
|
||||
DirectMessage
|
||||
}
|
||||
|
||||
public class ChatRoom : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string? Name { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public ChatRoomType Type { get; set; }
|
||||
public bool IsCommunity { get; set; }
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<ChatMember> Members { get; set; } = new List<ChatMember>();
|
||||
|
||||
public Guid? RealmId { get; set; }
|
||||
public Shared.Models.Realm? Realm { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonPropertyName("members")]
|
||||
public ICollection<ChatMemberTransmissionObject> DirectMembers { get; set; } =
|
||||
new List<ChatMemberTransmissionObject>();
|
||||
|
||||
public string ResourceIdentifier => $"chatroom/{Id}";
|
||||
}
|
||||
|
||||
public abstract class ChatMemberRole
|
||||
{
|
||||
public const int Owner = 100;
|
||||
public const int Moderator = 50;
|
||||
public const int Member = 0;
|
||||
}
|
||||
|
||||
public enum ChatMemberNotify
|
||||
{
|
||||
All,
|
||||
Mentions,
|
||||
None
|
||||
}
|
||||
|
||||
public enum ChatTimeoutCauseType
|
||||
{
|
||||
ByModerator = 0,
|
||||
BySlowMode = 1,
|
||||
}
|
||||
|
||||
public class ChatTimeoutCause
|
||||
{
|
||||
public ChatTimeoutCauseType Type { get; set; }
|
||||
public Guid? SenderId { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMember : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ChatRoomId { get; set; }
|
||||
public ChatRoom ChatRoom { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
[MaxLength(1024)] public string? Nick { get; set; }
|
||||
|
||||
public int Role { get; set; } = ChatMemberRole.Member;
|
||||
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
|
||||
public Instant? LastReadAt { get; set; }
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
public bool IsBot { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The break time is the user doesn't receive any message from this member for a while.
|
||||
/// Expect mentioned him or her.
|
||||
/// </summary>
|
||||
public Instant? BreakUntil { get; set; }
|
||||
/// <summary>
|
||||
/// The timeout is the user can't send any message.
|
||||
/// Set by the moderator of the chat room.
|
||||
/// </summary>
|
||||
public Instant? TimeoutUntil { get; set; }
|
||||
/// <summary>
|
||||
/// The timeout cause is the reason why the user is timeout.
|
||||
/// </summary>
|
||||
[Column(TypeName = "jsonb")] public ChatTimeoutCause? TimeoutCause { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMemberTransmissionObject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ChatRoomId { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
[MaxLength(1024)] public string? Nick { get; set; }
|
||||
|
||||
public int Role { get; set; } = ChatMemberRole.Member;
|
||||
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
public bool IsBot { get; set; } = false;
|
||||
|
||||
public Instant? BreakUntil { get; set; }
|
||||
public Instant? TimeoutUntil { get; set; }
|
||||
public ChatTimeoutCause? TimeoutCause { get; set; }
|
||||
|
||||
public static ChatMemberTransmissionObject FromEntity(ChatMember member)
|
||||
{
|
||||
return new ChatMemberTransmissionObject
|
||||
{
|
||||
Id = member.Id,
|
||||
ChatRoomId = member.ChatRoomId,
|
||||
AccountId = member.AccountId,
|
||||
Account = member.Account,
|
||||
Nick = member.Nick,
|
||||
Role = member.Role,
|
||||
Notify = member.Notify,
|
||||
JoinedAt = member.JoinedAt,
|
||||
LeaveAt = member.LeaveAt,
|
||||
IsBot = member.IsBot,
|
||||
BreakUntil = member.BreakUntil,
|
||||
TimeoutUntil = member.TimeoutUntil,
|
||||
TimeoutCause = member.TimeoutCause,
|
||||
CreatedAt = member.CreatedAt,
|
||||
UpdatedAt = member.UpdatedAt,
|
||||
DeletedAt = member.DeletedAt
|
||||
};
|
||||
}
|
||||
}
|
183
DysonNetwork.Shared/Models/CloudFile.cs
Normal file
183
DysonNetwork.Shared/Models/CloudFile.cs
Normal file
@ -0,0 +1,183 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class RemoteStorageConfig
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Label { get; set; } = string.Empty;
|
||||
public string Region { get; set; } = string.Empty;
|
||||
public string Bucket { get; set; } = string.Empty;
|
||||
public string Endpoint { get; set; } = string.Empty;
|
||||
public string SecretId { get; set; } = string.Empty;
|
||||
public string SecretKey { get; set; } = string.Empty;
|
||||
public bool EnableSigned { get; set; }
|
||||
public bool EnableSsl { get; set; }
|
||||
public string? ImageProxy { get; set; }
|
||||
public string? AccessProxy { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common interface for cloud file entities that can be used in file operations.
|
||||
/// This interface exposes the essential properties needed for file operations
|
||||
/// and is implemented by both CloudFile and CloudFileReferenceObject.
|
||||
/// </summary>
|
||||
public interface ICloudFile
|
||||
{
|
||||
public Instant CreatedAt { get; }
|
||||
public Instant UpdatedAt { get; }
|
||||
public Instant? DeletedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the cloud file.
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the cloud file.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file metadata dictionary.
|
||||
/// </summary>
|
||||
Dictionary<string, object>? FileMeta { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user metadata dictionary.
|
||||
/// </summary>
|
||||
Dictionary<string, object>? UserMeta { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MIME type of the file.
|
||||
/// </summary>
|
||||
string? MimeType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash of the file content.
|
||||
/// </summary>
|
||||
string? Hash { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the file in bytes.
|
||||
/// </summary>
|
||||
long Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the file has a compressed version available.
|
||||
/// </summary>
|
||||
bool HasCompression { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The class that used in jsonb columns which referenced the cloud file.
|
||||
/// The aim of this class is to store some properties that won't change to a file to reduce the database load.
|
||||
/// </summary>
|
||||
public class CloudFileReferenceObject : ModelBase, ICloudFile
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Dictionary<string, object>? FileMeta { get; set; } = null!;
|
||||
public Dictionary<string, object>? UserMeta { get; set; } = null!;
|
||||
public string? MimeType { get; set; }
|
||||
public string? Hash { get; set; }
|
||||
public long Size { get; set; }
|
||||
public bool HasCompression { get; set; } = false;
|
||||
}
|
||||
|
||||
public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
||||
{
|
||||
/// The id generated by TuS, basically just UUID remove the dash lines
|
||||
[MaxLength(32)]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? UserMeta { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
[MaxLength(256)] public string? MimeType { get; set; }
|
||||
[MaxLength(256)] public string? Hash { get; set; }
|
||||
public long Size { get; set; }
|
||||
public Instant? UploadedAt { get; set; }
|
||||
[MaxLength(128)] public string? UploadedTo { get; set; }
|
||||
public bool HasCompression { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The field is set to true if the recycling job plans to delete the file.
|
||||
/// Due to the unstable of the recycling job, this doesn't really delete the file until a human verifies it.
|
||||
/// </summary>
|
||||
public bool IsMarkedRecycle { get; set; } = false;
|
||||
|
||||
/// The object name which stored remotely,
|
||||
/// multiple cloud file may have same storage id to indicate they are the same file
|
||||
///
|
||||
/// If the storage id was null and the uploaded at is not null, means it is an embedding file,
|
||||
/// The embedding file means the file is store on another site,
|
||||
/// or it is a webpage (based on mimetype)
|
||||
[MaxLength(32)]
|
||||
public string? StorageId { get; set; }
|
||||
|
||||
/// This field should be null when the storage id is filled
|
||||
/// Indicates the off-site accessible url of the file
|
||||
[MaxLength(4096)]
|
||||
public string? StorageUrl { get; set; }
|
||||
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public CloudFileReferenceObject ToReferenceObject()
|
||||
{
|
||||
return new CloudFileReferenceObject
|
||||
{
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
DeletedAt = DeletedAt,
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
FileMeta = FileMeta,
|
||||
UserMeta = UserMeta,
|
||||
MimeType = MimeType,
|
||||
Hash = Hash,
|
||||
Size = Size,
|
||||
HasCompression = HasCompression
|
||||
};
|
||||
}
|
||||
|
||||
public string ResourceIdentifier => $"file/{Id}";
|
||||
}
|
||||
|
||||
public enum ContentSensitiveMark
|
||||
{
|
||||
Language,
|
||||
SexualContent,
|
||||
Violence,
|
||||
Profanity,
|
||||
HateSpeech,
|
||||
Racism,
|
||||
AdultContent,
|
||||
DrugAbuse,
|
||||
AlcoholAbuse,
|
||||
Gambling,
|
||||
SelfHarm,
|
||||
ChildAbuse,
|
||||
Other
|
||||
}
|
||||
|
||||
public class CloudFileReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(32)] public string FileId { get; set; } = null!;
|
||||
public CloudFile File { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Usage { get; set; } = null!;
|
||||
[MaxLength(1024)] public string ResourceId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Optional expiration date for the file reference
|
||||
/// </summary>
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
}
|
67
DysonNetwork.Shared/Models/CustomApp.cs
Normal file
67
DysonNetwork.Shared/Models/CustomApp.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum CustomAppStatus
|
||||
{
|
||||
Developing,
|
||||
Staging,
|
||||
Production,
|
||||
Suspended
|
||||
}
|
||||
|
||||
public class CustomApp : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Name { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing;
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CustomAppOauthConfig? OauthConfig { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CustomAppLinks? Links { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
public Publisher Developer { get; set; } = null!;
|
||||
|
||||
[NotMapped] public string ResourceIdentifier => "custom-app/" + Id;
|
||||
}
|
||||
|
||||
public class CustomAppLinks : ModelBase
|
||||
{
|
||||
[MaxLength(8192)] public string? HomePage { get; set; }
|
||||
[MaxLength(8192)] public string? PrivacyPolicy { get; set; }
|
||||
[MaxLength(8192)] public string? TermsOfService { get; set; }
|
||||
}
|
||||
|
||||
public class CustomAppOauthConfig : ModelBase
|
||||
{
|
||||
[MaxLength(1024)] public string? ClientUri { get; set; }
|
||||
[MaxLength(4096)] public string[] RedirectUris { get; set; } = [];
|
||||
[MaxLength(4096)] public string[]? PostLogoutRedirectUris { get; set; }
|
||||
[MaxLength(256)] public string[]? AllowedScopes { get; set; } = ["openid", "profile", "email"];
|
||||
[MaxLength(256)] public string[] AllowedGrantTypes { get; set; } = ["authorization_code", "refresh_token"];
|
||||
public bool RequirePkce { get; set; } = true;
|
||||
public bool AllowOfflineAccess { get; set; } = false;
|
||||
}
|
||||
|
||||
public class CustomAppSecret : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Secret { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Description { get; set; } = null!;
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public bool IsOidc { get; set; } = false; // Indicates if this secret is for OIDC/OAuth
|
||||
|
||||
public Guid AppId { get; set; }
|
||||
public CustomApp App { get; set; } = null!;
|
||||
}
|
65
DysonNetwork.Shared/Models/Message.cs
Normal file
65
DysonNetwork.Shared/Models/Message.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class Message : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Type { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Content { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<Guid>? MembersMentioned { get; set; }
|
||||
[MaxLength(36)] public string Nonce { get; set; } = null!;
|
||||
public Instant? EditedAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public List<CloudFileReferenceObject> Attachments { get; set; } = [];
|
||||
|
||||
// Outdated fields, keep for backward compability
|
||||
public ICollection<CloudFile> OutdatedAttachments { get; set; } = new List<CloudFile>();
|
||||
public ICollection<MessageReaction> Reactions { get; set; } = new List<MessageReaction>();
|
||||
|
||||
public Guid? RepliedMessageId { get; set; }
|
||||
public Message? RepliedMessage { get; set; }
|
||||
public Guid? ForwardedMessageId { get; set; }
|
||||
public Message? ForwardedMessage { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
public Guid ChatRoomId { get; set; }
|
||||
[JsonIgnore] public ChatRoom ChatRoom { get; set; } = null!;
|
||||
|
||||
public string ResourceIdentifier => $"message/{Id}";
|
||||
}
|
||||
|
||||
public enum MessageReactionAttitude
|
||||
{
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative,
|
||||
}
|
||||
|
||||
public class MessageReaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid MessageId { get; set; }
|
||||
[JsonIgnore] public Message Message { get; set; } = null!;
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
|
||||
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
||||
public MessageReactionAttitude Attitude { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data model for updating the last read at field for chat member,
|
||||
/// after the refactor of the unread system, this no longer stored in the database.
|
||||
/// Not only used for the data transmission object
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public class MessageReadReceipt
|
||||
{
|
||||
public Guid SenderId { get; set; }
|
||||
}
|
15
DysonNetwork.Shared/Models/ModelBase.cs
Normal file
15
DysonNetwork.Shared/Models/ModelBase.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public interface IIdentifiedResource
|
||||
{
|
||||
public string ResourceIdentifier { get; }
|
||||
}
|
||||
|
||||
public abstract class ModelBase
|
||||
{
|
||||
public Instant CreatedAt { get; set; }
|
||||
public Instant UpdatedAt { get; set; }
|
||||
public Instant? DeletedAt { get; set; }
|
||||
}
|
41
DysonNetwork.Shared/Models/Notification.cs
Normal file
41
DysonNetwork.Shared/Models/Notification.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class Notification : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Topic { get; set; } = null!;
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(2048)] public string? Subtitle { get; set; }
|
||||
[MaxLength(4096)] public string? Content { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
public int Priority { get; set; } = 10;
|
||||
public Instant? ViewedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public enum NotificationPushProvider
|
||||
{
|
||||
Apple,
|
||||
Google
|
||||
}
|
||||
|
||||
[Index(nameof(DeviceToken), nameof(DeviceId), nameof(AccountId), IsUnique = true)]
|
||||
public class NotificationPushSubscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(4096)] public string DeviceId { get; set; } = null!;
|
||||
[MaxLength(4096)] public string DeviceToken { get; set; } = null!;
|
||||
public NotificationPushProvider Provider { get; set; }
|
||||
public Instant? LastUsedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
}
|
62
DysonNetwork.Shared/Models/Payment.cs
Normal file
62
DysonNetwork.Shared/Models/Payment.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class WalletCurrency
|
||||
{
|
||||
public const string SourcePoint = "points";
|
||||
public const string GoldenPoint = "golds";
|
||||
}
|
||||
|
||||
public enum OrderStatus
|
||||
{
|
||||
Unpaid,
|
||||
Paid,
|
||||
Cancelled,
|
||||
Finished,
|
||||
Expired
|
||||
}
|
||||
|
||||
public class Order : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public OrderStatus Status { get; set; } = OrderStatus.Unpaid;
|
||||
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Remarks { get; set; }
|
||||
[MaxLength(4096)] public string? AppIdentifier { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public Instant ExpiredAt { get; set; }
|
||||
|
||||
public Guid? PayeeWalletId { get; set; }
|
||||
public Shared.Models.Wallet? PayeeWallet { get; set; } = null!;
|
||||
public Guid? TransactionId { get; set; }
|
||||
public Transaction? Transaction { get; set; }
|
||||
public Guid? IssuerAppId { get; set; }
|
||||
public CustomApp? IssuerApp { get; set; }
|
||||
}
|
||||
|
||||
public enum TransactionType
|
||||
{
|
||||
System,
|
||||
Transfer,
|
||||
Order
|
||||
}
|
||||
|
||||
public class Transaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||
public decimal Amount { get; set; }
|
||||
[MaxLength(4096)] public string? Remarks { get; set; }
|
||||
public TransactionType Type { get; set; }
|
||||
|
||||
// When the payer is null, it's pay from the system
|
||||
public Guid? PayerWalletId { get; set; }
|
||||
public Shared.Models.Wallet? PayerWallet { get; set; }
|
||||
// When the payee is null, it's pay for the system
|
||||
public Guid? PayeeWalletId { get; set; }
|
||||
public Shared.Models.Wallet? PayeeWallet { get; set; }
|
||||
}
|
59
DysonNetwork.Shared/Models/Permission.cs
Normal file
59
DysonNetwork.Shared/Models/Permission.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
/// The permission node model provides the infrastructure of permission control in Dyson Network.
|
||||
/// It based on the ABAC permission model.
|
||||
///
|
||||
/// The value can be any type, boolean and number for most cases and stored in jsonb.
|
||||
///
|
||||
/// The area represents the region this permission affects. For example, the pub:<publisherId>
|
||||
/// indicates it's a permission node for the publishers managing.
|
||||
///
|
||||
/// And the actor shows who owns the permission, in most cases, the user:<userId>
|
||||
/// and when the permission node has a GroupId, the actor will be set to the group, but it won't work on checking
|
||||
/// expect the member of that permission group inherent the permission from the group.
|
||||
[Index(nameof(Key), nameof(Area), nameof(Actor))]
|
||||
public class PermissionNode : ModelBase, IDisposable
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Actor { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Area { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Key { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public JsonDocument Value { get; set; } = null!;
|
||||
public Instant? ExpiredAt { get; set; } = null;
|
||||
public Instant? AffectedAt { get; set; } = null;
|
||||
|
||||
public Guid? GroupId { get; set; } = null;
|
||||
[JsonIgnore] public PermissionGroup? Group { get; set; } = null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Value.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class PermissionGroup : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Key { get; set; } = null!;
|
||||
|
||||
public ICollection<PermissionNode> Nodes { get; set; } = new List<PermissionNode>();
|
||||
[JsonIgnore] public ICollection<PermissionGroupMember> Members { get; set; } = new List<PermissionGroupMember>();
|
||||
}
|
||||
|
||||
public class PermissionGroupMember : ModelBase
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public PermissionGroup Group { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Actor { get; set; } = null!;
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public Instant? AffectedAt { get; set; }
|
||||
}
|
100
DysonNetwork.Shared/Models/Publisher.cs
Normal file
100
DysonNetwork.Shared/Models/Publisher.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum PublisherType
|
||||
{
|
||||
Individual,
|
||||
Organizational
|
||||
}
|
||||
|
||||
[Index(nameof(Name), IsUnique = true)]
|
||||
public class Publisher : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public PublisherType Type { get; set; }
|
||||
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
||||
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
||||
[MaxLength(4096)] public string? Bio { get; set; }
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<PublisherMember> Members { get; set; } = new List<PublisherMember>();
|
||||
[JsonIgnore] public ICollection<PublisherFeature> Features { get; set; } = new List<PublisherFeature>();
|
||||
|
||||
[JsonIgnore]
|
||||
public ICollection<PublisherSubscription> Subscriptions { get; set; } = new List<PublisherSubscription>();
|
||||
|
||||
public Guid? AccountId { get; set; }
|
||||
public Shared.Models.Account? Account { get; set; }
|
||||
public Guid? RealmId { get; set; }
|
||||
[JsonIgnore] public Realm? Realm { get; set; }
|
||||
|
||||
public string ResourceIdentifier => $"publisher/{Id}";
|
||||
}
|
||||
|
||||
public enum PublisherMemberRole
|
||||
{
|
||||
Owner = 100,
|
||||
Manager = 75,
|
||||
Editor = 50,
|
||||
Viewer = 25
|
||||
}
|
||||
|
||||
public class PublisherMember : ModelBase
|
||||
{
|
||||
public Guid PublisherId { get; set; }
|
||||
[JsonIgnore] public Publisher Publisher { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum PublisherSubscriptionStatus
|
||||
{
|
||||
Active,
|
||||
Expired,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
public class PublisherSubscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
[JsonIgnore] public Publisher Publisher { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
public PublisherSubscriptionStatus Status { get; set; } = PublisherSubscriptionStatus.Active;
|
||||
public int Tier { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class PublisherFeature : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string Flag { get; set; } = null!;
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
public Publisher Publisher { get; set; } = null!;
|
||||
}
|
||||
|
||||
public abstract class PublisherFeatureFlag
|
||||
{
|
||||
public static List<string> AllFlags => [Develop];
|
||||
public static string Develop = "develop";
|
||||
}
|
54
DysonNetwork.Shared/Models/Realm.cs
Normal file
54
DysonNetwork.Shared/Models/Realm.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
[Index(nameof(Slug), IsUnique = true)]
|
||||
public class Realm : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string Slug { get; set; } = string.Empty;
|
||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
||||
public bool IsCommunity { get; set; }
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<RealmMember> Members { get; set; } = new List<RealmMember>();
|
||||
[JsonIgnore] public ICollection<ChatRoom> ChatRooms { get; set; } = new List<ChatRoom>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
public string ResourceIdentifier => $"realm/{Id}";
|
||||
}
|
||||
|
||||
public abstract class RealmMemberRole
|
||||
{
|
||||
public const int Owner = 100;
|
||||
public const int Moderator = 50;
|
||||
public const int Normal = 0;
|
||||
}
|
||||
|
||||
public class RealmMember : ModelBase
|
||||
{
|
||||
public Guid RealmId { get; set; }
|
||||
public Realm Realm { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
public int Role { get; set; } = RealmMemberRole.Normal;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
}
|
46
DysonNetwork.Shared/Models/RealtimeCall.cs
Normal file
46
DysonNetwork.Shared/Models/RealtimeCall.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class RealtimeCall : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant? EndedAt { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
public Guid RoomId { get; set; }
|
||||
public ChatRoom Room { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Provider name (e.g., "cloudflare", "agora", "twilio")
|
||||
/// </summary>
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service provider's session identifier
|
||||
/// </summary>
|
||||
public string? SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// JSONB column containing provider-specific configuration
|
||||
/// </summary>
|
||||
[Column(name: "upstream", TypeName = "jsonb")]
|
||||
public string? UpstreamConfigJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized upstream configuration
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public Dictionary<string, object> UpstreamConfig
|
||||
{
|
||||
get => string.IsNullOrEmpty(UpstreamConfigJson)
|
||||
? new Dictionary<string, object>()
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(UpstreamConfigJson) ?? new Dictionary<string, object>();
|
||||
set => UpstreamConfigJson = value.Count > 0
|
||||
? JsonSerializer.Serialize(value)
|
||||
: null;
|
||||
}
|
||||
}
|
22
DysonNetwork.Shared/Models/Relationship.cs
Normal file
22
DysonNetwork.Shared/Models/Relationship.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum RelationshipStatus : short
|
||||
{
|
||||
Friends = 100,
|
||||
Pending = 0,
|
||||
Blocked = -100
|
||||
}
|
||||
|
||||
public class Relationship : ModelBase
|
||||
{
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
public Guid RelatedId { get; set; }
|
||||
public Shared.Models.Account Related { get; set; } = null!;
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending;
|
||||
}
|
68
DysonNetwork.Shared/Models/Session.cs
Normal file
68
DysonNetwork.Shared/Models/Session.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
using Point = NetTopologySuite.Geometries.Point;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class Session : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string? Label { get; set; }
|
||||
public Instant? LastGrantedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
public Guid ChallengeId { get; set; }
|
||||
public Challenge Challenge { get; set; } = null!;
|
||||
public Guid? AppId { get; set; }
|
||||
public CustomApp? App { get; set; }
|
||||
}
|
||||
|
||||
public enum ChallengeType
|
||||
{
|
||||
Login,
|
||||
OAuth, // Trying to authorize other platforms
|
||||
Oidc // Trying to connect other platforms
|
||||
}
|
||||
|
||||
public enum ChallengePlatform
|
||||
{
|
||||
Unidentified,
|
||||
Web,
|
||||
Ios,
|
||||
Android,
|
||||
MacOs,
|
||||
Windows,
|
||||
Linux
|
||||
}
|
||||
|
||||
public class Challenge : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public int StepRemain { get; set; }
|
||||
public int StepTotal { get; set; }
|
||||
public int FailedAttempts { get; set; }
|
||||
public ChallengePlatform Platform { get; set; } = ChallengePlatform.Unidentified;
|
||||
public ChallengeType Type { get; set; } = ChallengeType.Login;
|
||||
[Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = [];
|
||||
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = [];
|
||||
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
|
||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||
[MaxLength(256)] public string? DeviceId { get; set; }
|
||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||
public Point? Location { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
|
||||
public Challenge Normalize()
|
||||
{
|
||||
if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal;
|
||||
return this;
|
||||
}
|
||||
}
|
256
DysonNetwork.Shared/Models/Subscription.cs
Normal file
256
DysonNetwork.Shared/Models/Subscription.cs
Normal file
@ -0,0 +1,256 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public record class SubscriptionTypeData(
|
||||
string Identifier,
|
||||
string? GroupIdentifier,
|
||||
string Currency,
|
||||
decimal BasePrice,
|
||||
int? RequiredLevel = null
|
||||
)
|
||||
{
|
||||
public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict =
|
||||
new()
|
||||
{
|
||||
[SubscriptionType.Twinkle] = new SubscriptionTypeData(
|
||||
SubscriptionType.Twinkle,
|
||||
SubscriptionType.StellarProgram,
|
||||
WalletCurrency.SourcePoint,
|
||||
0,
|
||||
1
|
||||
),
|
||||
[SubscriptionType.Stellar] = new SubscriptionTypeData(
|
||||
SubscriptionType.Stellar,
|
||||
SubscriptionType.StellarProgram,
|
||||
WalletCurrency.SourcePoint,
|
||||
1200,
|
||||
3
|
||||
),
|
||||
[SubscriptionType.Nova] = new SubscriptionTypeData(
|
||||
SubscriptionType.Nova,
|
||||
SubscriptionType.StellarProgram,
|
||||
WalletCurrency.SourcePoint,
|
||||
2400,
|
||||
6
|
||||
),
|
||||
[SubscriptionType.Supernova] = new SubscriptionTypeData(
|
||||
SubscriptionType.Supernova,
|
||||
SubscriptionType.StellarProgram,
|
||||
WalletCurrency.SourcePoint,
|
||||
3600,
|
||||
9
|
||||
)
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> SubscriptionHumanReadable =
|
||||
new()
|
||||
{
|
||||
[SubscriptionType.Twinkle] = "Stellar Program Twinkle",
|
||||
[SubscriptionType.Stellar] = "Stellar Program",
|
||||
[SubscriptionType.Nova] = "Stellar Program Nova",
|
||||
[SubscriptionType.Supernova] = "Stellar Program Supernova"
|
||||
};
|
||||
}
|
||||
|
||||
public abstract class SubscriptionType
|
||||
{
|
||||
/// <summary>
|
||||
/// DO NOT USE THIS TYPE DIRECTLY,
|
||||
/// this is the prefix of all the stellar program subscriptions.
|
||||
/// </summary>
|
||||
public const string StellarProgram = "solian.stellar";
|
||||
|
||||
/// <summary>
|
||||
/// No actual usage, just tells there is a free level named twinkle.
|
||||
/// Applies to every registered user by default, so there is no need to create a record in db for that.
|
||||
/// </summary>
|
||||
public const string Twinkle = "solian.stellar.twinkle";
|
||||
|
||||
public const string Stellar = "solian.stellar.primary";
|
||||
public const string Nova = "solian.stellar.nova";
|
||||
public const string Supernova = "solian.stellar.supernova";
|
||||
}
|
||||
|
||||
public abstract class SubscriptionPaymentMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// The solar points / solar dollars.
|
||||
/// </summary>
|
||||
public const string InAppWallet = "solian.wallet";
|
||||
|
||||
/// <summary>
|
||||
/// afdian.com
|
||||
/// aka. China patreon
|
||||
/// </summary>
|
||||
public const string Afdian = "afdian";
|
||||
}
|
||||
|
||||
public enum SubscriptionStatus
|
||||
{
|
||||
Unpaid,
|
||||
Active,
|
||||
Expired,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The subscription is for the Stellar Program in most cases.
|
||||
/// The paid subscription in another word.
|
||||
/// </summary>
|
||||
[Index(nameof(Identifier))]
|
||||
public class Subscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant BegunAt { get; set; }
|
||||
public Instant? EndedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the subscriptions
|
||||
/// </summary>
|
||||
[MaxLength(4096)]
|
||||
public string Identifier { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The field is used to override the activation status of the membership.
|
||||
/// Might be used for refund handling and other special cases.
|
||||
///
|
||||
/// Go see the IsAvailable field if you want to get real the status of the membership.
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates is the current user got the membership for free,
|
||||
/// to prevent giving the same discount for the same user again.
|
||||
/// </summary>
|
||||
public bool IsFreeTrial { get; set; }
|
||||
|
||||
public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Unpaid;
|
||||
|
||||
[MaxLength(4096)] public string PaymentMethod { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public PaymentDetails PaymentDetails { get; set; } = null!;
|
||||
public decimal BasePrice { get; set; }
|
||||
public Guid? CouponId { get; set; }
|
||||
public Coupon? Coupon { get; set; }
|
||||
public Instant? RenewalAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActive) return false;
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
if (BegunAt > now) return false;
|
||||
if (EndedAt.HasValue && now > EndedAt.Value) return false;
|
||||
if (RenewalAt.HasValue && now > RenewalAt.Value) return false;
|
||||
if (Status != SubscriptionStatus.Active) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public decimal FinalPrice
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsFreeTrial) return 0;
|
||||
if (Coupon == null) return BasePrice;
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
if (Coupon.AffectedAt.HasValue && now < Coupon.AffectedAt.Value ||
|
||||
Coupon.ExpiredAt.HasValue && now > Coupon.ExpiredAt.Value) return BasePrice;
|
||||
|
||||
if (Coupon.DiscountAmount.HasValue) return BasePrice - Coupon.DiscountAmount.Value;
|
||||
if (Coupon.DiscountRate.HasValue) return BasePrice * (decimal)(1 - Coupon.DiscountRate.Value);
|
||||
return BasePrice;
|
||||
}
|
||||
}
|
||||
|
||||
public SubscriptionReferenceObject ToReference()
|
||||
{
|
||||
return new SubscriptionReferenceObject
|
||||
{
|
||||
Id = Id,
|
||||
Status = Status,
|
||||
Identifier = Identifier,
|
||||
IsActive = IsActive,
|
||||
AccountId = AccountId,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
DeletedAt = DeletedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PaymentDetails
|
||||
{
|
||||
public string Currency { get; set; } = null!;
|
||||
public string? OrderId { get; set; }
|
||||
}
|
||||
|
||||
public class SubscriptionReferenceObject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public SubscriptionStatus Status { get; set; }
|
||||
public string Identifier { get; set; } = null!;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public Guid AccountId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A discount that can applies in purchases among the Solar Network.
|
||||
/// For now, it can be used in the subscription purchase.
|
||||
/// </summary>
|
||||
public class Coupon : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// The items that can apply this coupon.
|
||||
/// Leave it to null to apply to all items.
|
||||
/// </summary>
|
||||
[MaxLength(4096)]
|
||||
public string? Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The code that human-readable and memorizable.
|
||||
/// Leave it blank to use it only with the ID.
|
||||
/// </summary>
|
||||
[MaxLength(1024)]
|
||||
public string? Code { get; set; }
|
||||
|
||||
public Instant? AffectedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the discount.
|
||||
/// If this field and the rate field are both not null,
|
||||
/// the amount discount will be applied and the discount rate will be ignored.
|
||||
/// Formula: <code>final price = base price - discount amount</code>
|
||||
/// </summary>
|
||||
public decimal? DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The percentage of the discount.
|
||||
/// If this field and the amount field are both not null,
|
||||
/// this field will be ignored.
|
||||
/// Formula: <code>final price = base price * (1 - discount rate)</code>
|
||||
/// </summary>
|
||||
public double? DiscountRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The max usage of the current coupon.
|
||||
/// Leave it to null to use it unlimited.
|
||||
/// </summary>
|
||||
public int? MaxUsage { get; set; }
|
||||
}
|
25
DysonNetwork.Shared/Models/VerificationMark.cs
Normal file
25
DysonNetwork.Shared/Models/VerificationMark.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
/// <summary>
|
||||
/// The verification info of a resource
|
||||
/// stands, for it is really an individual or organization or a company in the real world.
|
||||
/// Besides, it can also be use for mark parody or fake.
|
||||
/// </summary>
|
||||
public class VerificationMark
|
||||
{
|
||||
public VerificationMarkType Type { get; set; }
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(8192)] public string? Description { get; set; }
|
||||
[MaxLength(1024)] public string? VerifiedBy { get; set; }
|
||||
}
|
||||
|
||||
public enum VerificationMarkType
|
||||
{
|
||||
Official,
|
||||
Individual,
|
||||
Organization,
|
||||
Government,
|
||||
Creator
|
||||
}
|
24
DysonNetwork.Shared/Models/Wallet.cs
Normal file
24
DysonNetwork.Shared/Models/Wallet.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class Wallet : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public ICollection<WalletPocket> Pockets { get; set; } = new List<WalletPocket>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Shared.Models.Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class WalletPocket : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public Guid WalletId { get; set; }
|
||||
[JsonIgnore] public Wallet Wallet { get; set; } = null!;
|
||||
}
|
Reference in New Issue
Block a user