♻️ Centralized data models (wip)
This commit is contained in:
@@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SystemClock = NodaTime.SystemClock;
|
||||
|
||||
namespace DysonNetwork.Shared.Auth;
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DysonNetwork.Shared.Auth;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DysonNetwork.Shared.Content;
|
||||
|
||||
|
@@ -1,350 +0,0 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
|
||||
public class AccountReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Nick { get; set; } = string.Empty;
|
||||
public string Language { get; set; } = string.Empty;
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public bool IsSuperuser { get; set; }
|
||||
public Guid? AutomatedId { get; set; }
|
||||
public AccountProfileReference Profile { get; set; } = null!;
|
||||
public List<AccountContactReference> Contacts { get; set; } = new();
|
||||
public List<AccountBadgeReference> Badges { get; set; } = new();
|
||||
public SubscriptionReference? PerkSubscription { get; set; }
|
||||
|
||||
public Proto.Account ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.Account
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Name = Name,
|
||||
Nick = Nick,
|
||||
Language = Language,
|
||||
ActivatedAt = ActivatedAt?.ToTimestamp(),
|
||||
IsSuperuser = IsSuperuser,
|
||||
Profile = Profile.ToProtoValue(),
|
||||
PerkSubscription = PerkSubscription?.ToProtoValue(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
foreach (var contact in Contacts)
|
||||
proto.Contacts.Add(contact.ToProtoValue());
|
||||
|
||||
foreach (var badge in Badges)
|
||||
proto.Badges.Add(badge.ToProtoValue());
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountReference FromProtoValue(Proto.Account proto)
|
||||
{
|
||||
var account = new AccountReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Name = proto.Name,
|
||||
Nick = proto.Nick,
|
||||
Language = proto.Language,
|
||||
ActivatedAt = proto.ActivatedAt?.ToInstant(),
|
||||
IsSuperuser = proto.IsSuperuser,
|
||||
AutomatedId = string.IsNullOrEmpty(proto.AutomatedId) ? null : Guid.Parse(proto.AutomatedId),
|
||||
PerkSubscription = proto.PerkSubscription != null
|
||||
? SubscriptionReference.FromProtoValue(proto.PerkSubscription)
|
||||
: null,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
account.Profile = AccountProfileReference.FromProtoValue(proto.Profile);
|
||||
|
||||
foreach (var contactProto in proto.Contacts)
|
||||
account.Contacts.Add(AccountContactReference.FromProtoValue(contactProto));
|
||||
|
||||
foreach (var badgeProto in proto.Badges)
|
||||
account.Badges.Add(AccountBadgeReference.FromProtoValue(badgeProto));
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public class AccountProfileReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? MiddleName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? Bio { get; set; }
|
||||
public string? Gender { get; set; }
|
||||
public string? Pronouns { get; set; }
|
||||
public string? TimeZone { get; set; }
|
||||
public string? Location { get; set; }
|
||||
public List<ProfileLinkReference>? Links { get; set; }
|
||||
public Instant? Birthday { get; set; }
|
||||
public Instant? LastSeenAt { get; set; }
|
||||
public VerificationMark? Verification { get; set; }
|
||||
public int Experience { get; set; }
|
||||
public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1;
|
||||
public double SocialCredits { get; set; } = 100;
|
||||
|
||||
public int SocialCreditsLevel => SocialCredits switch
|
||||
{
|
||||
< 100 => -1,
|
||||
> 100 and < 200 => 0,
|
||||
< 200 => 1,
|
||||
_ => 2
|
||||
};
|
||||
|
||||
public double LevelingProgress => Level >= Leveling.ExperiencePerLevel.Count - 1
|
||||
? 100
|
||||
: (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 /
|
||||
(Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]);
|
||||
|
||||
public CloudFileReferenceObject? Picture { get; set; }
|
||||
public CloudFileReferenceObject? Background { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public Proto.AccountProfile ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.AccountProfile
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
FirstName = FirstName ?? string.Empty,
|
||||
MiddleName = MiddleName ?? string.Empty,
|
||||
LastName = LastName ?? string.Empty,
|
||||
Bio = Bio ?? string.Empty,
|
||||
Gender = Gender ?? string.Empty,
|
||||
Pronouns = Pronouns ?? string.Empty,
|
||||
TimeZone = TimeZone ?? string.Empty,
|
||||
Location = Location ?? string.Empty,
|
||||
Birthday = Birthday?.ToTimestamp(),
|
||||
LastSeenAt = LastSeenAt?.ToTimestamp(),
|
||||
Experience = Experience,
|
||||
Level = Level,
|
||||
LevelingProgress = LevelingProgress,
|
||||
SocialCredits = SocialCredits,
|
||||
SocialCreditsLevel = SocialCreditsLevel,
|
||||
Picture = Picture?.ToProtoValue(),
|
||||
Background = Background?.ToProtoValue(),
|
||||
AccountId = AccountId.ToString(),
|
||||
Verification = Verification?.ToProtoValue(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountProfileReference FromProtoValue(Proto.AccountProfile proto)
|
||||
{
|
||||
return new AccountProfileReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
FirstName = string.IsNullOrEmpty(proto.FirstName) ? null : proto.FirstName,
|
||||
MiddleName = string.IsNullOrEmpty(proto.MiddleName) ? null : proto.MiddleName,
|
||||
LastName = string.IsNullOrEmpty(proto.LastName) ? null : proto.LastName,
|
||||
Bio = string.IsNullOrEmpty(proto.Bio) ? null : proto.Bio,
|
||||
Gender = string.IsNullOrEmpty(proto.Gender) ? null : proto.Gender,
|
||||
Pronouns = string.IsNullOrEmpty(proto.Pronouns) ? null : proto.Pronouns,
|
||||
TimeZone = string.IsNullOrEmpty(proto.TimeZone) ? null : proto.TimeZone,
|
||||
Location = string.IsNullOrEmpty(proto.Location) ? null : proto.Location,
|
||||
Birthday = proto.Birthday?.ToInstant(),
|
||||
LastSeenAt = proto.LastSeenAt?.ToInstant(),
|
||||
Experience = proto.Experience,
|
||||
SocialCredits = proto.SocialCredits,
|
||||
Picture = proto.Picture != null ? CloudFileReferenceObject.FromProtoValue(proto.Picture) : null,
|
||||
Background = proto.Background != null ? CloudFileReferenceObject.FromProtoValue(proto.Background) : null,
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Verification = proto.Verification != null ? VerificationMark.FromProtoValue(proto.Verification) : null,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class AccountContactReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public AccountContactReferenceType Type { get; set; }
|
||||
public Instant? VerifiedAt { get; set; }
|
||||
public bool IsPrimary { get; set; } = false;
|
||||
public bool IsPublic { get; set; } = false;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public Shared.Proto.AccountContact ToProtoValue()
|
||||
{
|
||||
var proto = new Shared.Proto.AccountContact
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type switch
|
||||
{
|
||||
AccountContactReferenceType.Email => Shared.Proto.AccountContactType.Email,
|
||||
AccountContactReferenceType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber,
|
||||
AccountContactReferenceType.Address => Shared.Proto.AccountContactType.Address,
|
||||
_ => Shared.Proto.AccountContactType.Unspecified
|
||||
},
|
||||
Content = Content,
|
||||
IsPrimary = IsPrimary,
|
||||
VerifiedAt = VerifiedAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountContactReference FromProtoValue(Shared.Proto.AccountContact proto)
|
||||
{
|
||||
var contact = new AccountContactReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Type = proto.Type switch
|
||||
{
|
||||
Shared.Proto.AccountContactType.Email => AccountContactReferenceType.Email,
|
||||
Shared.Proto.AccountContactType.PhoneNumber => AccountContactReferenceType.PhoneNumber,
|
||||
Shared.Proto.AccountContactType.Address => AccountContactReferenceType.Address,
|
||||
_ => AccountContactReferenceType.Email
|
||||
},
|
||||
Content = proto.Content,
|
||||
IsPrimary = proto.IsPrimary,
|
||||
VerifiedAt = proto.VerifiedAt?.ToInstant(),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AccountContactReferenceType
|
||||
{
|
||||
Email,
|
||||
PhoneNumber,
|
||||
Address
|
||||
}
|
||||
|
||||
public class AccountBadgeReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Type { get; set; } = null!;
|
||||
public string? Label { get; set; }
|
||||
public string? Caption { get; set; }
|
||||
public Dictionary<string, object?> Meta { get; set; } = new();
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public AccountBadge ToProtoValue()
|
||||
{
|
||||
var proto = new AccountBadge
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type,
|
||||
Label = Label ?? string.Empty,
|
||||
Caption = Caption ?? string.Empty,
|
||||
ActivatedAt = ActivatedAt?.ToTimestamp(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountBadgeReference FromProtoValue(AccountBadge proto)
|
||||
{
|
||||
var badge = new AccountBadgeReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Type = proto.Type,
|
||||
Label = proto.Label,
|
||||
Caption = proto.Caption,
|
||||
ActivatedAt = proto.ActivatedAt?.ToInstant(),
|
||||
ExpiredAt = proto.ExpiredAt?.ToInstant(),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProfileLinkReference
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static 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
|
||||
];
|
||||
}
|
||||
|
||||
public class ApiKeyReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Label { get; set; } = null!;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
public string? Key { get; set; }
|
||||
|
||||
public ApiKey ToProtoValue()
|
||||
{
|
||||
return new ApiKey
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Label = Label,
|
||||
AccountId = AccountId.ToString(),
|
||||
SessionId = SessionId.ToString(),
|
||||
Key = Key,
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
}
|
||||
|
||||
public static ApiKeyReference FromProtoValue(ApiKey proto)
|
||||
{
|
||||
return new ApiKeyReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
SessionId = Guid.Parse(proto.SessionId),
|
||||
Label = proto.Label,
|
||||
Key = proto.Key,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
|
||||
public class AccountStatusReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public StatusAttitude Attitude { get; set; }
|
||||
public bool IsOnline { get; set; }
|
||||
public bool IsCustomized { get; set; } = true;
|
||||
public bool IsInvisible { get; set; }
|
||||
public bool IsNotDisturb { get; set; }
|
||||
public string? Label { get; set; }
|
||||
public Dictionary<string, object>? Meta { get; set; }
|
||||
public Instant? ClearedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public AccountStatus ToProtoValue()
|
||||
{
|
||||
var proto = new AccountStatus
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Attitude = Attitude,
|
||||
IsOnline = IsOnline,
|
||||
IsCustomized = IsCustomized,
|
||||
IsInvisible = IsInvisible,
|
||||
IsNotDisturb = IsNotDisturb,
|
||||
Label = Label ?? string.Empty,
|
||||
Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta),
|
||||
ClearedAt = ClearedAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountStatusReference FromProtoValue(AccountStatus proto)
|
||||
{
|
||||
var status = new AccountStatusReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Attitude = proto.Attitude,
|
||||
IsOnline = proto.IsOnline,
|
||||
IsCustomized = proto.IsCustomized,
|
||||
IsInvisible = proto.IsInvisible,
|
||||
IsNotDisturb = proto.IsNotDisturb,
|
||||
Label = proto.Label,
|
||||
Meta = GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(proto.Meta),
|
||||
ClearedAt = proto.ClearedAt?.ToInstant(),
|
||||
AccountId = Guid.Parse(proto.AccountId)
|
||||
};
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
|
||||
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 PostPin = "posts.pin";
|
||||
public const string PostUnpin = "posts.unpin";
|
||||
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";
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
|
||||
public class SubscriptionReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Identifier { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsAvailable { get; set; }
|
||||
public Instant BegunAt { get; set; }
|
||||
public Instant? EndedAt { get; set; }
|
||||
public Instant? RenewalAt { get; set; }
|
||||
public SubscriptionReferenceStatus Status { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public static SubscriptionReference FromProtoValue(Proto.SubscriptionReferenceObject proto)
|
||||
{
|
||||
return new SubscriptionReference
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Identifier = proto.Identifier,
|
||||
DisplayName = proto.DisplayName,
|
||||
IsActive = proto.IsActive,
|
||||
IsAvailable = proto.IsAvailable,
|
||||
BegunAt = proto.BegunAt.ToInstant(),
|
||||
EndedAt = proto.EndedAt?.ToInstant(),
|
||||
RenewalAt = proto.RenewalAt?.ToInstant(),
|
||||
Status = (SubscriptionReferenceStatus)proto.Status,
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant(),
|
||||
};
|
||||
}
|
||||
|
||||
public Proto.SubscriptionReferenceObject ToProtoValue()
|
||||
{
|
||||
return new Proto.SubscriptionReferenceObject
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Identifier = Identifier,
|
||||
DisplayName = DisplayName,
|
||||
IsActive = IsActive,
|
||||
IsAvailable = IsAvailable,
|
||||
BegunAt = BegunAt.ToTimestamp(),
|
||||
EndedAt = EndedAt?.ToTimestamp(),
|
||||
RenewalAt = RenewalAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp(),
|
||||
Status = Status switch
|
||||
{
|
||||
SubscriptionReferenceStatus.Unpaid => Proto.SubscriptionStatus.Unpaid,
|
||||
SubscriptionReferenceStatus.Active => Proto.SubscriptionStatus.Active,
|
||||
SubscriptionReferenceStatus.Expired => Proto.SubscriptionStatus.Expired,
|
||||
SubscriptionReferenceStatus.Cancelled => Proto.SubscriptionStatus.Cancelled,
|
||||
_ => Proto.SubscriptionStatus.Unpaid
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum SubscriptionReferenceStatus
|
||||
{
|
||||
Unpaid = 0,
|
||||
Active = 1,
|
||||
Expired = 2,
|
||||
Cancelled = 3
|
||||
}
|
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.31.1" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
|
||||
@@ -27,6 +28,7 @@
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||
<PackageReference Include="OpenGraph-Net" Version="4.0.1" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.4" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
|
@@ -23,7 +23,7 @@ public static class KestrelConfiguration
|
||||
if (enableGrpc)
|
||||
{
|
||||
// gRPC
|
||||
var grpcPort = int.Parse(configuration.GetValue<string>("GRPC_PORT", "5001"));
|
||||
var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5001"));
|
||||
options.ListenAnyIP(grpcPort, listenOptions =>
|
||||
{
|
||||
listenOptions.Protocols = HttpProtocols.Http2;
|
||||
@@ -34,7 +34,7 @@ public static class KestrelConfiguration
|
||||
}
|
||||
|
||||
|
||||
var httpPorts = configuration.GetValue<string>("HTTP_PORTS", "6000")
|
||||
var httpPorts = configuration.GetValue("HTTP_PORTS", "6000")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => int.Parse(p.Trim()))
|
||||
.ToArray();
|
||||
|
30
DysonNetwork.Shared/Models/AbuseReport.cs
Normal file
30
DysonNetwork.Shared/Models/AbuseReport.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum AbuseReportType
|
||||
{
|
||||
Copyright,
|
||||
Harassment,
|
||||
Impersonation,
|
||||
OffensiveContent,
|
||||
Spam,
|
||||
PrivacyViolation,
|
||||
IllegalContent,
|
||||
Other
|
||||
}
|
||||
|
||||
public class SnAbuseReport : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
|
||||
public AbuseReportType Type { get; set; }
|
||||
[MaxLength(8192)] public string Reason { get; set; } = null!;
|
||||
|
||||
public Instant? ResolvedAt { get; set; }
|
||||
[MaxLength(8192)] public string? Resolution { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
}
|
402
DysonNetwork.Shared/Models/Account.cs
Normal file
402
DysonNetwork.Shared/Models/Account.cs
Normal file
@@ -0,0 +1,402 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
using OtpNet;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
[Index(nameof(Name), IsUnique = true)]
|
||||
public class SnAccount : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[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;
|
||||
[MaxLength(32)] public string Region { get; set; } = string.Empty;
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public bool IsSuperuser { get; set; } = false;
|
||||
|
||||
// The ID is the BotAccount ID in the DysonNetwork.Develop
|
||||
public Guid? AutomatedId { get; set; }
|
||||
|
||||
public SnAccountProfile Profile { get; set; } = null!;
|
||||
public ICollection<AccountContact> Contacts { get; set; } = [];
|
||||
public ICollection<SnAccountBadge> Badges { get; set; } = [];
|
||||
|
||||
[JsonIgnore] public ICollection<AccountAuthFactor> AuthFactors { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<AccountConnection> Connections { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnAuthSession> Sessions { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnAuthChallenge> Challenges { get; set; } = [];
|
||||
|
||||
[JsonIgnore] public ICollection<SnAccountRelationship> OutgoingRelationships { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnAccountRelationship> IncomingRelationships { get; set; } = [];
|
||||
|
||||
[NotMapped] public SubscriptionReferenceObject? PerkSubscription { get; set; }
|
||||
|
||||
public Proto.Account ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.Account
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Name = Name,
|
||||
Nick = Nick,
|
||||
Language = Language,
|
||||
Region = Region,
|
||||
ActivatedAt = ActivatedAt?.ToTimestamp(),
|
||||
IsSuperuser = IsSuperuser,
|
||||
Profile = Profile.ToProtoValue(),
|
||||
PerkSubscription = PerkSubscription?.ToProtoValue(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp(),
|
||||
AutomatedId = AutomatedId?.ToString()
|
||||
};
|
||||
|
||||
// Add contacts
|
||||
foreach (var contact in Contacts)
|
||||
proto.Contacts.Add(contact.ToProtoValue());
|
||||
|
||||
// Add badges
|
||||
foreach (var badge in Badges)
|
||||
proto.Badges.Add(badge.ToProtoValue());
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
public static SnAccount FromProtoValue(Proto.Account proto)
|
||||
{
|
||||
var account = new SnAccount
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Name = proto.Name,
|
||||
Nick = proto.Nick,
|
||||
Language = proto.Language,
|
||||
Region = proto.Region,
|
||||
ActivatedAt = proto.ActivatedAt?.ToInstant(),
|
||||
IsSuperuser = proto.IsSuperuser,
|
||||
PerkSubscription = proto.PerkSubscription is not null
|
||||
? SubscriptionReferenceObject.FromProtoValue(proto.PerkSubscription)
|
||||
: null,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant(),
|
||||
AutomatedId = proto.AutomatedId is not null ? Guid.Parse(proto.AutomatedId) : null,
|
||||
Profile = SnAccountProfile.FromProtoValue(proto.Profile)
|
||||
};
|
||||
|
||||
foreach (var contactProto in proto.Contacts)
|
||||
account.Contacts.Add(AccountContact.FromProtoValue(contactProto));
|
||||
|
||||
foreach (var badgeProto in proto.Badges)
|
||||
account.Badges.Add(SnAccountBadge.FromProtoValue(badgeProto));
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Leveling
|
||||
{
|
||||
private const int MaxLevel = 120;
|
||||
private const double BaseExp = 100.0;
|
||||
private const double K = 3.52; // tweak this for balance
|
||||
|
||||
// Single level XP requirement (from L → L+1)
|
||||
public static double ExpForLevel(int level)
|
||||
{
|
||||
if (level < 1) return 0;
|
||||
return BaseExp + K * Math.Pow(level - 1, 2);
|
||||
}
|
||||
|
||||
// Total cumulative XP required to reach level L
|
||||
public static double TotalExpForLevel(int level)
|
||||
{
|
||||
if (level < 1) return 0;
|
||||
return BaseExp * level + K * ((level - 1) * level * (2 * level - 1)) / 6.0;
|
||||
}
|
||||
|
||||
// Get level from experience
|
||||
public static int GetLevelFromExp(int xp)
|
||||
{
|
||||
if (xp < 0) return 0;
|
||||
|
||||
int level = 0;
|
||||
while (level < MaxLevel && TotalExpForLevel(level + 1) <= xp)
|
||||
{
|
||||
level++;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
// Progress to next level (0.0 ~ 1.0)
|
||||
public static double GetProgressToNextLevel(int xp)
|
||||
{
|
||||
int currentLevel = GetLevelFromExp(xp);
|
||||
if (currentLevel >= MaxLevel) return 1.0;
|
||||
|
||||
double prevTotal = TotalExpForLevel(currentLevel);
|
||||
double nextTotal = TotalExpForLevel(currentLevel + 1);
|
||||
|
||||
return (xp - prevTotal) / (nextTotal - prevTotal);
|
||||
}
|
||||
}
|
||||
|
||||
public class SnAccountProfile : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[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; }
|
||||
[Column(TypeName = "jsonb")] public List<ProfileLink>? Links { get; set; }
|
||||
public Instant? Birthday { get; set; }
|
||||
public Instant? LastSeenAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnVerificationMark? Verification { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnAccountBadgeRef? ActiveBadge { get; set; }
|
||||
|
||||
public int Experience { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public int Level => Leveling.GetLevelFromExp(Experience);
|
||||
[NotMapped]
|
||||
public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience);
|
||||
|
||||
public double SocialCredits { get; set; } = 100;
|
||||
|
||||
[NotMapped]
|
||||
public int SocialCreditsLevel => SocialCredits switch
|
||||
{
|
||||
< 100 => -1,
|
||||
> 100 and < 200 => 0,
|
||||
< 200 => 1,
|
||||
_ => 2
|
||||
};
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Proto.AccountProfile ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.AccountProfile
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
FirstName = FirstName ?? string.Empty,
|
||||
MiddleName = MiddleName ?? string.Empty,
|
||||
LastName = LastName ?? string.Empty,
|
||||
Bio = Bio ?? string.Empty,
|
||||
Gender = Gender ?? string.Empty,
|
||||
Pronouns = Pronouns ?? string.Empty,
|
||||
TimeZone = TimeZone ?? string.Empty,
|
||||
Location = Location ?? string.Empty,
|
||||
Birthday = Birthday?.ToTimestamp(),
|
||||
LastSeenAt = LastSeenAt?.ToTimestamp(),
|
||||
Experience = Experience,
|
||||
Level = Level,
|
||||
LevelingProgress = LevelingProgress,
|
||||
SocialCredits = SocialCredits,
|
||||
SocialCreditsLevel = SocialCreditsLevel,
|
||||
Picture = Picture?.ToProtoValue(),
|
||||
Background = Background?.ToProtoValue(),
|
||||
AccountId = AccountId.ToString(),
|
||||
Verification = Verification?.ToProtoValue(),
|
||||
ActiveBadge = ActiveBadge?.ToProtoValue(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static SnAccountProfile FromProtoValue(Proto.AccountProfile proto)
|
||||
{
|
||||
var profile = new SnAccountProfile
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
FirstName = proto.FirstName,
|
||||
LastName = proto.LastName,
|
||||
MiddleName = proto.MiddleName,
|
||||
Bio = proto.Bio,
|
||||
Gender = proto.Gender,
|
||||
Pronouns = proto.Pronouns,
|
||||
TimeZone = proto.TimeZone,
|
||||
Location = proto.Location,
|
||||
Birthday = proto.Birthday?.ToInstant(),
|
||||
LastSeenAt = proto.LastSeenAt?.ToInstant(),
|
||||
Verification = proto.Verification is null ? null : SnVerificationMark.FromProtoValue(proto.Verification),
|
||||
ActiveBadge = proto.ActiveBadge is null ? null : SnAccountBadgeRef.FromProtoValue(proto.ActiveBadge),
|
||||
Experience = proto.Experience,
|
||||
SocialCredits = proto.SocialCredits,
|
||||
Picture = proto.Picture is null ? null : SnCloudFileReferenceObject.FromProtoValue(proto.Picture),
|
||||
Background = proto.Background is null ? null : SnCloudFileReferenceObject.FromProtoValue(proto.Background),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public string ResourceIdentifier => $"account:profile:{Id}";
|
||||
}
|
||||
|
||||
public class ProfileLink
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
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;
|
||||
public bool IsPublic { get; set; } = false;
|
||||
[MaxLength(1024)] public string Content { get; set; } = string.Empty;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Proto.AccountContact ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.AccountContact
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type switch
|
||||
{
|
||||
AccountContactType.Email => Shared.Proto.AccountContactType.Email,
|
||||
AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber,
|
||||
AccountContactType.Address => Shared.Proto.AccountContactType.Address,
|
||||
_ => Shared.Proto.AccountContactType.Unspecified
|
||||
},
|
||||
Content = Content,
|
||||
IsPrimary = IsPrimary,
|
||||
VerifiedAt = VerifiedAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static AccountContact FromProtoValue(Proto.AccountContact proto)
|
||||
{
|
||||
var contact = new AccountContact
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Type = proto.Type switch
|
||||
{
|
||||
Shared.Proto.AccountContactType.Email => AccountContactType.Email,
|
||||
Shared.Proto.AccountContactType.PhoneNumber => AccountContactType.PhoneNumber,
|
||||
Shared.Proto.AccountContactType.Address => AccountContactType.Address,
|
||||
_ => AccountContactType.Email
|
||||
},
|
||||
Content = proto.Content,
|
||||
IsPrimary = proto.IsPrimary,
|
||||
VerifiedAt = proto.VerifiedAt?.ToInstant(),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
|
||||
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; } = [];
|
||||
|
||||
/// <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 SnAccount 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; } = [];
|
||||
|
||||
[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 SnAccount Account { get; set; } = null!;
|
||||
}
|
127
DysonNetwork.Shared/Models/AccountEvent.cs
Normal file
127
DysonNetwork.Shared/Models/AccountEvent.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum StatusAttitude
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Neutral
|
||||
}
|
||||
|
||||
public class SnAccountStatus : 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; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
public Instant? ClearedAt { get; set; }
|
||||
[MaxLength(4096)] public string? AppIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates this status is created based on running process or rich presence
|
||||
/// </summary>
|
||||
public bool IsAutomated { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public AccountStatus ToProtoValue()
|
||||
{
|
||||
var proto = new AccountStatus
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Attitude = Attitude switch
|
||||
{
|
||||
StatusAttitude.Positive => Shared.Proto.StatusAttitude.Positive,
|
||||
StatusAttitude.Negative => Shared.Proto.StatusAttitude.Negative,
|
||||
StatusAttitude.Neutral => Shared.Proto.StatusAttitude.Neutral,
|
||||
_ => Shared.Proto.StatusAttitude.Unspecified
|
||||
},
|
||||
IsOnline = IsOnline,
|
||||
IsCustomized = IsCustomized,
|
||||
IsInvisible = IsInvisible,
|
||||
IsNotDisturb = IsNotDisturb,
|
||||
Label = Label ?? string.Empty,
|
||||
Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta),
|
||||
ClearedAt = ClearedAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static SnAccountStatus FromProtoValue(AccountStatus proto)
|
||||
{
|
||||
var status = new SnAccountStatus
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Attitude = proto.Attitude switch
|
||||
{
|
||||
Shared.Proto.StatusAttitude.Positive => StatusAttitude.Positive,
|
||||
Shared.Proto.StatusAttitude.Negative => StatusAttitude.Negative,
|
||||
Shared.Proto.StatusAttitude.Neutral => StatusAttitude.Neutral,
|
||||
_ => StatusAttitude.Neutral
|
||||
},
|
||||
IsOnline = proto.IsOnline,
|
||||
IsCustomized = proto.IsCustomized,
|
||||
IsInvisible = proto.IsInvisible,
|
||||
IsNotDisturb = proto.IsNotDisturb,
|
||||
Label = proto.Label,
|
||||
Meta = GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(proto.Meta),
|
||||
ClearedAt = proto.ClearedAt?.ToInstant(),
|
||||
AccountId = Guid.Parse(proto.AccountId)
|
||||
};
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CheckInResultLevel
|
||||
{
|
||||
Worst,
|
||||
Worse,
|
||||
Normal,
|
||||
Better,
|
||||
Best,
|
||||
Special
|
||||
}
|
||||
|
||||
public class SnCheckInResult : 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<CheckInFortuneTip> Tips { get; set; } = new List<CheckInFortuneTip>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Instant? BackdatedFrom { get; set; }
|
||||
}
|
||||
|
||||
public class CheckInFortuneTip
|
||||
{
|
||||
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 SnCheckInResult? CheckInResult { get; set; }
|
||||
public ICollection<SnAccountStatus> Statuses { get; set; } = new List<SnAccountStatus>();
|
||||
}
|
43
DysonNetwork.Shared/Models/ActionLog.cs
Normal file
43
DysonNetwork.Shared/Models/ActionLog.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnActionLog : 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; }
|
||||
[Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
public Guid? SessionId { get; set; }
|
||||
|
||||
public ActionLog ToProtoValue()
|
||||
{
|
||||
var protoLog = new ActionLog
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Action = Action,
|
||||
UserAgent = UserAgent ?? string.Empty,
|
||||
IpAddress = IpAddress ?? string.Empty,
|
||||
Location = Location?.ToString() ?? string.Empty,
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
// Convert Meta dictionary to Struct
|
||||
protoLog.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
|
||||
|
||||
if (SessionId.HasValue)
|
||||
protoLog.SessionId = SessionId.Value.ToString();
|
||||
|
||||
return protoLog;
|
||||
}
|
||||
}
|
37
DysonNetwork.Shared/Models/Activity.cs
Normal file
37
DysonNetwork.Shared/Models/Activity.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public interface IActivity
|
||||
{
|
||||
public SnActivity ToActivity();
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public class SnActivity : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string Type { get; set; } = null!;
|
||||
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
|
||||
public object? Data { get; set; }
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
public int Visibility => 0;
|
||||
|
||||
public static SnActivity Empty()
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
return new SnActivity
|
||||
{
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
Id = Guid.NewGuid(),
|
||||
Type = "empty",
|
||||
ResourceIdentifier = "none"
|
||||
};
|
||||
}
|
||||
}
|
49
DysonNetwork.Shared/Models/ApiKey.cs
Normal file
49
DysonNetwork.Shared/Models/ApiKey.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnApiKey : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Label { get; set; } = null!;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
public Guid SessionId { get; set; }
|
||||
public SnAuthSession Session { get; set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Key { get; set; }
|
||||
|
||||
public Proto.ApiKey ToProtoValue()
|
||||
{
|
||||
return new Proto.ApiKey
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Label = Label,
|
||||
AccountId = AccountId.ToString(),
|
||||
SessionId = SessionId.ToString(),
|
||||
Key = Key,
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
}
|
||||
|
||||
public static SnApiKey FromProtoValue(Proto.ApiKey proto)
|
||||
{
|
||||
return new SnApiKey
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
SessionId = Guid.Parse(proto.SessionId),
|
||||
Label = proto.Label,
|
||||
Key = proto.Key,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
||||
}
|
131
DysonNetwork.Shared/Models/AuthSession.cs
Normal file
131
DysonNetwork.Shared/Models/AuthSession.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnAuthSession : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant? LastGrantedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||
|
||||
// When the challenge is null, indicates the session is for an API Key
|
||||
public Guid? ChallengeId { get; set; }
|
||||
public SnAuthChallenge? Challenge { get; set; } = null!;
|
||||
|
||||
// Indicates the session is for an OIDC connection
|
||||
public Guid? AppId { get; set; }
|
||||
|
||||
public Proto.AuthSession ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
Account = Account.ToProtoValue(),
|
||||
ChallengeId = ChallengeId.ToString(),
|
||||
Challenge = Challenge?.ToProtoValue(),
|
||||
AppId = AppId?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public enum ChallengeType
|
||||
{
|
||||
Login,
|
||||
OAuth, // Trying to authorize other platforms
|
||||
Oidc // Trying to connect other platforms
|
||||
}
|
||||
|
||||
public enum ClientPlatform
|
||||
{
|
||||
Unidentified,
|
||||
Web,
|
||||
Ios,
|
||||
Android,
|
||||
MacOs,
|
||||
Windows,
|
||||
Linux
|
||||
}
|
||||
|
||||
public class SnAuthChallenge : 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 ChallengeType Type { get; set; } = ChallengeType.Login;
|
||||
[Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = new();
|
||||
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new();
|
||||
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();
|
||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||
[Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||
public Guid? ClientId { get; set; }
|
||||
public SnAuthClient? Client { get; set; } = null!;
|
||||
|
||||
public SnAuthChallenge Normalize()
|
||||
{
|
||||
if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto.AuthChallenge ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
StepRemain = StepRemain,
|
||||
StepTotal = StepTotal,
|
||||
FailedAttempts = FailedAttempts,
|
||||
Type = (Proto.ChallengeType)Type,
|
||||
BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) },
|
||||
Audiences = { Audiences },
|
||||
Scopes = { Scopes },
|
||||
IpAddress = IpAddress,
|
||||
UserAgent = UserAgent,
|
||||
DeviceId = Client!.DeviceId,
|
||||
Nonce = Nonce,
|
||||
AccountId = AccountId.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public class SnAuthClient : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public ClientPlatform Platform { get; set; } = ClientPlatform.Unidentified;
|
||||
[MaxLength(1024)] public string DeviceName { get; set; } = string.Empty;
|
||||
[MaxLength(1024)] public string? DeviceLabel { get; set; }
|
||||
[MaxLength(1024)] public string DeviceId { get; set; } = string.Empty;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class SnAuthClientWithChallenge : SnAuthClient
|
||||
{
|
||||
public List<SnAuthChallenge> Challenges { get; set; } = [];
|
||||
|
||||
public static SnAuthClientWithChallenge FromClient(SnAuthClient client)
|
||||
{
|
||||
return new SnAuthClientWithChallenge
|
||||
{
|
||||
Id = client.Id,
|
||||
Platform = client.Platform,
|
||||
DeviceName = client.DeviceName,
|
||||
DeviceLabel = client.DeviceLabel,
|
||||
DeviceId = client.DeviceId,
|
||||
AccountId = client.AccountId,
|
||||
};
|
||||
}
|
||||
}
|
122
DysonNetwork.Shared/Models/Badge.cs
Normal file
122
DysonNetwork.Shared/Models/Badge.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnAccountBadge : 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 SnAccount Account { get; set; } = null!;
|
||||
|
||||
public SnAccountBadgeRef ToReference()
|
||||
{
|
||||
return new SnAccountBadgeRef
|
||||
{
|
||||
Id = Id,
|
||||
Type = Type,
|
||||
Label = Label,
|
||||
Caption = Caption,
|
||||
Meta = Meta,
|
||||
ActivatedAt = ActivatedAt,
|
||||
ExpiredAt = ExpiredAt,
|
||||
AccountId = AccountId,
|
||||
};
|
||||
}
|
||||
|
||||
public AccountBadge ToProtoValue()
|
||||
{
|
||||
var proto = new AccountBadge
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type,
|
||||
Label = Label ?? string.Empty,
|
||||
Caption = Caption ?? string.Empty,
|
||||
ActivatedAt = ActivatedAt?.ToTimestamp(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static SnAccountBadge FromProtoValue(AccountBadge proto)
|
||||
{
|
||||
var badge = new SnAccountBadge
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Type = proto.Type,
|
||||
Label = proto.Label,
|
||||
Caption = proto.Caption,
|
||||
ActivatedAt = proto.ActivatedAt?.ToInstant(),
|
||||
ExpiredAt = proto.ExpiredAt?.ToInstant(),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
|
||||
public class SnAccountBadgeRef : 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; } = new();
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public BadgeReferenceObject ToProtoValue()
|
||||
{
|
||||
var proto = new BadgeReferenceObject
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type,
|
||||
Label = Label ?? string.Empty,
|
||||
Caption = Caption ?? string.Empty,
|
||||
ActivatedAt = ActivatedAt?.ToTimestamp(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString()
|
||||
};
|
||||
proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta!));
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
public static SnAccountBadgeRef FromProtoValue(BadgeReferenceObject proto)
|
||||
{
|
||||
var badge = new SnAccountBadgeRef
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Type = proto.Type,
|
||||
Label = proto.Label,
|
||||
Caption = proto.Caption,
|
||||
Meta = GrpcTypeHelper.ConvertFromValueMap(proto.Meta).ToDictionary(),
|
||||
ActivatedAt = proto.ActivatedAt?.ToInstant(),
|
||||
ExpiredAt = proto.ExpiredAt?.ToInstant(),
|
||||
AccountId = Guid.Parse(proto.AccountId)
|
||||
};
|
||||
|
||||
return badge;
|
||||
}
|
||||
}
|
53
DysonNetwork.Shared/Models/BotAccount.cs
Normal file
53
DysonNetwork.Shared/Models/BotAccount.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnBotAccount : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Slug { get; set; } = null!;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public Guid ProjectId { get; set; }
|
||||
public SnDevProject Project { get; set; } = null!;
|
||||
|
||||
[NotMapped] public SnAccount? Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This developer field is to serve the transparent info for user to know which developer
|
||||
/// published this robot. Not for relationships usage.
|
||||
/// </summary>
|
||||
[NotMapped] public SnDeveloper? Developer { get; set; }
|
||||
|
||||
public Proto.BotAccount ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.BotAccount
|
||||
{
|
||||
Slug = Slug,
|
||||
IsActive = IsActive,
|
||||
AutomatedId = Id.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static SnBotAccount FromProto(Proto.BotAccount proto)
|
||||
{
|
||||
var botAccount = new SnBotAccount
|
||||
{
|
||||
Id = Guid.Parse(proto.AutomatedId),
|
||||
Slug = proto.Slug,
|
||||
IsActive = proto.IsActive,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
|
||||
return botAccount;
|
||||
}
|
||||
}
|
79
DysonNetwork.Shared/Models/ChatMessage.cs
Normal file
79
DysonNetwork.Shared/Models/ChatMessage.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnChatMessage : 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<SnCloudFileReferenceObject> Attachments { get; set; } = [];
|
||||
|
||||
public ICollection<SnChatMessageReaction> Reactions { get; set; } = new List<SnChatMessageReaction>();
|
||||
|
||||
public Guid? RepliedMessageId { get; set; }
|
||||
public SnChatMessage? RepliedMessage { get; set; }
|
||||
public Guid? ForwardedMessageId { get; set; }
|
||||
public SnChatMessage? ForwardedMessage { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public SnChatMember Sender { get; set; } = null!;
|
||||
public Guid ChatRoomId { get; set; }
|
||||
[JsonIgnore] public SnChatRoom ChatRoom { get; set; } = null!;
|
||||
|
||||
public string ResourceIdentifier => $"message:{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow clone of this message for sync operations
|
||||
/// </summary>
|
||||
/// <returns>A new Message instance with copied properties</returns>
|
||||
public SnChatMessage Clone()
|
||||
{
|
||||
return new SnChatMessage
|
||||
{
|
||||
Id = Id,
|
||||
Type = Type,
|
||||
Content = Content,
|
||||
Meta = Meta,
|
||||
MembersMentioned = MembersMentioned,
|
||||
Nonce = Nonce,
|
||||
EditedAt = EditedAt,
|
||||
Attachments = Attachments,
|
||||
RepliedMessageId = RepliedMessageId,
|
||||
ForwardedMessageId = ForwardedMessageId,
|
||||
SenderId = SenderId,
|
||||
Sender = Sender,
|
||||
ChatRoomId = ChatRoomId,
|
||||
ChatRoom = ChatRoom,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageReactionAttitude
|
||||
{
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative,
|
||||
}
|
||||
|
||||
public class SnChatMessageReaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid MessageId { get; set; }
|
||||
[JsonIgnore] public SnChatMessage Message { get; set; } = null!;
|
||||
public Guid SenderId { get; set; }
|
||||
public SnChatMember Sender { get; set; } = null!;
|
||||
|
||||
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
||||
public MessageReactionAttitude Attitude { get; set; }
|
||||
}
|
144
DysonNetwork.Shared/Models/ChatRoom.cs
Normal file
144
DysonNetwork.Shared/Models/ChatRoom.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
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 SnChatRoom : 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 SnCloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<SnChatMember> Members { get; set; } = new List<SnChatMember>();
|
||||
|
||||
public Guid? RealmId { get; set; }
|
||||
public SnRealm? 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 SnChatMember : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ChatRoomId { get; set; }
|
||||
public SnChatRoom ChatRoom { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public SnAccount? Account { get; set; }
|
||||
[NotMapped] public SnAccountStatus? Status { get; set; }
|
||||
|
||||
[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; }
|
||||
[NotMapped] public SnAccount 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(SnChatMember 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
|
||||
};
|
||||
}
|
||||
}
|
141
DysonNetwork.Shared/Models/CloudFile.cs
Normal file
141
DysonNetwork.Shared/Models/CloudFile.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnCloudFile : 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().Replace("-", string.Empty);
|
||||
|
||||
[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; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? UserMeta { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
[MaxLength(256)] public string? MimeType { get; set; }
|
||||
[MaxLength(256)] public string? Hash { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public long Size { get; set; }
|
||||
public Instant? UploadedAt { get; set; }
|
||||
public bool HasCompression { get; set; } = false;
|
||||
public bool HasThumbnail { get; set; } = false;
|
||||
public bool IsEncrypted { get; set; } = false;
|
||||
|
||||
public FilePool? Pool { get; set; }
|
||||
public Guid? PoolId { get; set; }
|
||||
[JsonIgnore] public SnFileBundle? Bundle { get; set; }
|
||||
public Guid? BundleId { get; set; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FastUploadLink { get; set; }
|
||||
|
||||
public ICollection<CloudFileReference> References { get; set; } = new List<CloudFileReference>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public SnCloudFileReferenceObject ToReferenceObject()
|
||||
{
|
||||
return new SnCloudFileReferenceObject
|
||||
{
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
DeletedAt = DeletedAt,
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
FileMeta = FileMeta ?? [],
|
||||
UserMeta = UserMeta ?? [],
|
||||
SensitiveMarks = SensitiveMarks,
|
||||
MimeType = MimeType,
|
||||
Hash = Hash,
|
||||
Size = Size,
|
||||
HasCompression = HasCompression
|
||||
};
|
||||
}
|
||||
|
||||
public string ResourceIdentifier => $"file:{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// Converts the CloudFile to a protobuf message
|
||||
/// </summary>
|
||||
/// <returns>The protobuf message representation of this object</returns>
|
||||
public Proto.CloudFile ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.CloudFile
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
MimeType = MimeType ?? string.Empty,
|
||||
Hash = Hash ?? string.Empty,
|
||||
Size = Size,
|
||||
HasCompression = HasCompression,
|
||||
Url = StorageUrl ?? string.Empty,
|
||||
ContentType = MimeType ?? string.Empty,
|
||||
UploadedAt = UploadedAt?.ToTimestamp(),
|
||||
// Convert file metadata
|
||||
FileMeta = GrpcTypeHelper.ConvertObjectToByteString(FileMeta),
|
||||
// Convert user metadata
|
||||
UserMeta = GrpcTypeHelper.ConvertObjectToByteString(UserMeta),
|
||||
SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks)
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudFileReference : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(32)] public string FileId { get; set; } = null!;
|
||||
public SnCloudFile 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the CloudFileReference to a protobuf message
|
||||
/// </summary>
|
||||
/// <returns>The protobuf message representation of this object</returns>
|
||||
public Proto.CloudFileReference ToProtoValue()
|
||||
{
|
||||
return new Proto.CloudFileReference
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
FileId = FileId,
|
||||
File = File?.ToProtoValue(),
|
||||
Usage = Usage,
|
||||
ResourceId = ResourceId,
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp()
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum ContentSensitiveMark
|
||||
{
|
||||
@@ -23,7 +23,7 @@ public enum ContentSensitiveMark
|
||||
/// 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 class SnCloudFileReferenceObject : ModelBase, ICloudFile
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
@@ -35,9 +35,9 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile
|
||||
public long Size { get; set; }
|
||||
public bool HasCompression { get; set; } = false;
|
||||
|
||||
public static CloudFileReferenceObject FromProtoValue(Proto.CloudFile proto)
|
||||
public static SnCloudFileReferenceObject FromProtoValue(Proto.CloudFile proto)
|
||||
{
|
||||
return new CloudFileReferenceObject
|
||||
return new SnCloudFileReferenceObject
|
||||
{
|
||||
Id = proto.Id,
|
||||
Name = proto.Name,
|
||||
@@ -67,17 +67,14 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile
|
||||
Size = Size,
|
||||
HasCompression = HasCompression,
|
||||
ContentType = MimeType ?? string.Empty,
|
||||
Url = string.Empty // This should be set by the caller if needed
|
||||
Url = string.Empty,
|
||||
// Convert file metadata
|
||||
FileMeta = GrpcTypeHelper.ConvertObjectToByteString(FileMeta),
|
||||
// Convert user metadata
|
||||
UserMeta = GrpcTypeHelper.ConvertObjectToByteString(UserMeta),
|
||||
SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks)
|
||||
};
|
||||
|
||||
// Convert file metadata
|
||||
proto.FileMeta = GrpcTypeHelper.ConvertObjectToByteString(FileMeta);
|
||||
|
||||
// Convert user metadata
|
||||
proto.UserMeta = GrpcTypeHelper.ConvertObjectToByteString(UserMeta);
|
||||
|
||||
proto.SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks);
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
174
DysonNetwork.Shared/Models/CustomApp.cs
Normal file
174
DysonNetwork.Shared/Models/CustomApp.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
using NodaTime;
|
||||
|
||||
using SnVerificationMark = DysonNetwork.Shared.Models.SnVerificationMark;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum CustomAppStatus
|
||||
{
|
||||
Developing,
|
||||
Staging,
|
||||
Production,
|
||||
Suspended
|
||||
}
|
||||
|
||||
public class SnCustomApp : 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 SnCloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnVerificationMark? Verification { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CustomAppOauthConfig? OauthConfig { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CustomAppLinks? Links { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<SnCustomAppSecret> Secrets { get; set; } = new List<SnCustomAppSecret>();
|
||||
|
||||
public Guid ProjectId { get; set; }
|
||||
public SnDevProject Project { get; set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
public SnDeveloper Developer => Project.Developer;
|
||||
|
||||
[NotMapped] public string ResourceIdentifier => "custom-app:" + Id;
|
||||
|
||||
public Proto.CustomApp ToProto()
|
||||
{
|
||||
return new Proto.CustomApp
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Slug = Slug,
|
||||
Name = Name,
|
||||
Description = Description ?? string.Empty,
|
||||
Status = Status switch
|
||||
{
|
||||
CustomAppStatus.Developing => Shared.Proto.CustomAppStatus.Developing,
|
||||
CustomAppStatus.Staging => Shared.Proto.CustomAppStatus.Staging,
|
||||
CustomAppStatus.Production => Shared.Proto.CustomAppStatus.Production,
|
||||
CustomAppStatus.Suspended => Shared.Proto.CustomAppStatus.Suspended,
|
||||
_ => Shared.Proto.CustomAppStatus.Unspecified
|
||||
},
|
||||
Picture = Picture?.ToProtoValue(),
|
||||
Background = Background?.ToProtoValue(),
|
||||
Verification = Verification?.ToProtoValue(),
|
||||
Links = Links is null ? null : new Proto.CustomAppLinks
|
||||
{
|
||||
HomePage = Links.HomePage ?? string.Empty,
|
||||
PrivacyPolicy = Links.PrivacyPolicy ?? string.Empty,
|
||||
TermsOfService = Links.TermsOfService ?? string.Empty
|
||||
},
|
||||
OauthConfig = OauthConfig is null ? null : new Proto.CustomAppOauthConfig
|
||||
{
|
||||
ClientUri = OauthConfig.ClientUri ?? string.Empty,
|
||||
RedirectUris = { OauthConfig.RedirectUris ?? [] },
|
||||
PostLogoutRedirectUris = { OauthConfig.PostLogoutRedirectUris ?? [] },
|
||||
AllowedScopes = { OauthConfig.AllowedScopes ?? [] },
|
||||
AllowedGrantTypes = { OauthConfig.AllowedGrantTypes ?? [] },
|
||||
RequirePkce = OauthConfig.RequirePkce,
|
||||
AllowOfflineAccess = OauthConfig.AllowOfflineAccess
|
||||
},
|
||||
ProjectId = ProjectId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
}
|
||||
|
||||
public SnCustomApp FromProtoValue(Proto.CustomApp p)
|
||||
{
|
||||
Id = Guid.Parse(p.Id);
|
||||
Slug = p.Slug;
|
||||
Name = p.Name;
|
||||
Description = string.IsNullOrEmpty(p.Description) ? null : p.Description;
|
||||
Status = p.Status switch
|
||||
{
|
||||
Shared.Proto.CustomAppStatus.Developing => CustomAppStatus.Developing,
|
||||
Shared.Proto.CustomAppStatus.Staging => CustomAppStatus.Staging,
|
||||
Shared.Proto.CustomAppStatus.Production => CustomAppStatus.Production,
|
||||
Shared.Proto.CustomAppStatus.Suspended => CustomAppStatus.Suspended,
|
||||
_ => CustomAppStatus.Developing
|
||||
};
|
||||
ProjectId = string.IsNullOrEmpty(p.ProjectId) ? Guid.Empty : Guid.Parse(p.ProjectId);
|
||||
CreatedAt = p.CreatedAt.ToInstant();
|
||||
UpdatedAt = p.UpdatedAt.ToInstant();
|
||||
if (p.Picture is not null) Picture = SnCloudFileReferenceObject.FromProtoValue(p.Picture);
|
||||
if (p.Background is not null) Background = SnCloudFileReferenceObject.FromProtoValue(p.Background);
|
||||
if (p.Verification is not null) Verification = SnVerificationMark.FromProtoValue(p.Verification);
|
||||
if (p.Links is not null)
|
||||
{
|
||||
Links = new CustomAppLinks
|
||||
{
|
||||
HomePage = string.IsNullOrEmpty(p.Links.HomePage) ? null : p.Links.HomePage,
|
||||
PrivacyPolicy = string.IsNullOrEmpty(p.Links.PrivacyPolicy) ? null : p.Links.PrivacyPolicy,
|
||||
TermsOfService = string.IsNullOrEmpty(p.Links.TermsOfService) ? null : p.Links.TermsOfService
|
||||
};
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomAppLinks
|
||||
{
|
||||
[MaxLength(8192)] public string? HomePage { get; set; }
|
||||
[MaxLength(8192)] public string? PrivacyPolicy { get; set; }
|
||||
[MaxLength(8192)] public string? TermsOfService { get; set; }
|
||||
}
|
||||
|
||||
public class CustomAppOauthConfig
|
||||
{
|
||||
[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 SnCustomAppSecret : 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 SnCustomApp App { get; set; } = null!;
|
||||
|
||||
|
||||
public static SnCustomAppSecret FromProtoValue(Proto.CustomAppSecret p)
|
||||
{
|
||||
return new SnCustomAppSecret
|
||||
{
|
||||
Id = Guid.Parse(p.Id),
|
||||
Secret = p.Secret,
|
||||
Description = p.Description,
|
||||
ExpiredAt = p.ExpiredAt?.ToInstant(),
|
||||
IsOidc = p.IsOidc,
|
||||
AppId = Guid.Parse(p.AppId),
|
||||
};
|
||||
}
|
||||
|
||||
public Proto.CustomAppSecret ToProto()
|
||||
{
|
||||
return new Proto.CustomAppSecret
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Secret = Secret,
|
||||
Description = Description,
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
IsOidc = IsOidc,
|
||||
AppId = Id.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
14
DysonNetwork.Shared/Models/DevProject.cs
Normal file
14
DysonNetwork.Shared/Models/DevProject.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnDevProject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[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 SnDeveloper Developer { get; set; } = null!;
|
||||
public Guid DeveloperId { get; set; }
|
||||
}
|
14
DysonNetwork.Shared/Models/Developer.cs
Normal file
14
DysonNetwork.Shared/Models/Developer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnDeveloper
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid PublisherId { get; set; }
|
||||
|
||||
[JsonIgnore] public List<SnDevProject> Projects { get; set; } = [];
|
||||
|
||||
[NotMapped] public SnPublisher? Publisher { get; set; }
|
||||
}
|
33
DysonNetwork.Shared/Models/ExperienceRecord.cs
Normal file
33
DysonNetwork.Shared/Models/ExperienceRecord.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class ExperienceRecord : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
||||
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
||||
public long Delta { get; set; }
|
||||
public double BonusMultiplier { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Proto.ExperienceRecord ToProto()
|
||||
{
|
||||
var proto = new Proto.ExperienceRecord
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
ReasonType = ReasonType,
|
||||
Reason = Reason,
|
||||
Delta = Delta,
|
||||
BonusMultiplier = BonusMultiplier,
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
35
DysonNetwork.Shared/Models/FileBundle.cs
Normal file
35
DysonNetwork.Shared/Models/FileBundle.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
[Index(nameof(Slug), IsUnique = true)]
|
||||
public class SnFileBundle : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Name { get; set; } = null!;
|
||||
[MaxLength(8192)] public string? Description { get; set; }
|
||||
[MaxLength(256)] public string? Passcode { get; set; }
|
||||
|
||||
public List<SnCloudFile> Files { get; set; } = new();
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public SnFileBundle HashPasscode()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Passcode)) return this;
|
||||
Passcode = BCrypt.Net.BCrypt.HashPassword(Passcode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool VerifyPasscode(string? passcode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Passcode)) return true;
|
||||
if (string.IsNullOrEmpty(passcode)) return false;
|
||||
return BCrypt.Net.BCrypt.Verify(passcode, Passcode);
|
||||
}
|
||||
}
|
56
DysonNetwork.Shared/Models/FilePool.cs
Normal file
56
DysonNetwork.Shared/Models/FilePool.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class RemoteStorageConfig
|
||||
{
|
||||
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; }
|
||||
public Duration? Expiration { get; set; }
|
||||
}
|
||||
|
||||
public class BillingConfig
|
||||
{
|
||||
public double? CostMultiplier { get; set; } = 1.0;
|
||||
}
|
||||
|
||||
public class PolicyConfig
|
||||
{
|
||||
public bool EnableFastUpload { get; set; } = false;
|
||||
public bool EnableRecycle { get; set; } = false;
|
||||
public bool PublicIndexable { get; set; } = false;
|
||||
public bool PublicUsable { get; set; } = false;
|
||||
public bool NoOptimization { get; set; } = false;
|
||||
public bool NoMetadata { get; set; } = false;
|
||||
public bool AllowEncryption { get; set; } = true;
|
||||
public bool AllowAnonymous { get; set; } = true;
|
||||
public List<string>? AcceptTypes { get; set; }
|
||||
public long? MaxFileSize { get; set; }
|
||||
public int RequirePrivilege { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class FilePool : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||
[MaxLength(8192)] public string Description { get; set; } = string.Empty;
|
||||
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
|
||||
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
|
||||
[Column(TypeName = "jsonb")] public PolicyConfig PolicyConfig { get; set; } = new();
|
||||
public bool IsHidden { get; set; } = false;
|
||||
|
||||
public Guid? AccountId { get; set; }
|
||||
|
||||
public string ResourceIdentifier => $"file-pool/{Id}";
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Common interface for cloud file entities that can be used in file operations.
|
30
DysonNetwork.Shared/Models/MagicSpell.cs
Normal file
30
DysonNetwork.Shared/Models/MagicSpell.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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 MagicSpellType
|
||||
{
|
||||
AccountActivation,
|
||||
AccountDeactivation,
|
||||
AccountRemoval,
|
||||
AuthPasswordReset,
|
||||
ContactVerification,
|
||||
}
|
||||
|
||||
[Index(nameof(Spell), IsUnique = true)]
|
||||
public class SnMagicSpell : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!;
|
||||
public MagicSpellType Type { get; set; }
|
||||
public Instant? ExpiresAt { get; set; }
|
||||
public Instant? AffectedAt { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
|
||||
public Guid? AccountId { get; set; }
|
||||
public SnAccount? Account { get; set; }
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public interface IIdentifiedResource
|
||||
{
|
39
DysonNetwork.Shared/Models/Notification.cs
Normal file
39
DysonNetwork.Shared/Models/Notification.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnNotification : 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; } = new();
|
||||
public int Priority { get; set; } = 10;
|
||||
public Instant? ViewedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
}
|
||||
|
||||
public enum PushProvider
|
||||
{
|
||||
Apple,
|
||||
Google
|
||||
}
|
||||
|
||||
[Index(nameof(AccountId), nameof(DeviceId), nameof(DeletedAt), IsUnique = true)]
|
||||
public class SnNotificationPushSubscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid AccountId { get; set; }
|
||||
[MaxLength(8192)] public string DeviceId { get; set; } = null!;
|
||||
[MaxLength(8192)] public string DeviceToken { get; set; } = null!;
|
||||
public PushProvider Provider { get; set; }
|
||||
|
||||
public int CountDelivered { get; set; }
|
||||
public Instant? LastUsedAt { get; set; }
|
||||
}
|
125
DysonNetwork.Shared/Models/Payment.cs
Normal file
125
DysonNetwork.Shared/Models/Payment.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Globalization;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
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 SnWalletOrder : ModelBase
|
||||
{
|
||||
public const string InternalAppIdentifier = "internal";
|
||||
|
||||
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; }
|
||||
[MaxLength(4096)] public string? ProductIdentifier { 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 SnWallet? PayeeWallet { get; set; } = null!;
|
||||
public Guid? TransactionId { get; set; }
|
||||
public SnWalletTransaction? Transaction { get; set; }
|
||||
|
||||
public Proto.Order ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Status = (Proto.OrderStatus)Status,
|
||||
Currency = Currency,
|
||||
Remarks = Remarks,
|
||||
AppIdentifier = AppIdentifier,
|
||||
ProductIdentifier = ProductIdentifier,
|
||||
Meta = Meta == null
|
||||
? null
|
||||
: Google.Protobuf.ByteString.CopyFrom(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(Meta)),
|
||||
Amount = Amount.ToString(CultureInfo.InvariantCulture),
|
||||
ExpiredAt = ExpiredAt.ToTimestamp(),
|
||||
PayeeWalletId = PayeeWalletId?.ToString(),
|
||||
TransactionId = TransactionId?.ToString(),
|
||||
Transaction = Transaction?.ToProtoValue(),
|
||||
};
|
||||
|
||||
public static SnWalletOrder FromProtoValue(Proto.Order proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Status = (OrderStatus)proto.Status,
|
||||
Currency = proto.Currency,
|
||||
Remarks = proto.Remarks,
|
||||
AppIdentifier = proto.AppIdentifier,
|
||||
ProductIdentifier = proto.ProductIdentifier,
|
||||
Meta = proto.HasMeta
|
||||
? System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(proto.Meta.ToByteArray())
|
||||
: null,
|
||||
Amount = decimal.Parse(proto.Amount),
|
||||
ExpiredAt = proto.ExpiredAt.ToInstant(),
|
||||
PayeeWalletId = proto.PayeeWalletId is not null ? Guid.Parse(proto.PayeeWalletId) : null,
|
||||
TransactionId = proto.TransactionId is not null ? Guid.Parse(proto.TransactionId) : null,
|
||||
Transaction = proto.Transaction is not null ? SnWalletTransaction.FromProtoValue(proto.Transaction) : null,
|
||||
};
|
||||
}
|
||||
|
||||
public enum TransactionType
|
||||
{
|
||||
System,
|
||||
Transfer,
|
||||
Order
|
||||
}
|
||||
|
||||
public class SnWalletTransaction : 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 SnWallet? PayerWallet { get; set; }
|
||||
|
||||
// When the payee is null, it's pay for the system
|
||||
public Guid? PayeeWalletId { get; set; }
|
||||
public SnWallet? PayeeWallet { get; set; }
|
||||
|
||||
public Proto.Transaction ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Currency = Currency,
|
||||
Amount = Amount.ToString(CultureInfo.InvariantCulture),
|
||||
Remarks = Remarks,
|
||||
Type = (Proto.TransactionType)Type,
|
||||
PayerWalletId = PayerWalletId?.ToString(),
|
||||
PayeeWalletId = PayeeWalletId?.ToString(),
|
||||
};
|
||||
|
||||
public static SnWalletTransaction FromProtoValue(Proto.Transaction proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Currency = proto.Currency,
|
||||
Amount = decimal.Parse(proto.Amount),
|
||||
Remarks = proto.Remarks,
|
||||
Type = (TransactionType)proto.Type,
|
||||
PayerWalletId = proto.PayerWalletId is not null ? Guid.Parse(proto.PayerWalletId) : null,
|
||||
PayeeWalletId = proto.PayeeWalletId is not null ? Guid.Parse(proto.PayeeWalletId) : null,
|
||||
};
|
||||
}
|
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 SnPermissionNode : 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 SnPermissionGroup? Group { get; set; } = null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Value.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class SnPermissionGroup : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Key { get; set; } = null!;
|
||||
|
||||
public ICollection<SnPermissionNode> Nodes { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnPermissionGroupMember> Members { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SnPermissionGroupMember : ModelBase
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public SnPermissionGroup Group { get; set; } = null!;
|
||||
[MaxLength(1024)] public string Actor { get; set; } = null!;
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
public Instant? AffectedAt { get; set; }
|
||||
}
|
67
DysonNetwork.Shared/Models/Poll.cs
Normal file
67
DysonNetwork.Shared/Models/Poll.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnPoll : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public List<SnPollQuestion> Questions { get; set; } = new();
|
||||
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
|
||||
public Instant? EndedAt { get; set; }
|
||||
public bool IsAnonymous { get; set; }
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
[JsonIgnore] public SnPublisher? Publisher { get; set; }
|
||||
}
|
||||
|
||||
public enum PollQuestionType
|
||||
{
|
||||
SingleChoice,
|
||||
MultipleChoice,
|
||||
YesNo,
|
||||
Rating,
|
||||
FreeText
|
||||
}
|
||||
|
||||
public class SnPollQuestion : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public PollQuestionType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<SnPollOption>? Options { get; set; }
|
||||
|
||||
[MaxLength(1024)] public string Title { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public int Order { get; set; } = 0;
|
||||
public bool IsRequired { get; set; }
|
||||
|
||||
public Guid PollId { get; set; }
|
||||
[JsonIgnore] public SnPoll Poll { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class SnPollOption
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[Required][MaxLength(1024)] public string Label { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public int Order { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class SnPollAnswer : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, JsonElement> Answer { get; set; } = null!;
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Guid PollId { get; set; }
|
||||
[JsonIgnore] public SnPoll? Poll { get; set; }
|
||||
[NotMapped] public SnAccount? Account { get; set; }
|
||||
}
|
200
DysonNetwork.Shared/Models/Post.cs
Normal file
200
DysonNetwork.Shared/Models/Post.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using NodaTime;
|
||||
using NpgsqlTypes;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum PostType
|
||||
{
|
||||
Moment,
|
||||
Article
|
||||
}
|
||||
|
||||
public enum PostVisibility
|
||||
{
|
||||
Public,
|
||||
Friends,
|
||||
Unlisted,
|
||||
Private
|
||||
}
|
||||
|
||||
public enum PostPinMode
|
||||
{
|
||||
PublisherPage,
|
||||
RealmPage,
|
||||
ReplyPage,
|
||||
}
|
||||
|
||||
public class SnPost : ModelBase, IIdentifiedResource, IActivity
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
[MaxLength(1024)] public string? Slug { get; set; }
|
||||
public Instant? EditedAt { get; set; }
|
||||
public Instant? PublishedAt { get; set; }
|
||||
public PostVisibility Visibility { get; set; } = PostVisibility.Public;
|
||||
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string? Content { get; set; }
|
||||
|
||||
public PostType Type { get; set; }
|
||||
public PostPinMode? PinMode { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
[Column(TypeName = "jsonb")] public PostEmbedView? EmbedView { get; set; }
|
||||
|
||||
public int ViewsUnique { get; set; }
|
||||
public int ViewsTotal { get; set; }
|
||||
public int Upvotes { get; set; }
|
||||
public int Downvotes { get; set; }
|
||||
public decimal AwardedScore { get; set; }
|
||||
[NotMapped] public Dictionary<string, int> ReactionsCount { get; set; } = new();
|
||||
[NotMapped] public int RepliesCount { get; set; }
|
||||
[NotMapped] public Dictionary<string, bool>? ReactionsMade { get; set; }
|
||||
|
||||
public bool RepliedGone { get; set; }
|
||||
public bool ForwardedGone { get; set; }
|
||||
|
||||
public Guid? RepliedPostId { get; set; }
|
||||
public SnPost? RepliedPost { get; set; }
|
||||
public Guid? ForwardedPostId { get; set; }
|
||||
public SnPost? ForwardedPost { get; set; }
|
||||
|
||||
public Guid? RealmId { get; set; }
|
||||
public SnRealm? Realm { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public List<SnCloudFileReferenceObject> Attachments { get; set; } = [];
|
||||
|
||||
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
public SnPublisher Publisher { get; set; } = null!;
|
||||
|
||||
public ICollection<SnPostAward> Awards { get; set; } = null!;
|
||||
[JsonIgnore] public ICollection<SnPostReaction> Reactions { get; set; } = new List<SnPostReaction>();
|
||||
public ICollection<SnPostTag> Tags { get; set; } = new List<SnPostTag>();
|
||||
public ICollection<SnPostCategory> Categories { get; set; } = new List<SnPostCategory>();
|
||||
[JsonIgnore] public ICollection<SnPostCollection> Collections { get; set; } = new List<SnPostCollection>();
|
||||
|
||||
[JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
|
||||
[NotMapped] public bool IsTruncated { get; set; } = false;
|
||||
|
||||
public string ResourceIdentifier => $"post:{Id}";
|
||||
|
||||
public SnActivity ToActivity()
|
||||
{
|
||||
return new SnActivity()
|
||||
{
|
||||
CreatedAt = PublishedAt ?? CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
DeletedAt = DeletedAt,
|
||||
Id = Id,
|
||||
Type = RepliedPostId is null ? "posts.new" : "posts.new.replies",
|
||||
ResourceIdentifier = ResourceIdentifier,
|
||||
Data = this
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class SnPostTag : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
[JsonIgnore] public ICollection<SnPost> Posts { get; set; } = new List<SnPost>();
|
||||
|
||||
[NotMapped] public int? Usage { get; set; }
|
||||
}
|
||||
|
||||
public class SnPostCategory : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
[JsonIgnore] public ICollection<SnPost> Posts { get; set; } = new List<SnPost>();
|
||||
|
||||
[NotMapped] public int? Usage { get; set; }
|
||||
}
|
||||
|
||||
public class SnPostCategorySubscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
public Guid? CategoryId { get; set; }
|
||||
public SnPostCategory? Category { get; set; }
|
||||
public Guid? TagId { get; set; }
|
||||
public SnPostTag? Tag { get; set; }
|
||||
}
|
||||
|
||||
public class SnPostCollection : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
|
||||
public SnPublisher Publisher { get; set; } = null!;
|
||||
|
||||
public ICollection<SnPost> Posts { get; set; } = new List<SnPost>();
|
||||
}
|
||||
|
||||
public class SnPostFeaturedRecord : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid PostId { get; set; }
|
||||
public SnPost Post { get; set; } = null!;
|
||||
public Instant? FeaturedAt { get; set; }
|
||||
public int SocialCredits { get; set; }
|
||||
}
|
||||
|
||||
public enum PostReactionAttitude
|
||||
{
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative,
|
||||
}
|
||||
|
||||
public class SnPostReaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
||||
public PostReactionAttitude Attitude { get; set; }
|
||||
|
||||
public Guid PostId { get; set; }
|
||||
[JsonIgnore] public SnPost Post { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
}
|
||||
|
||||
public class SnPostAward : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public PostReactionAttitude Attitude { get; set; }
|
||||
[MaxLength(4096)] public string? Message { get; set; }
|
||||
|
||||
public Guid PostId { get; set; }
|
||||
[JsonIgnore] public SnPost Post { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This model is used to tell the client to render a WebView / iframe
|
||||
/// Usually external website and web pages
|
||||
/// Used as a JSON column
|
||||
/// </summary>
|
||||
public class PostEmbedView
|
||||
{
|
||||
public string Uri { get; set; } = null!;
|
||||
public double? AspectRatio { get; set; }
|
||||
public PostEmbedViewRenderer Renderer { get; set; } = PostEmbedViewRenderer.WebView;
|
||||
}
|
||||
|
||||
public enum PostEmbedViewRenderer
|
||||
{
|
||||
WebView
|
||||
}
|
166
DysonNetwork.Shared/Models/Publisher.cs
Normal file
166
DysonNetwork.Shared/Models/Publisher.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum PublisherType
|
||||
{
|
||||
Individual,
|
||||
Organizational
|
||||
}
|
||||
|
||||
[Index(nameof(Name), IsUnique = true)]
|
||||
public class SnPublisher : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
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 SnCloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnVerificationMark? Verification { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<SnPost> Posts { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnPoll> Polls { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnPostCollection> Collections { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<SnPublisherMember> Members { get; set; } = [];
|
||||
[JsonIgnore] public ICollection<PublisherFeature> Features { get; set; } = [];
|
||||
|
||||
[JsonIgnore]
|
||||
public ICollection<PublisherSubscription> Subscriptions { get; set; } = [];
|
||||
|
||||
public Guid? AccountId { get; set; }
|
||||
public Guid? RealmId { get; set; }
|
||||
[JsonIgnore] public SnRealm? Realm { get; set; }
|
||||
[NotMapped] public Account? Account { get; set; }
|
||||
|
||||
public string ResourceIdentifier => $"publisher:{Id}";
|
||||
|
||||
public Proto.Publisher ToProto()
|
||||
{
|
||||
var p = new Proto.Publisher()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Type = Type == PublisherType.Individual
|
||||
? Shared.Proto.PublisherType.PubIndividual
|
||||
: Shared.Proto.PublisherType.PubOrganizational,
|
||||
Name = Name,
|
||||
Nick = Nick,
|
||||
Bio = Bio,
|
||||
AccountId = AccountId?.ToString() ?? string.Empty,
|
||||
RealmId = RealmId?.ToString() ?? string.Empty,
|
||||
CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
|
||||
UpdatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
|
||||
};
|
||||
if (Picture is not null)
|
||||
{
|
||||
p.Picture = new Proto.CloudFile
|
||||
{
|
||||
Id = Picture.Id,
|
||||
Name = Picture.Name,
|
||||
MimeType = Picture.MimeType,
|
||||
Hash = Picture.Hash,
|
||||
Size = Picture.Size,
|
||||
};
|
||||
}
|
||||
|
||||
if (Background is not null)
|
||||
{
|
||||
p.Background = new Proto.CloudFile
|
||||
{
|
||||
Id = Background.Id,
|
||||
Name = Background.Name,
|
||||
MimeType = Background.MimeType,
|
||||
Hash = Background.Hash,
|
||||
Size = Background.Size,
|
||||
};
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PublisherMemberRole
|
||||
{
|
||||
Owner = 100,
|
||||
Manager = 75,
|
||||
Editor = 50,
|
||||
Viewer = 25
|
||||
}
|
||||
|
||||
public class SnPublisherMember : ModelBase
|
||||
{
|
||||
public Guid PublisherId { get; set; }
|
||||
[JsonIgnore] public SnPublisher Publisher { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public Account? Account { get; set; }
|
||||
|
||||
public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
|
||||
|
||||
public Proto.PublisherMember ToProto()
|
||||
{
|
||||
return new Proto.PublisherMember()
|
||||
{
|
||||
PublisherId = PublisherId.ToString(),
|
||||
AccountId = AccountId.ToString(),
|
||||
Role = Role switch
|
||||
{
|
||||
PublisherMemberRole.Owner => Shared.Proto.PublisherMemberRole.Owner,
|
||||
PublisherMemberRole.Manager => Shared.Proto.PublisherMemberRole.Manager,
|
||||
PublisherMemberRole.Editor => Shared.Proto.PublisherMemberRole.Editor,
|
||||
PublisherMemberRole.Viewer => Shared.Proto.PublisherMemberRole.Viewer,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Role), Role, null)
|
||||
},
|
||||
JoinedAt = JoinedAt?.ToTimestamp()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum PublisherSubscriptionStatus
|
||||
{
|
||||
Active,
|
||||
Expired,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
public class PublisherSubscription : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
[JsonIgnore] public SnPublisher Publisher { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
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 SnPublisher Publisher { get; set; } = null!;
|
||||
}
|
||||
|
||||
public abstract class PublisherFeatureFlag
|
||||
{
|
||||
public static List<string> AllFlags => [Develop];
|
||||
public static string Develop = "develop";
|
||||
}
|
27
DysonNetwork.Shared/Models/Punishment.cs
Normal file
27
DysonNetwork.Shared/Models/Punishment.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum PunishmentType
|
||||
{
|
||||
// TODO: impl the permission modification
|
||||
PermissionModification,
|
||||
BlockLogin,
|
||||
DisableAccount,
|
||||
Strike
|
||||
}
|
||||
|
||||
public class SnAccountPunishment : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(8192)] public string Reason { get; set; } = string.Empty;
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public PunishmentType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<string>? BlockedPermissions { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
}
|
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 SnRealm : 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 SnCloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnVerificationMark? Verification { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<SnRealmMember> Members { get; set; } = new List<SnRealmMember>();
|
||||
[JsonIgnore] public ICollection<SnChatRoom> ChatRooms { get; set; } = new List<SnChatRoom>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
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 SnRealmMember : ModelBase
|
||||
{
|
||||
public Guid RealmId { get; set; }
|
||||
public SnRealm Realm { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public SnAccount? Account { get; set; }
|
||||
[NotMapped] public SnAccountStatus? Status { get; set; }
|
||||
|
||||
public int Role { get; set; } = RealmMemberRole.Normal;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
}
|
47
DysonNetwork.Shared/Models/RealtimeCall.cs
Normal file
47
DysonNetwork.Shared/Models/RealtimeCall.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnRealtimeCall : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant? EndedAt { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public SnChatMember Sender { get; set; } = null!;
|
||||
public Guid RoomId { get; set; }
|
||||
public SnChatRoom 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;
|
||||
}
|
||||
}
|
34
DysonNetwork.Shared/Models/Relationship.cs
Normal file
34
DysonNetwork.Shared/Models/Relationship.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public enum RelationshipStatus : short
|
||||
{
|
||||
Friends = 100,
|
||||
Pending = 0,
|
||||
Blocked = -100
|
||||
}
|
||||
|
||||
public class SnAccountRelationship : ModelBase
|
||||
{
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
public Guid RelatedId { get; set; }
|
||||
public SnAccount Related { get; set; } = null!;
|
||||
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending;
|
||||
|
||||
public Proto.Relationship ToProtoValue() => new()
|
||||
{
|
||||
AccountId = AccountId.ToString(),
|
||||
RelatedId = RelatedId.ToString(),
|
||||
Account = Account.ToProtoValue(),
|
||||
Related = Related.ToProtoValue(),
|
||||
Status = (int)Status,
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
}
|
33
DysonNetwork.Shared/Models/SocialCreditRecord.cs
Normal file
33
DysonNetwork.Shared/Models/SocialCreditRecord.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SocialCreditRecord : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
||||
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
||||
public double Delta { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Proto.SocialCreditRecord ToProto()
|
||||
{
|
||||
var proto = new Proto.SocialCreditRecord
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
ReasonType = ReasonType,
|
||||
Reason = Reason,
|
||||
Delta = Delta,
|
||||
AccountId = AccountId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
48
DysonNetwork.Shared/Models/Sticker.cs
Normal file
48
DysonNetwork.Shared/Models/Sticker.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
[Index(nameof(Slug))] // The slug index shouldn't be unique, the sticker slug can be repeated across packs.
|
||||
public class SnSticker : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? ImageId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public SnCloudFileReferenceObject? Image { get; set; } = null!;
|
||||
|
||||
public Guid PackId { get; set; }
|
||||
[JsonIgnore] public StickerPack Pack { get; set; } = null!;
|
||||
|
||||
public string ResourceIdentifier => $"sticker/{Id}";
|
||||
}
|
||||
|
||||
[Index(nameof(Prefix), IsUnique = true)]
|
||||
public class StickerPack : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Name { get; set; } = null!;
|
||||
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
||||
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
||||
|
||||
public List<SnSticker> Stickers { get; set; } = [];
|
||||
[JsonIgnore] public List<StickerPackOwnership> Ownerships { get; set; } = [];
|
||||
|
||||
public Guid PublisherId { get; set; }
|
||||
public SnPublisher Publisher { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class StickerPackOwnership : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public Guid PackId { get; set; }
|
||||
public StickerPack Pack { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public SnAccount Account { get; set; } = null!;
|
||||
}
|
404
DysonNetwork.Shared/Models/Subscription.cs
Normal file
404
DysonNetwork.Shared/Models/Subscription.cs
Normal file
@@ -0,0 +1,404 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Globalization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
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 SnSubscription : 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 SnAccount 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference object that contains a subset of subscription data
|
||||
/// suitable for client-side use, with sensitive information removed.
|
||||
/// </summary>
|
||||
public SubscriptionReferenceObject ToReference()
|
||||
{
|
||||
return new SubscriptionReferenceObject
|
||||
{
|
||||
Id = Id,
|
||||
Identifier = Identifier,
|
||||
BegunAt = BegunAt,
|
||||
EndedAt = EndedAt,
|
||||
IsActive = IsActive,
|
||||
IsAvailable = IsAvailable,
|
||||
IsFreeTrial = IsFreeTrial,
|
||||
Status = Status,
|
||||
BasePrice = BasePrice,
|
||||
FinalPrice = FinalPrice,
|
||||
RenewalAt = RenewalAt,
|
||||
AccountId = AccountId
|
||||
};
|
||||
}
|
||||
|
||||
public Proto.Subscription ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
BegunAt = BegunAt.ToTimestamp(),
|
||||
EndedAt = EndedAt?.ToTimestamp(),
|
||||
Identifier = Identifier,
|
||||
IsActive = IsActive,
|
||||
IsFreeTrial = IsFreeTrial,
|
||||
Status = (Proto.SubscriptionStatus)Status,
|
||||
PaymentMethod = PaymentMethod,
|
||||
PaymentDetails = PaymentDetails.ToProtoValue(),
|
||||
BasePrice = BasePrice.ToString(CultureInfo.InvariantCulture),
|
||||
CouponId = CouponId?.ToString(),
|
||||
Coupon = Coupon?.ToProtoValue(),
|
||||
RenewalAt = RenewalAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
IsAvailable = IsAvailable,
|
||||
FinalPrice = FinalPrice.ToString(CultureInfo.InvariantCulture),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
public static SnSubscription FromProtoValue(Proto.Subscription proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
BegunAt = proto.BegunAt.ToInstant(),
|
||||
EndedAt = proto.EndedAt?.ToInstant(),
|
||||
Identifier = proto.Identifier,
|
||||
IsActive = proto.IsActive,
|
||||
IsFreeTrial = proto.IsFreeTrial,
|
||||
Status = (SubscriptionStatus)proto.Status,
|
||||
PaymentMethod = proto.PaymentMethod,
|
||||
PaymentDetails = PaymentDetails.FromProtoValue(proto.PaymentDetails),
|
||||
BasePrice = decimal.Parse(proto.BasePrice),
|
||||
CouponId = proto.HasCouponId ? Guid.Parse(proto.CouponId) : null,
|
||||
Coupon = proto.Coupon is not null ? Coupon.FromProtoValue(proto.Coupon) : null,
|
||||
RenewalAt = proto.RenewalAt?.ToInstant(),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reference object for Subscription that contains only non-sensitive information
|
||||
/// suitable for client-side use.
|
||||
/// </summary>
|
||||
public class SubscriptionReferenceObject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Identifier { get; set; } = null!;
|
||||
public Instant BegunAt { get; set; }
|
||||
public Instant? EndedAt { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsAvailable { get; set; }
|
||||
public bool IsFreeTrial { get; set; }
|
||||
public SubscriptionStatus Status { get; set; }
|
||||
public decimal BasePrice { get; set; }
|
||||
public decimal FinalPrice { get; set; }
|
||||
public Instant? RenewalAt { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the human-readable name of the subscription type if available.
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string? DisplayName => SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(Identifier, out var name)
|
||||
? name
|
||||
: null;
|
||||
|
||||
public Proto.SubscriptionReferenceObject ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Identifier = Identifier,
|
||||
BegunAt = BegunAt.ToTimestamp(),
|
||||
EndedAt = EndedAt?.ToTimestamp(),
|
||||
IsActive = IsActive,
|
||||
IsAvailable = IsAvailable,
|
||||
IsFreeTrial = IsFreeTrial,
|
||||
Status = (Proto.SubscriptionStatus)Status,
|
||||
BasePrice = BasePrice.ToString(CultureInfo.CurrentCulture),
|
||||
FinalPrice = FinalPrice.ToString(CultureInfo.CurrentCulture),
|
||||
RenewalAt = RenewalAt?.ToTimestamp(),
|
||||
AccountId = AccountId.ToString(),
|
||||
DisplayName = DisplayName,
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
public static SubscriptionReferenceObject FromProtoValue(Proto.SubscriptionReferenceObject proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Identifier = proto.Identifier,
|
||||
BegunAt = proto.BegunAt.ToInstant(),
|
||||
EndedAt = proto.EndedAt?.ToInstant(),
|
||||
IsActive = proto.IsActive,
|
||||
IsAvailable = proto.IsAvailable,
|
||||
IsFreeTrial = proto.IsFreeTrial,
|
||||
Status = (SubscriptionStatus)proto.Status,
|
||||
BasePrice = decimal.Parse(proto.BasePrice),
|
||||
FinalPrice = decimal.Parse(proto.FinalPrice),
|
||||
RenewalAt = proto.RenewalAt?.ToInstant(),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
||||
|
||||
public class PaymentDetails
|
||||
{
|
||||
public string Currency { get; set; } = null!;
|
||||
public string? OrderId { get; set; }
|
||||
|
||||
public Proto.PaymentDetails ToProtoValue() => new()
|
||||
{
|
||||
Currency = Currency,
|
||||
OrderId = OrderId,
|
||||
};
|
||||
|
||||
public static PaymentDetails FromProtoValue(Proto.PaymentDetails proto) => new()
|
||||
{
|
||||
Currency = proto.Currency,
|
||||
OrderId = proto.OrderId,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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; }
|
||||
|
||||
public Proto.Coupon ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Identifier = Identifier,
|
||||
Code = Code,
|
||||
AffectedAt = AffectedAt?.ToTimestamp(),
|
||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||
DiscountAmount = DiscountAmount?.ToString(),
|
||||
DiscountRate = DiscountRate,
|
||||
MaxUsage = MaxUsage,
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
|
||||
public static Coupon FromProtoValue(Proto.Coupon proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Identifier = proto.Identifier,
|
||||
Code = proto.Code,
|
||||
AffectedAt = proto.AffectedAt?.ToInstant(),
|
||||
ExpiredAt = proto.ExpiredAt?.ToInstant(),
|
||||
DiscountAmount = proto.HasDiscountAmount ? decimal.Parse(proto.DiscountAmount) : null,
|
||||
DiscountRate = proto.DiscountRate,
|
||||
MaxUsage = proto.MaxUsage,
|
||||
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||
};
|
||||
}
|
@@ -1,13 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
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 class SnVerificationMark
|
||||
{
|
||||
public VerificationMarkType Type { get; set; }
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
@@ -38,9 +38,9 @@ public class VerificationMark
|
||||
}
|
||||
|
||||
|
||||
public static VerificationMark FromProtoValue(Shared.Proto.VerificationMark proto)
|
||||
public static SnVerificationMark FromProtoValue(Proto.VerificationMark proto)
|
||||
{
|
||||
return new VerificationMark
|
||||
return new SnVerificationMark
|
||||
{
|
||||
Type = proto.Type switch
|
||||
{
|
64
DysonNetwork.Shared/Models/Wallet.cs
Normal file
64
DysonNetwork.Shared/Models/Wallet.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnWallet : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public ICollection<SnWalletPocket> Pockets { get; set; } = new List<SnWalletPocket>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
|
||||
public Proto.Wallet ToProtoValue()
|
||||
{
|
||||
var proto = new Proto.Wallet
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
AccountId = AccountId.ToString(),
|
||||
};
|
||||
|
||||
foreach (var pocket in Pockets)
|
||||
{
|
||||
proto.Pockets.Add(pocket.ToProtoValue());
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static SnWallet FromProtoValue(Proto.Wallet proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
AccountId = Guid.Parse(proto.AccountId),
|
||||
Pockets = [.. proto.Pockets.Select(SnWalletPocket.FromProtoValue)],
|
||||
};
|
||||
}
|
||||
|
||||
public class SnWalletPocket : 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 SnWallet Wallet { get; set; } = null!;
|
||||
|
||||
public Proto.WalletPocket ToProtoValue() => new()
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Currency = Currency,
|
||||
Amount = Amount.ToString(CultureInfo.CurrentCulture),
|
||||
WalletId = WalletId.ToString(),
|
||||
};
|
||||
|
||||
public static SnWalletPocket FromProtoValue(Proto.WalletPocket proto) => new()
|
||||
{
|
||||
Id = Guid.Parse(proto.Id),
|
||||
Currency = proto.Currency,
|
||||
Amount = decimal.Parse(proto.Amount),
|
||||
WalletId = Guid.Parse(proto.WalletId),
|
||||
};
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public abstract class WebSocketPacketType
|
||||
{
|
@@ -1,10 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
|
||||
namespace DysonNetwork.Shared.Data;
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class WebSocketPacket
|
||||
{
|
||||
@@ -57,9 +55,9 @@ public class WebSocketPacket
|
||||
return System.Text.Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
public Shared.Proto.WebSocketPacket ToProtoValue()
|
||||
public Proto.WebSocketPacket ToProtoValue()
|
||||
{
|
||||
return new Shared.Proto.WebSocketPacket
|
||||
return new Proto.WebSocketPacket
|
||||
{
|
||||
Type = Type,
|
||||
Data = GrpcTypeHelper.ConvertObjectToByteString(Data),
|
||||
@@ -67,7 +65,7 @@ public class WebSocketPacket
|
||||
};
|
||||
}
|
||||
|
||||
public static WebSocketPacket FromProtoValue(Shared.Proto.WebSocketPacket packet)
|
||||
public static WebSocketPacket FromProtoValue(Proto.WebSocketPacket packet)
|
||||
{
|
||||
return new WebSocketPacket
|
||||
{
|
@@ -1,6 +1,4 @@
|
||||
using System.Net;
|
||||
using Grpc.Net.Client;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace DysonNetwork.Shared.Proto;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
|
||||
namespace DysonNetwork.Shared.Registry;
|
||||
@@ -35,13 +36,13 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts)
|
||||
return response.Accounts.ToList();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, AccountStatusReference>> GetAccountStatusBatch(List<Guid> ids)
|
||||
public async Task<Dictionary<Guid, SnAccountStatus>> GetAccountStatusBatch(List<Guid> ids)
|
||||
{
|
||||
var request = new GetAccountBatchRequest();
|
||||
request.Id.AddRange(ids.Select(id => id.ToString()));
|
||||
var response = await accounts.GetAccountStatusBatchAsync(request);
|
||||
return response.Statuses
|
||||
.Select(AccountStatusReference.FromProtoValue)
|
||||
.Select(SnAccountStatus.FromProtoValue)
|
||||
.ToDictionary(s => s.AccountId, s => s);
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DysonNetwork.Shared.Registry;
|
||||
|
Reference in New Issue
Block a user