XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

STHApplication

【MAUI】寄当家——安卓端交易管理系统

公开
关注 0 Fork 0 Star 0
UTF-8
using JiDangJia.Contracts;
using Microsoft.Data.Sqlite;

namespace JiDangJia.Server;

public sealed record ClearMyDataRequest(string Password,string PasswordAgain,string ClientId);
public sealed record PasswordVerificationRequest(string Password);
public sealed partial class ServerStore
{
    private static void SafetyColumn(SqliteConnection c,string table,string column,string definition)
    {if(!Rows(c,"PRAGMA table_info("+table+")").Any(r=>Text(r,"name")==column))Exec(c,$"ALTER TABLE {table} ADD COLUMN {column} {definition}");}
    private static void InitializeSafety(SqliteConnection c)
    {
        foreach(var (column,definition) in new[]{("phone","TEXT NOT NULL DEFAULT ''"),("phoneMethod","TEXT NOT NULL DEFAULT ''"),("identityStatus","TEXT NOT NULL DEFAULT 'NONE'"),("identityKind","TEXT NOT NULL DEFAULT 'PERSONAL'"),("communityBanned","INTEGER NOT NULL DEFAULT 0"),("communityBanReason","TEXT NOT NULL DEFAULT ''")})SafetyColumn(c,"Accounts",column,definition);
#if !COMMUNITY_APP
        SafetyColumn(c,"Cards","permanent","INTEGER NOT NULL DEFAULT 0");SafetyColumn(c,"Cards","fixedUntil","TEXT");SafetyColumn(c,"Licenses","permanent","INTEGER NOT NULL DEFAULT 0");
#endif
        SafetyColumn(c,"Accounts","communityBanUntil","TEXT NOT NULL DEFAULT ''");
        Exec(c,"""
        CREATE TABLE IF NOT EXISTS CommunityPolicy(id INTEGER PRIMARY KEY CHECK(id=1),version INTEGER NOT NULL,text TEXT NOT NULL,publishedAt TEXT NOT NULL,purchaseUrl TEXT NOT NULL DEFAULT '');
        CREATE TABLE IF NOT EXISTS CommunityAcceptances(accountId TEXT PRIMARY KEY,version INTEGER NOT NULL,acceptedAt TEXT NOT NULL);
        CREATE TABLE IF NOT EXISTS AccountChatClears(accountId TEXT PRIMARY KEY,throughId INTEGER NOT NULL,clearedAt TEXT NOT NULL);
        CREATE TABLE IF NOT EXISTS AccountDataClears(accountId TEXT NOT NULL,clientId TEXT NOT NULL,throughId INTEGER NOT NULL,createdAt TEXT NOT NULL,PRIMARY KEY(accountId,clientId));
        """);
        Exec(c,"INSERT OR IGNORE INTO CommunityPolicy(id,version,text,publishedAt) VALUES(1,1,@p0,@p1)",CommunitySafetyDefaults.Agreement,Now);
    }
    internal static Dictionary<string,object?> SafetyAccount(SqliteConnection c,string id)
    =>Rows(c,"SELECT * FROM Accounts WHERE id=@p0 AND active=1",id).FirstOrDefault()??throw new CommunityException(401,"账户不可用,请重新登录");
    internal static void SocialRead(SqliteConnection c,Account account)
    {
        SafetyAccount(c,account.Id);

        if(Rows(c,"SELECT a.accountId FROM CommunityAcceptances a JOIN CommunityPolicy p ON a.version=p.version WHERE a.accountId=@p0",account.Id).Count==0)throw new CommunityException(428,"请先阅读并勾选同意最新社群用户协议");
    }
    internal static void SocialWrite(SqliteConnection c,Account account,string? peer=null)
    {
        SocialRead(c,account);var row=SafetyAccount(c,account.Id);
        if(CommunityMuted(row))throw new CommunityException(403,"社群已禁言"+(Text(row,"communityBanUntil").Length==0?"(永久)":",截止 "+Text(row,"communityBanUntil"))+":"+Text(row,"communityBanReason"));
        if(Text(row,"identityStatus")!="APPROVED"||Text(row,"phone").Length==0)throw new CommunityException(403,"浏览无需认证;发布或聊天请先完成个人 / 企业认证及手机号绑定");
        if(peer is not null){var other=SafetyAccount(c,peer);SocialRead(c,AccountFrom(other));if(Text(other,"identityStatus")!="APPROVED"||Text(other,"phone").Length==0)throw new CommunityException(403,"对方尚未完成认证及手机号绑定");}
    }
    internal static bool CommunityMuted(Dictionary<string,object?> row)
    {var until=Text(row,"communityBanUntil");return Text(row,"communityBanned")=="1"&&(until.Length==0||!DateTimeOffset.TryParse(until,out var end)||end>DateTimeOffset.UtcNow);}
    public CommunityAgreement Agreement()=>Read(c=>{var r=Rows(c,"SELECT * FROM CommunityPolicy WHERE id=1")[0];return new CommunityAgreement(Convert.ToInt64(r["version"]),Text(r,"text"),Text(r,"publishedAt"));});
    public CommunityAccess Access(Account account)=>Read(c=>
    {
        var r=SafetyAccount(c,account.Id);var p=Rows(c,"SELECT * FROM CommunityPolicy WHERE id=1")[0];var accepted=Rows(c,"SELECT accountId FROM CommunityAcceptances WHERE accountId=@p0 AND version=@p1",account.Id,p["version"]).Count>0;
        bool muted=CommunityMuted(r);return new CommunityAccess(muted,muted?Text(r,"communityBanReason"):"",accepted,Convert.ToInt64(p["version"]),Text(r,"identityStatus"),Text(r,"identityKind"),Text(r,"phone").Length>0,muted?Text(r,"communityBanUntil"):"");
    });
    public CommunityAccess AcceptAgreement(Account account,AcceptAgreementRequest request)
    {
        Write(c=>{SafetyAccount(c,account.Id);var current=Convert.ToInt64(Rows(c,"SELECT version FROM CommunityPolicy WHERE id=1")[0]["version"]);if(!request.Accepted)throw new ArgumentException("需主动勾选同意协议");if(current!=request.Version)throw new CommunityException(409,"协议版本已更新,请重新阅读");Exec(c,"INSERT INTO CommunityAcceptances VALUES(@p0,@p1,@p2) ON CONFLICT(accountId) DO UPDATE SET version=excluded.version,acceptedAt=excluded.acceptedAt",account.Id,current,Now);Audit(c,account.Id,"SOCIAL_AGREEMENT",current.ToString(),"");return 0;});return Access(account);
    }
    public CommunityGovernance Governance(Account actor)
    {RequireAdmin(actor);return Read(c=>{var r=Rows(c,"SELECT * FROM CommunityPolicy WHERE id=1")[0];return new CommunityGovernance(new(Convert.ToInt64(r["version"]),Text(r,"text"),Text(r,"publishedAt")),"DIRECT");});}
    public CommunityGovernance UpdateGovernance(Account actor,UpdateGovernanceRequest request)
    {
        RequireAdmin(actor);var text=(request.AgreementText??"").Trim();var url="";if(text.Length is <20 or >16000)throw new ArgumentException("协议正文需要 20–16000 字");
        Write(c=>{var current=Rows(c,"SELECT * FROM CommunityPolicy WHERE id=1")[0];if(Convert.ToInt64(current["version"])!=request.ExpectedVersion)throw new CommunityException(409,"配置已更新,请刷新后修改");bool changed=Text(current,"text")!=text;Exec(c,"UPDATE CommunityPolicy SET version=version+@p0,text=@p1,publishedAt=CASE WHEN @p0=1 THEN @p2 ELSE publishedAt END,purchaseUrl=@p3 WHERE id=1",changed?1:0,text,Now,url);Audit(c,actor.Id,"SOCIAL_POLICY",request.ExpectedVersion.ToString(),changed?"新协议版本":"协议设置");return 0;});return Governance(actor);
    }
    public void SetCommunityBan(Account admin,string id,CommunityBanRequest request)
    {
        RequireAdmin(admin);var reason=(request.Reason??"").Trim();if(request.Banned&&reason.Length is <1 or >500)throw new ArgumentException("禁言原因需为 1–500 字");
        if(request.Banned&&request.Days is <1 or >36500)throw new ArgumentException("禁言天数需为 1–36500,永久禁言不填写天数");
        string until=request.Banned&&request.Days.HasValue?DateTimeOffset.UtcNow.AddDays(request.Days.Value).ToString("O"):"";
        Write(c=>{var row=SafetyAccount(c,id);if(Text(row,"role")=="admin")throw new ArgumentException("管理员不可通过此处禁言");Exec(c,"UPDATE Accounts SET communityBanned=@p0,communityBanReason=@p1,communityBanUntil=@p2 WHERE id=@p3",request.Banned?1:0,request.Banned?reason:"",until,id);Audit(c,admin.Id,"SOCIAL_BAN",id,request.Banned?reason+";"+(until.Length==0?"永久":"截止 "+until):"解除禁言");return 0;});
    }
    public void VerifyOwnPassword(Account account,string password)=>Read(c=>{CheckPassword(c,account,password);return 0;});
    private static void CheckPassword(SqliteConnection c,Account account,string? password)
    {var row=SafetyAccount(c,account.Id);if(password is null||password.Length is <8 or >128||!VerifyPassword(password,Text(row,"passwordHash")))throw new CommunityException(403,"登录密码不正确");}
    public object ClearOwnChat(Account account,ClearMyDataRequest request)=>Write(c=>
    {
        CheckPassword(c,account,request.Password);CheckPassword(c,account,request.PasswordAgain);
        if(request.ClientId is null||!System.Text.RegularExpressions.Regex.IsMatch(request.ClientId,"^[a-f0-9]{32}$"))throw new ArgumentException("清除请求标识无效");
        var previous=Rows(c,"SELECT throughId FROM AccountDataClears WHERE accountId=@p0 AND clientId=@p1",account.Id,request.ClientId).FirstOrDefault();
        long through=previous is null?Convert.ToInt64(Rows(c,"SELECT COALESCE(MAX(id),0) id FROM ChatMessages WHERE senderId=@p0 OR recipientId=@p0",account.Id)[0]["id"]):Convert.ToInt64(previous["throughId"]);
        Exec(c,"INSERT INTO AccountChatClears VALUES(@p0,@p1,@p2) ON CONFLICT(accountId) DO UPDATE SET throughId=MAX(throughId,excluded.throughId),clearedAt=excluded.clearedAt",account.Id,through,Now);
        Exec(c,"INSERT OR IGNORE INTO AccountDataClears VALUES(@p0,@p1,@p2,@p3)",account.Id,request.ClientId,through,Now);
        Exec(c,"DELETE FROM ChatReads WHERE accountId=@p0",account.Id);Exec(c,"DELETE FROM ChatReadMessages WHERE accountId=@p0",account.Id);Exec(c,"DELETE FROM ChatDeliveries WHERE accountId=@p0 AND messageId<=@p1",account.Id,through);
        Audit(c,account.Id,"OWN_DATA_CLEAR",account.Id,"仅清除此账户可见聊天历史;社群本机缓存由设备清除");return new{ok=true,accountId=account.Id,clientId=request.ClientId,throughId=through};
    });
}