🧱 Grpc service basis
This commit is contained in:
@@ -18,7 +18,7 @@ public class Account : ModelBase
|
||||
public Instant? ActivatedAt { get; set; }
|
||||
public bool IsSuperuser { get; set; } = false;
|
||||
|
||||
public Profile Profile { get; set; } = null!;
|
||||
public AccountProfile Profile { get; set; } = null!;
|
||||
public ICollection<AccountContact> Contacts { get; set; } = new List<AccountContact>();
|
||||
public ICollection<AccountBadge> Badges { get; set; } = new List<AccountBadge>();
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract class Leveling
|
||||
];
|
||||
}
|
||||
|
||||
public class Profile : ModelBase
|
||||
public class AccountProfile : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(256)] public string? FirstName { get; set; }
|
||||
|
@@ -72,7 +72,7 @@ public class AccountCurrentController(
|
||||
}
|
||||
|
||||
[HttpPatch("profile")]
|
||||
public async Task<ActionResult<Profile>> UpdateProfile([FromBody] ProfileRequest request)
|
||||
public async Task<ActionResult<AccountProfile>> UpdateProfile([FromBody] ProfileRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
var userId = currentUser.Id;
|
||||
|
@@ -115,7 +115,7 @@ public class AccountService(
|
||||
}.HashSecret()
|
||||
}
|
||||
: [],
|
||||
Profile = new Profile()
|
||||
Profile = new AccountProfile()
|
||||
};
|
||||
|
||||
if (isActivated)
|
||||
@@ -648,7 +648,7 @@ public class AccountService(
|
||||
|
||||
if (missingId.Count != 0)
|
||||
{
|
||||
var newProfiles = missingId.Select(id => new Profile { Id = Guid.NewGuid(), AccountId = id }).ToList();
|
||||
var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id }).ToList();
|
||||
await db.BulkInsertAsync(newProfiles);
|
||||
}
|
||||
}
|
||||
|
303
DysonNetwork.Pass/Account/AccountServiceGrpc.cs
Normal file
303
DysonNetwork.Pass/Account/AccountServiceGrpc.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.Protobuf;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class AccountServiceGrpc(
|
||||
AppDatabase db,
|
||||
IClock clock,
|
||||
ILogger<AccountServiceGrpc> logger
|
||||
)
|
||||
: Shared.Proto.AccountService.AccountServiceBase
|
||||
{
|
||||
private readonly AppDatabase _db = db ?? throw new ArgumentNullException(nameof(db));
|
||||
private readonly IClock _clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
|
||||
private readonly ILogger<AccountServiceGrpc>
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// Helper methods for conversion between protobuf and domain models
|
||||
private static Shared.Proto.Account ToProtoAccount(Account account) => new()
|
||||
{
|
||||
Id = account.Id.ToString(),
|
||||
Name = account.Name,
|
||||
Nick = account.Nick,
|
||||
Language = account.Language,
|
||||
ActivatedAt = account.ActivatedAt?.ToTimestamp(),
|
||||
IsSuperuser = account.IsSuperuser,
|
||||
Profile = ToProtoProfile(account.Profile)
|
||||
// Note: Collections are not included by default to avoid large payloads
|
||||
// They should be loaded on demand via specific methods
|
||||
};
|
||||
|
||||
private static Shared.Proto.AccountProfile ToProtoProfile(AccountProfile profile) => new()
|
||||
{
|
||||
Id = profile.Id.ToString(),
|
||||
FirstName = profile.FirstName,
|
||||
MiddleName = profile.MiddleName,
|
||||
LastName = profile.LastName,
|
||||
Bio = profile.Bio,
|
||||
Gender = profile.Gender,
|
||||
Pronouns = profile.Pronouns,
|
||||
TimeZone = profile.TimeZone,
|
||||
Location = profile.Location,
|
||||
Birthday = profile.Birthday?.ToTimestamp(),
|
||||
LastSeenAt = profile.LastSeenAt?.ToTimestamp(),
|
||||
Experience = profile.Experience,
|
||||
Level = profile.Level,
|
||||
LevelingProgress = profile.LevelingProgress,
|
||||
AccountId = profile.AccountId.ToString(),
|
||||
PictureId = profile.PictureId,
|
||||
BackgroundId = profile.BackgroundId,
|
||||
Picture = profile.Picture?.ToProtoValue(),
|
||||
Background = profile.Background?.ToProtoValue()
|
||||
};
|
||||
|
||||
private static Shared.Proto.AccountContact ToProtoContact(AccountContact contact) => new()
|
||||
{
|
||||
Id = contact.Id.ToString(),
|
||||
Type = contact.Type switch
|
||||
{
|
||||
AccountContactType.Address => Shared.Proto.AccountContactType.Address,
|
||||
AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber,
|
||||
AccountContactType.Email => Shared.Proto.AccountContactType.Email,
|
||||
_ => Shared.Proto.AccountContactType.Unspecified
|
||||
},
|
||||
VerifiedAt = contact.VerifiedAt?.ToTimestamp(),
|
||||
IsPrimary = contact.IsPrimary,
|
||||
Content = contact.Content,
|
||||
AccountId = contact.AccountId.ToString()
|
||||
};
|
||||
|
||||
private static Shared.Proto.AccountBadge ToProtoBadge(AccountBadge badge) => new()
|
||||
{
|
||||
Id = badge.Id.ToString(),
|
||||
Type = badge.Type,
|
||||
Label = badge.Label,
|
||||
Caption = badge.Caption,
|
||||
ActivatedAt = badge.ActivatedAt?.ToTimestamp(),
|
||||
ExpiredAt = badge.ExpiredAt?.ToTimestamp(),
|
||||
AccountId = badge.AccountId.ToString()
|
||||
};
|
||||
|
||||
// Implementation of gRPC service methods
|
||||
public override async Task<Shared.Proto.Account> GetAccount(GetAccountRequest request, ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var account = await _db.Accounts
|
||||
.AsNoTracking()
|
||||
.Include(a => a.Profile)
|
||||
.FirstOrDefaultAsync(a => a.Id == accountId);
|
||||
|
||||
if (account == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
||||
|
||||
return ToProtoAccount(account);
|
||||
}
|
||||
|
||||
public override async Task<Shared.Proto.Account> CreateAccount(CreateAccountRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
// Map protobuf request to domain model
|
||||
var account = new Account
|
||||
{
|
||||
Name = request.Name,
|
||||
Nick = request.Nick,
|
||||
Language = request.Language,
|
||||
IsSuperuser = request.IsSuperuser,
|
||||
ActivatedAt = request.Profile != null ? null : _clock.GetCurrentInstant(),
|
||||
Profile = new AccountProfile
|
||||
{
|
||||
FirstName = request.Profile?.FirstName,
|
||||
LastName = request.Profile?.LastName,
|
||||
// Initialize other profile fields as needed
|
||||
}
|
||||
};
|
||||
|
||||
// Add to database
|
||||
_db.Accounts.Add(account);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Created new account with ID {AccountId}", account.Id);
|
||||
return ToProtoAccount(account);
|
||||
}
|
||||
|
||||
public override async Task<Shared.Proto.Account> UpdateAccount(UpdateAccountRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var account = await _db.Accounts.FindAsync(accountId);
|
||||
if (account == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
||||
|
||||
// Update fields if they are provided in the request
|
||||
if (request.Name != null) account.Name = request.Name;
|
||||
if (request.Nick != null) account.Nick = request.Nick;
|
||||
if (request.Language != null) account.Language = request.Language;
|
||||
if (request.IsSuperuser != null) account.IsSuperuser = request.IsSuperuser.Value;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return ToProtoAccount(account);
|
||||
}
|
||||
|
||||
public override async Task<Empty> DeleteAccount(DeleteAccountRequest request, ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var account = await _db.Accounts.FindAsync(accountId);
|
||||
if (account == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
||||
|
||||
_db.Accounts.Remove(account);
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<ListAccountsResponse> ListAccounts(ListAccountsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var query = _db.Accounts.AsNoTracking();
|
||||
|
||||
// Apply filters if provided
|
||||
if (!string.IsNullOrEmpty(request.Filter))
|
||||
{
|
||||
// Implement filtering logic based on request.Filter
|
||||
// This is a simplified example
|
||||
query = query.Where(a => a.Name.Contains(request.Filter) || a.Nick.Contains(request.Filter));
|
||||
}
|
||||
|
||||
// Apply ordering
|
||||
query = request.OrderBy switch
|
||||
{
|
||||
"name" => query.OrderBy(a => a.Name),
|
||||
"name_desc" => query.OrderByDescending(a => a.Name),
|
||||
_ => query.OrderBy(a => a.Id)
|
||||
};
|
||||
|
||||
// Get total count for pagination
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
// Apply pagination
|
||||
var accounts = await query
|
||||
.Skip(request.PageSize * (request.PageToken != null ? int.Parse(request.PageToken) : 0))
|
||||
.Take(request.PageSize)
|
||||
.Include(a => a.Profile)
|
||||
.ToListAsync();
|
||||
|
||||
var response = new ListAccountsResponse
|
||||
{
|
||||
TotalSize = totalCount,
|
||||
NextPageToken = (accounts.Count == request.PageSize)
|
||||
? ((request.PageToken != null ? int.Parse(request.PageToken) : 0) + 1).ToString()
|
||||
: ""
|
||||
};
|
||||
|
||||
response.Accounts.AddRange(accounts.Select(ToProtoAccount));
|
||||
return response;
|
||||
}
|
||||
|
||||
// Implement other service methods following the same pattern...
|
||||
|
||||
// Profile operations
|
||||
public override async Task<Shared.Proto.AccountProfile> GetProfile(GetProfileRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var profile = await _db.AccountProfiles
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
||||
|
||||
if (profile == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
|
||||
$"Profile for account {request.AccountId} not found"));
|
||||
|
||||
return ToProtoProfile(profile);
|
||||
}
|
||||
|
||||
public override async Task<Shared.Proto.AccountProfile> UpdateProfile(UpdateProfileRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var profile = await _db.AccountProfiles
|
||||
.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
||||
|
||||
if (profile == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
|
||||
$"Profile for account {request.AccountId} not found"));
|
||||
|
||||
// Update only the fields specified in the field mask
|
||||
if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("first_name"))
|
||||
profile.FirstName = request.Profile.FirstName;
|
||||
|
||||
if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("last_name"))
|
||||
profile.LastName = request.Profile.LastName;
|
||||
|
||||
// Update other fields similarly...
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return ToProtoProfile(profile);
|
||||
}
|
||||
|
||||
// Contact operations
|
||||
public override async Task<Shared.Proto.AccountContact> AddContact(AddContactRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var contact = new AccountContact
|
||||
{
|
||||
AccountId = accountId,
|
||||
Type = (AccountContactType)request.Type,
|
||||
Content = request.Content,
|
||||
IsPrimary = request.IsPrimary,
|
||||
VerifiedAt = null
|
||||
};
|
||||
|
||||
_db.AccountContacts.Add(contact);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return ToProtoContact(contact);
|
||||
}
|
||||
|
||||
// Implement other contact operations...
|
||||
|
||||
// Badge operations
|
||||
public override async Task<Shared.Proto.AccountBadge> AddBadge(AddBadgeRequest request, ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var badge = new AccountBadge
|
||||
{
|
||||
AccountId = accountId,
|
||||
Type = request.Type,
|
||||
Label = request.Label,
|
||||
Caption = request.Caption,
|
||||
ActivatedAt = _clock.GetCurrentInstant(),
|
||||
ExpiredAt = request.ExpiredAt?.ToInstant(),
|
||||
Meta = request.Meta.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value)
|
||||
};
|
||||
|
||||
_db.Badges.Add(badge);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return ToProtoBadge(badge);
|
||||
}
|
||||
|
||||
// Implement other badge operations...
|
||||
}
|
@@ -25,7 +25,7 @@ public class AppDatabase(
|
||||
public DbSet<MagicSpell> MagicSpells { get; set; }
|
||||
public DbSet<Account.Account> Accounts { get; set; }
|
||||
public DbSet<AccountConnection> AccountConnections { get; set; }
|
||||
public DbSet<Profile> AccountProfiles { get; set; }
|
||||
public DbSet<AccountProfile> AccountProfiles { get; set; }
|
||||
public DbSet<AccountContact> AccountContacts { get; set; }
|
||||
public DbSet<AccountAuthFactor> AccountAuthFactors { get; set; }
|
||||
public DbSet<Relationship> AccountRelationships { get; set; }
|
||||
|
@@ -7,9 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
|
||||
<PackageReference Include="NodaTime" Version="3.2.2"/>
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0"/>
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0"/>
|
||||
|
@@ -1,6 +0,0 @@
|
||||
@DysonNetwork.Pass_HostAddress = http://localhost:5216
|
||||
|
||||
GET {{DysonNetwork.Pass_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
@@ -1,4 +1,5 @@
|
||||
using DysonNetwork.Pass;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Startup;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -37,4 +38,7 @@ using (var scope = app.Services.CreateScope())
|
||||
// Configure application middleware pipeline
|
||||
app.ConfigureAppMiddleware(builder.Configuration);
|
||||
|
||||
// Configure gRPC
|
||||
app.ConfigureGrpcServices();
|
||||
|
||||
app.Run();
|
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Prometheus;
|
||||
@@ -63,4 +64,11 @@ public static class ApplicationConfiguration
|
||||
|
||||
app.UseForwardedHeaders(forwardedHeadersOptions);
|
||||
}
|
||||
|
||||
public static WebApplication ConfigureGrpcServices(this WebApplication app)
|
||||
{
|
||||
app.MapGrpcService<AccountServiceGrpc>();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,20 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true; // Will be adjusted in Program.cs
|
||||
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
|
||||
// Register gRPC reflection for service discovery
|
||||
services.AddGrpc();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddScoped<AccountServiceGrpc>();
|
||||
|
||||
// Register OIDC services
|
||||
services.AddScoped<OidcService, GoogleOidcService>();
|
||||
services.AddScoped<OidcService, AppleOidcService>();
|
||||
|
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,129 @@
|
||||
{
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5071",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
"Bearer": {
|
||||
"ValidAudiences": [
|
||||
"http://localhost:5071",
|
||||
"https://localhost:7099"
|
||||
],
|
||||
"ValidIssuer": "solar-network"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AuthToken": {
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem"
|
||||
},
|
||||
"OidcProvider": {
|
||||
"IssuerUri": "https://nt.solian.app",
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem",
|
||||
"AccessTokenLifetime": "01:00:00",
|
||||
"RefreshTokenLifetime": "30.00:00:00",
|
||||
"AuthorizationCodeLifetime": "00:30:00",
|
||||
"RequireHttpsMetadata": true
|
||||
},
|
||||
"Tus": {
|
||||
"StorePath": "Uploads"
|
||||
},
|
||||
"Storage": {
|
||||
"PreferredRemote": "minio",
|
||||
"Remote": [
|
||||
{
|
||||
"Id": "minio",
|
||||
"Label": "Minio",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network-development",
|
||||
"Endpoint": "localhost:9000",
|
||||
"SecretId": "littlesheep",
|
||||
"SecretKey": "password",
|
||||
"EnabledSigned": true,
|
||||
"EnableSsl": false
|
||||
},
|
||||
{
|
||||
"Id": "cloudflare",
|
||||
"Label": "Cloudflare R2",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network",
|
||||
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
|
||||
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
|
||||
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
|
||||
"EnableSigned": true,
|
||||
"EnableSsl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"Captcha": {
|
||||
"Provider": "cloudflare",
|
||||
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
|
||||
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
|
||||
},
|
||||
"Notifications": {
|
||||
"Topic": "dev.solsynth.solian",
|
||||
"Endpoint": "http://localhost:8088"
|
||||
},
|
||||
"Email": {
|
||||
"Server": "smtp4dev.orb.local",
|
||||
"Port": 25,
|
||||
"UseSsl": false,
|
||||
"Username": "no-reply@mail.solsynth.dev",
|
||||
"Password": "password",
|
||||
"FromAddress": "no-reply@mail.solsynth.dev",
|
||||
"FromName": "Alphabot",
|
||||
"SubjectPrefix": "Solar Network"
|
||||
},
|
||||
"RealtimeChat": {
|
||||
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
|
||||
"ApiKey": "APIs6TiL8wj3A4j",
|
||||
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
|
||||
},
|
||||
"GeoIp": {
|
||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||
},
|
||||
"Oidc": {
|
||||
"Google": {
|
||||
"ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Apple": {
|
||||
"ClientId": "dev.solsynth.solian",
|
||||
"TeamId": "W7HPZ53V6B",
|
||||
"KeyId": "B668YP4KBG",
|
||||
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "YOUR_MICROSOFT_CLIENT_ID",
|
||||
"ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET",
|
||||
"DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT"
|
||||
}
|
||||
},
|
||||
"Payment": {
|
||||
"Auth": {
|
||||
"Afdian": "<token here>"
|
||||
},
|
||||
"Subscriptions": {
|
||||
"Afdian": {
|
||||
"7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary",
|
||||
"7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova",
|
||||
"141713ee3d6211f085b352540025c377": "solian.stellar.supernova"
|
||||
}
|
||||
}
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user