feat: V2 microservices stack — backend services, gateway, JWT auth

Add full V2 architecture: identity, content, studio (.NET 10) and file,
render, notification, gateway (Go) services with vendored deps, plus DB
migrations, event/API contracts, and an init-db script.

Wire the Next.js frontend to the gateway: server-side JWT auth routes
(login/register/refresh/logout/me), gateway fetch helper, and session/
cookie/jwt helpers under src/lib.

Containerize the stack via docker-compose.v2.yml and per-service
Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and
MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via
next/font/local to avoid Google Fonts (geo-blocked).

Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-29 23:29:31 +03:30
parent 53ea78a00d
commit 90ac0b81d1
7636 changed files with 3707504 additions and 240 deletions
@@ -0,0 +1,54 @@
using System.ComponentModel.DataAnnotations;
namespace FlatRender.IdentitySvc.Models.Requests;
public record RegisterRequest(
[Required] string TenantSlug,
string? Email,
string? PhoneNumber,
[Required, MinLength(8)] string Password,
string? FullName,
string? AffiliateCode,
bool AcceptTerms = true
);
public record LoginRequest(
[Required] string TenantSlug,
string? Email,
string? PhoneNumber,
[Required] string Password,
string? DeviceId,
string? DeviceName
);
public record OAuthLoginRequest(
[Required] string TenantSlug,
[Required] string Code,
string? RedirectUri
);
public record RefreshTokenRequest([Required] string RefreshToken);
public record VerifyOtpRequest([Required] string Token, [Required] string Code);
public record PasswordResetRequestDto([Required] string TenantSlug, string? Email, string? PhoneNumber);
public record PasswordResetConfirmRequest([Required] string Token, [Required, MinLength(8)] string NewPassword);
public record PasswordChangeRequest([Required] string CurrentPassword, [Required, MinLength(8)] string NewPassword);
public record MfaSetupRequest([Required] string FactorType, string? Label);
public record MfaVerifyRequest([Required] Guid FactorId, [Required] string Code);
public record MfaChallengeRequest([Required] string MfaToken, [Required] string Code);
public record PushSubscribeRequest(
[Required] string Endpoint,
[Required] PushKeys Keys,
string? UserAgent
);
public record PushKeys([Required] string P256dh, [Required] string Auth);
public record PushUnsubscribeRequest(string? Endpoint);
@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
namespace FlatRender.IdentitySvc.Models.Requests;
public record PurchasePlanRequest(
[Required] Guid PlanId,
string? Gateway,
string? DiscountCode
);
public record ValidateDiscountRequest([Required] string Code, Guid? PlanId);
public record CreateDiscountRequest(
[Required] string Name,
[Required] string Code,
[Required] string Kind,
[Required] decimal Value,
Guid? OwnerUserId,
decimal OwnerProfitPercentage = 0,
int? MaxUseCount = null,
Guid[]? AppliesToPlanIds = null,
DateTime? StartsAt = null,
DateTime? ExpiresAt = null
);
public record IssueRefundRequest(
[Required] Guid PaymentId,
long? AmountMinor,
[Required] string Reason,
string RefundTo = "Balance"
);
@@ -0,0 +1,71 @@
using System.ComponentModel.DataAnnotations;
namespace FlatRender.IdentitySvc.Models.Requests;
public record CreateTenantRequest(
[Required] string Slug,
[Required] string Name,
string? Kind,
string? ContactName,
[Required, EmailAddress] string ContactEmail,
string? ContactPhone
);
public record UpdateTenantRequest(
string? Name,
string? ContactName,
string? ContactEmail,
string? ContactPhone,
string? BillingEmail,
string[]? AllowedOrigins
);
public record TenantBrandingRequest(
string? DisplayName,
string? LogoUrl,
string? LogoDarkUrl,
string? FaviconUrl,
string? OgImageUrl,
string? PrimaryColor,
string? SecondaryColor,
string? AccentColor,
string? BackgroundColor,
string? FontFamily,
string? EmailFromName,
string? EmailFromAddress,
string? EmailReplyTo,
string? EmailFooterHtml,
string? SupportUrl,
string? TermsUrl,
string? PrivacyUrl,
bool? EmbedEnabled,
string[]? EmbedAllowedHosts,
string? WatermarkText,
string? WatermarkImageUrl,
bool? WatermarkEnabled
);
public record StartDomainVerificationRequest([Required] string Domain, string Method = "DNS_TXT");
public record CreateApiKeyRequest(
[Required] string Name,
string Environment = "Live",
[Required] string[] Scopes = default!,
string[]? AllowedIps = null,
int RateLimitRpm = 60,
DateTime? ExpiresAt = null
);
public record RevokeApiKeyRequest(string? Reason);
public record ValidateApiKeyRequest(
[Required] string KeyPrefix,
[Required] string KeyHash,
string? IpAddress
);
public record CreateWebhookRequest(
[Required] string Name,
[Required] string Url,
[Required] string[] Events
);
@@ -0,0 +1,19 @@
namespace FlatRender.IdentitySvc.Models.Requests;
public record UpdateUserRequest(
string? FullName,
string? Slogan,
string? AboutMe,
string? CompanyName,
string? WebsiteName,
DateOnly? BirthDate,
string? Gender,
bool? EmailTellMe,
bool? SmsTellMe,
bool? PushTellMe,
bool? TelegramTellMe
);
public record SetAvatarRequest(Guid? AvatarId, string? AvatarUrl);
public record BanUserRequest(string Reason, DateTime? UnblockDate);
@@ -0,0 +1,278 @@
namespace FlatRender.IdentitySvc.Models.Responses;
public record AuthTokensResponse(
string AccessToken,
string RefreshToken,
string TokenType,
int ExpiresIn,
UserResponse? User,
TenantResponse? Tenant
);
public record RegisterResponse(Guid UserId, bool VerificationRequired);
public record SessionResponse(
Guid Id,
string? DeviceName,
string? UserAgent,
string? IpAddress,
DateTime IssuedAt,
DateTime? LastUsedAt,
bool IsCurrent
);
public record MfaSetupResponse(
Guid FactorId,
string? Secret,
string? QrCodeUrl,
string[]? RecoveryCodes
);
public record UserResponse(
Guid Id,
Guid TenantId,
string? Email,
bool EmailVerified,
string? PhoneNumber,
bool PhoneVerified,
string? FullName,
string? AvatarUrl,
bool IsAdmin,
bool IsTenantAdmin,
string RegisterMode,
DateTime? LastActiveDate,
long BalanceMinor,
long AffiliateBalanceMinor,
int LoyaltyScore,
int DailyRemainRenderCount,
int MaxDailyRenderCount,
int ParallelRenderingCeiling,
long UsedStorageBytes,
DateTime RegisterDate
);
public record BalanceResponse(
long BalanceMinor,
long AffiliateBalanceMinor,
string Currency,
int DailyRemainRenderCount,
int ParallelRenderingCeiling
);
public record TenantResponse(
Guid Id,
string Slug,
string Name,
string Kind,
string Status,
string? CustomDomain,
bool DomainVerified,
string? ContactEmail,
int? MaxUsers,
int? MaxStorageGb,
int? MonthlyRenderQty,
DateTime? TrialEndsAt,
DateTime CreatedAt
);
public record TenantBrandingResponse(
Guid TenantId,
string? DisplayName,
string? LogoUrl,
string? LogoDarkUrl,
string? PrimaryColor,
string? SecondaryColor,
string? AccentColor,
bool EmbedEnabled,
bool WatermarkEnabled
);
public record DomainVerificationResponse(
Guid VerificationId,
string ChallengeRecord,
DateTime ExpiresAt
);
public record TenantUsageDayResponse(
DateOnly UsageDate,
int RendersCompleted,
long RenderSeconds,
long StorageBytes,
long ApiCalls,
int ActiveUsers,
long AmountBilledMinor,
string BillingCurrency,
string? BillingStatus
);
public record ApiKeyResponse(
Guid Id,
Guid TenantId,
string Name,
string Environment,
string KeyPrefix,
string Last4,
string[] Scopes,
string[] AllowedIps,
int RateLimitRpm,
bool IsActive,
DateTime? ExpiresAt,
DateTime? LastUsedAt,
long UsageCount,
DateTime CreatedAt
);
public record ApiKeyCreatedResponse(
Guid Id,
Guid TenantId,
string Name,
string Environment,
string KeyPrefix,
string Last4,
string[] Scopes,
string SecretKey,
DateTime CreatedAt
);
public record ApiKeyValidateResponse(
bool Valid,
Guid? TenantId,
string[]? Scopes,
int? RateLimitRpm
);
public record WebhookResponse(
Guid Id,
string Name,
string Url,
string[] Events,
bool IsActive,
DateTime? LastTriggeredAt,
int? LastStatusCode,
int ConsecutiveFailures,
DateTime CreatedAt
);
public record WebhookDeliveryResponse(
Guid Id,
string EventType,
string RequestUrl,
int? ResponseStatus,
string? ResponseBody,
int DurationMs,
int Attempt,
bool Succeeded,
string? ErrorMessage,
DateTime? DeliveredAt,
DateTime CreatedAt
);
public record PlanResponse(
Guid Id,
string Code,
string Name,
string? Description,
long PriceMinor,
long? BeforePriceMinor,
string Currency,
string BillingPeriod,
int SecondsCharge,
int? MonthlyRendersQuota,
int StorageGb,
int ParallelRenders,
string MaxResolution,
decimal RenderSpeedFactor,
string? Icon,
bool IsFeatured,
object Features
);
public record UserPlanResponse(
Guid Id,
Guid PlanId,
string PlanCode,
string PlanName,
int InitialSecondsCharge,
int RemainChargeSec,
int MonthlyRendersUsed,
DateTime StartsAt,
DateTime ExpiresAt,
DateTime? CancelledAt,
bool AutoRenew
);
public record PurchasePlanResponse(Guid PaymentId, string RedirectUrl);
public record PaymentResponse(
Guid Id,
string Gateway,
string Status,
string Action,
long AmountMinor,
string Currency,
string? Title,
string? Description,
string? CardLast4,
DateTime? ConfirmedAt,
DateTime? FailedAt,
string? FailureReason,
DateTime CreatedAt
);
public record RefundResponse(Guid RefundId, string Status);
public record DiscountValidateResponse(
bool Valid,
long DiscountMinor,
string Kind,
decimal Value
);
public record DiscountResponse(
Guid Id,
string Name,
string Code,
string Kind,
decimal Value,
int UsedCount,
int? MaxUseCount,
bool IsActive,
DateTime? ExpiresAt,
DateTime CreatedAt
);
public record QuestResponse(
Guid Id,
string Title,
string? Challenge,
string? Why,
string? Hint,
string? Icon,
string QuestType,
int TargetCount,
int CurrentCount,
bool IsCompleted,
bool PrizeClaimed,
string PrizeType,
long PrizeAmount,
DateTime? ExpiresAt
);
public record EarnedGiftResponse(
Guid Id,
Guid GiftId,
string Name,
string? Description,
string PrizeType,
long Value,
string? Unit,
DateTime EarnedAt,
DateTime? ExpiresAt,
bool IsUsed
);
public record PagedResponse<T>(List<T> Data, PaginationMeta Meta);
public record PaginationMeta(int Page, int PageSize, long Total, bool HasMore);
public record ApiError(string Code, string Message, object? Details = null, string? TraceId = null);