diff --git a/DysonNetwork.Control/AppHost.cs b/DysonNetwork.Control/AppHost.cs index c18b942..4487c48 100644 --- a/DysonNetwork.Control/AppHost.cs +++ b/DysonNetwork.Control/AppHost.cs @@ -33,6 +33,8 @@ var developService = builder.AddProject("develop" .WithReference(ringService) .WithReference(sphereService); +passService.WithReference(developService).WithReference(driveService); + List> services = [ringService, passService, driveService, sphereService, developService]; diff --git a/DysonNetwork.Control/DysonNetwork.Control.csproj b/DysonNetwork.Control/DysonNetwork.Control.csproj index f8a51c4..5cbc295 100644 --- a/DysonNetwork.Control/DysonNetwork.Control.csproj +++ b/DysonNetwork.Control/DysonNetwork.Control.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,10 +12,10 @@ - + - - + + diff --git a/DysonNetwork.Drive/Storage/FileUploadController.cs b/DysonNetwork.Drive/Storage/FileUploadController.cs index 2004e98..367c418 100644 --- a/DysonNetwork.Drive/Storage/FileUploadController.cs +++ b/DysonNetwork.Drive/Storage/FileUploadController.cs @@ -3,6 +3,7 @@ using System.Text.Json; using DysonNetwork.Drive.Billing; using DysonNetwork.Drive.Storage.Model; using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -31,15 +32,18 @@ public class FileUploadController( [HttpPost("create")] public async Task CreateUploadTask([FromBody] CreateUploadTaskRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) + { + return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 }; + } if (!currentUser.IsSuperuser) { var allowed = await permission.HasPermissionAsync(new HasPermissionRequest - { Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" }); + { Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" }); if (!allowed.HasPermission) { - return Forbid(); + return new ObjectResult(ApiError.Unauthorized(forbidden: true)) { StatusCode = 403 }; } } @@ -48,23 +52,19 @@ public class FileUploadController( var pool = await fileService.GetPoolAsync(request.PoolId.Value); if (pool is null) { - return BadRequest("Pool not found"); + return new ObjectResult(ApiError.NotFound("Pool")) { StatusCode = 404 }; } - if (pool.PolicyConfig.RequirePrivilege > 0) + if (pool.PolicyConfig.RequirePrivilege is > 0) { - if (currentUser.PerkSubscription is null) - { - return new ObjectResult("You need to have join the Stellar Program to use this pool") - { StatusCode = 403 }; - } - var privilege = + currentUser.PerkSubscription is null ? 0 : PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier); if (privilege < pool.PolicyConfig.RequirePrivilege) { - return new ObjectResult( - $"You need Stellar Program tier {pool.PolicyConfig.RequirePrivilege} to use this pool, you are tier {privilege}") + return new ObjectResult(ApiError.Unauthorized( + $"You need Stellar Program tier {pool.PolicyConfig.RequirePrivilege} to use pool {pool.Name}, you are tier {privilege}", + forbidden: true)) { StatusCode = 403 }; @@ -74,14 +74,19 @@ public class FileUploadController( var policy = pool.PolicyConfig; if (!policy.AllowEncryption && !string.IsNullOrEmpty(request.EncryptPassword)) { - return new ObjectResult("File encryption is not allowed in this pool") { StatusCode = 403 }; + return new ObjectResult(ApiError.Unauthorized("File encryption is not allowed in this pool", true)) + { StatusCode = 403 }; } if (policy.AcceptTypes is { Count: > 0 }) { if (string.IsNullOrEmpty(request.ContentType)) { - return BadRequest("Content type is required by the pool's policy"); + return new ObjectResult(ApiError.Validation(new Dictionary + { + { "contentType", new[] { "Content type is required by the pool's policy" } } + })) + { StatusCode = 400 }; } var foundMatch = policy.AcceptTypes.Any(acceptType => @@ -97,15 +102,18 @@ public class FileUploadController( if (!foundMatch) { - return new ObjectResult($"Content type {request.ContentType} is not allowed by the pool's policy") - { StatusCode = 403 }; + return new ObjectResult( + ApiError.Unauthorized($"Content type {request.ContentType} is not allowed by the pool's policy", + true)) + { StatusCode = 403 }; } } if (policy.MaxFileSize is not null && request.FileSize > policy.MaxFileSize) { - return new ObjectResult( - $"File size {request.FileSize} is larger than the pool's maximum file size {policy.MaxFileSize}") + return new ObjectResult(ApiError.Unauthorized( + $"File size {request.FileSize} is larger than the pool's maximum file size {policy.MaxFileSize}", + true)) { StatusCode = 403 }; @@ -118,8 +126,10 @@ public class FileUploadController( ); if (!ok) { - return new ObjectResult($"File size {billableUnit} MiB is exceeded the user's quota {quota} MiB") - { StatusCode = 403 }; + return new ObjectResult( + ApiError.Unauthorized($"File size {billableUnit} MiB is exceeded the user's quota {quota} MiB", + true)) + { StatusCode = 403 }; } if (!Directory.Exists(_tempPath)) @@ -170,7 +180,7 @@ public class FileUploadController( ChunksCount = chunksCount }); } - + public class UploadChunkRequest { [Required] @@ -186,7 +196,7 @@ public class FileUploadController( var taskPath = Path.Combine(_tempPath, taskId); if (!Directory.Exists(taskPath)) { - return NotFound("Upload task not found."); + return new ObjectResult(ApiError.NotFound("Upload task")) { StatusCode = 404 }; } var chunkPath = Path.Combine(taskPath, $"{chunkIndex}.chunk"); @@ -202,19 +212,20 @@ public class FileUploadController( var taskPath = Path.Combine(_tempPath, taskId); if (!Directory.Exists(taskPath)) { - return NotFound("Upload task not found."); + return new ObjectResult(ApiError.NotFound("Upload task")) { StatusCode = 404 }; } var taskJsonPath = Path.Combine(taskPath, "task.json"); if (!System.IO.File.Exists(taskJsonPath)) { - return NotFound("Upload task metadata not found."); + return new ObjectResult(ApiError.NotFound("Upload task metadata")) { StatusCode = 404 }; } var task = JsonSerializer.Deserialize(await System.IO.File.ReadAllTextAsync(taskJsonPath)); if (task == null) { - return BadRequest("Invalid task metadata."); + return new ObjectResult(new ApiError { Code = "BAD_REQUEST", Message = "Invalid task metadata.", Status = 400 }) + { StatusCode = 400 }; } var mergedFilePath = Path.Combine(_tempPath, taskId + ".tmp"); @@ -229,7 +240,9 @@ public class FileUploadController( mergedStream.Close(); System.IO.File.Delete(mergedFilePath); Directory.Delete(taskPath, true); - return BadRequest($"Chunk {i} is missing."); + return new ObjectResult(new ApiError + { Code = "CHUNK_MISSING", Message = $"Chunk {i} is missing.", Status = 400 }) + { StatusCode = 400 }; } await using var chunkStream = new FileStream(chunkPath, FileMode.Open); @@ -237,21 +250,24 @@ public class FileUploadController( } } - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) + { + return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 }; + } var fileId = await Nanoid.GenerateAsync(); var cloudFile = await fileService.ProcessNewFileAsync( - currentUser, - fileId, - task.PoolId.ToString(), - task.BundleId?.ToString(), - mergedFilePath, - task.FileName, - task.ContentType, - task.EncryptPassword, - task.ExpiredAt - ); + currentUser, + fileId, + task.PoolId.ToString(), + task.BundleId?.ToString(), + mergedFilePath, + task.FileName, + task.ContentType, + task.EncryptPassword, + task.ExpiredAt + ); // Clean up Directory.Delete(taskPath, true); @@ -259,4 +275,4 @@ public class FileUploadController( return Ok(cloudFile); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 066d1a3..2613200 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -1,7 +1,7 @@ { "Debug": true, - "BaseUrl": "http://localhost:5216", - "SiteUrl": "https://id.solian.app", + "BaseUrl": "http://localhost:5001", + "SiteUrl": "http://localhost:3000", "Logging": { "LogLevel": { "Default": "Information", @@ -15,10 +15,7 @@ "Authentication": { "Schemes": { "Bearer": { - "ValidAudiences": [ - "http://localhost:5071", - "https://localhost:7099" - ], + "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], "ValidIssuer": "solar-network" } } @@ -74,10 +71,7 @@ } } }, - "KnownProxies": [ - "127.0.0.1", - "::1" - ], + "KnownProxies": ["127.0.0.1", "::1"], "Service": { "Name": "DysonNetwork.Pass", "Url": "https://localhost:7058" diff --git a/DysonNetwork.Shared/Cache/CacheService.cs b/DysonNetwork.Shared/Cache/CacheService.cs index 1673018..8150115 100644 --- a/DysonNetwork.Shared/Cache/CacheService.cs +++ b/DysonNetwork.Shared/Cache/CacheService.cs @@ -316,9 +316,9 @@ public class CacheServiceRedis : ICacheService public async Task> GetGroupKeysAsync(string group) { if (string.IsNullOrEmpty(group)) - throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); + throw new ArgumentException("Group cannot be null or empty.", nameof(group)); - var groupKey = $"{GroupKeyPrefix}{group}"; + var groupKey = string.Concat(GroupKeyPrefix, group); var members = await _database.SetMembersAsync(groupKey); return members.Select(m => m.ToString()); @@ -396,4 +396,4 @@ public class CacheServiceRedis : ICacheService var result = await func(); return (true, result); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 95d0124..889a9dd 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -9,7 +9,7 @@ - + diff --git a/DysonNetwork.Shared/Models/FilePool.cs b/DysonNetwork.Shared/Models/FilePool.cs index d24122f..c050d02 100644 --- a/DysonNetwork.Shared/Models/FilePool.cs +++ b/DysonNetwork.Shared/Models/FilePool.cs @@ -35,7 +35,7 @@ public class PolicyConfig public bool AllowAnonymous { get; set; } = true; public List? AcceptTypes { get; set; } public long? MaxFileSize { get; set; } - public int RequirePrivilege { get; set; } = 0; + public int? RequirePrivilege { get; set; } = 0; } public class FilePool : ModelBase, IIdentifiedResource @@ -47,8 +47,8 @@ public class FilePool : ModelBase, IIdentifiedResource [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}"; -} \ No newline at end of file +} diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index af6a32b..3ff88a5 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -16,7 +16,7 @@ - + diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 7a6ea48..7ca8903 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -71,6 +71,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/settings/pass.json b/settings/pass.json index 0d1dd45..156da49 100644 --- a/settings/pass.json +++ b/settings/pass.json @@ -1,7 +1,7 @@ { "Debug": true, - "BaseUrl": "http://localhost:5216", - "SiteUrl": "https://id.solian.app", + "BaseUrl": "http://localhost:5001", + "SiteUrl": "http://localhost:3000", "Logging": { "LogLevel": { "Default": "Information",