generated from NetCoreTemplates/blazor-vue
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for CDN backed User Avatars
- Loading branch information
Showing
18 changed files
with
513 additions
and
252 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using MyApp.ServiceModel; | ||
using ServiceStack; | ||
using ServiceStack.IO; | ||
|
||
namespace MyApp.ServiceInterface; | ||
|
||
public class BackgroundMqServices(R2VirtualFiles r2) : Service | ||
{ | ||
public async Task Any(DiskTasks request) | ||
{ | ||
if (request.SaveFile != null) | ||
{ | ||
await r2.WriteFileAsync(request.SaveFile.FilePath, request.SaveFile.Stream); | ||
} | ||
|
||
if (request.CdnDeleteFiles != null) | ||
{ | ||
r2.DeleteFiles(request.CdnDeleteFiles); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using MyApp.ServiceModel; | ||
using ServiceStack; | ||
using ServiceStack.IO; | ||
|
||
namespace MyApp.ServiceInterface; | ||
|
||
public class CdnServices(R2VirtualFiles r2) : Service | ||
{ | ||
public object Any(DeleteCdnFilesMq request) | ||
{ | ||
var msg = new DiskTasks | ||
{ | ||
CdnDeleteFiles = request.Files | ||
}; | ||
PublishMessage(msg); | ||
return msg; | ||
} | ||
|
||
public void Any(DeleteCdnFile request) | ||
{ | ||
r2.DeleteFile(request.File); | ||
} | ||
|
||
public object Any(GetCdnFile request) | ||
{ | ||
var file = r2.GetFile(request.File); | ||
if (file == null) | ||
throw new FileNotFoundException(request.File); | ||
return new HttpResult(file); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using ServiceStack; | ||
|
||
namespace MyApp.Data; | ||
|
||
public static class Urls | ||
{ | ||
public static string GetAvatarUrl(this string? userName) => userName != null | ||
? $"/avatar/{userName}" | ||
: Svg.ToDataUri(Svg.GetImage(Svg.Icons.Users)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using MyApp.ServiceModel; | ||
using ServiceStack; | ||
using ServiceStack.Host; | ||
using ServiceStack.Web; | ||
using SixLabors.ImageSharp; | ||
using SixLabors.ImageSharp.Formats; | ||
using SixLabors.ImageSharp.Formats.Png; | ||
using SixLabors.ImageSharp.Processing; | ||
|
||
namespace MyApp.ServiceInterface; | ||
|
||
public static class ImageUtils | ||
{ | ||
public const int MaxAvatarSize = 1024 * 1024; | ||
|
||
public static async Task<MemoryStream> CropAndResizeAsync(Stream inStream, int width, int height, IImageFormat format) | ||
{ | ||
var outStream = new MemoryStream(); | ||
using (var image = await Image.LoadAsync(inStream)) | ||
{ | ||
var clone = image.Clone(context => context | ||
.Resize(new ResizeOptions { | ||
Mode = ResizeMode.Crop, | ||
Size = new Size(width, height), | ||
})); | ||
await clone.SaveAsync(outStream, format); | ||
} | ||
outStream.Position = 0; | ||
return outStream; | ||
} | ||
|
||
public static async Task<IHttpFile?> TransformAvatarAsync(FilesUploadContext ctx) | ||
{ | ||
var originalMs = await ctx.File.InputStream.CopyToNewMemoryStreamAsync(); | ||
|
||
var resizedMs = await CropAndResizeAsync(originalMs, 128, 128, PngFormat.Instance); | ||
|
||
// Offload persistence of original image to background task | ||
originalMs.Position = 0; | ||
using var mqClient = HostContext.AppHost.GetMessageProducer(ctx.Request); | ||
mqClient.Publish(new DiskTasks | ||
{ | ||
SaveFile = new() | ||
{ | ||
FilePath = ctx.Location.ResolvePath(ctx), | ||
Stream = originalMs, | ||
} | ||
}); | ||
|
||
return new HttpFile(ctx.File) | ||
{ | ||
FileName = $"{ctx.FileName.LastLeftPart('.')}_128.{ctx.File.FileName.LastRightPart('.')}", | ||
ContentLength = resizedMs.Length, | ||
InputStream = resizedMs, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using MyApp.Data; | ||
using ServiceStack; | ||
using MyApp.ServiceModel; | ||
using ServiceStack.IO; | ||
using ServiceStack.OrmLite; | ||
using SixLabors.ImageSharp.Formats.Png; | ||
|
||
namespace MyApp.ServiceInterface; | ||
|
||
public class UserServices(R2VirtualFiles r2) : Service | ||
{ | ||
private const string AppData = "/App_Data"; | ||
|
||
public async Task<object> Any(UpdateUserProfile request) | ||
{ | ||
var userName = Request.GetClaimsPrincipal().Identity!.Name!; | ||
var file = base.Request!.Files.FirstOrDefault(); | ||
|
||
if (file != null) | ||
{ | ||
var userProfileDir = $"/profiles/{userName[..2]}/{userName}"; | ||
var origPath = userProfileDir.CombineWith(file.FileName); | ||
var fileName = $"{file.FileName.LastLeftPart('.')}_128.{file.FileName.LastRightPart('.')}"; | ||
var profilePath = userProfileDir.CombineWith(fileName); | ||
var originalMs = await file.InputStream.CopyToNewMemoryStreamAsync(); | ||
var resizedMs = await ImageUtils.CropAndResizeAsync(originalMs, 128, 128, PngFormat.Instance); | ||
|
||
await VirtualFiles.WriteFileAsync(AppData.CombineWith(origPath), originalMs); | ||
await VirtualFiles.WriteFileAsync(AppData.CombineWith(profilePath), resizedMs); | ||
|
||
await Db.UpdateOnlyAsync(() => new ApplicationUser { | ||
ProfilePath = profilePath, | ||
}, x => x.UserName == userName); | ||
|
||
PublishMessage(new DiskTasks { | ||
SaveFile = new() { | ||
FilePath = origPath, | ||
Stream = originalMs, | ||
} | ||
}); | ||
PublishMessage(new DiskTasks { | ||
SaveFile = new() { | ||
FilePath = profilePath, | ||
Stream = resizedMs, | ||
} | ||
}); | ||
} | ||
|
||
return new UpdateUserProfileResponse(); | ||
} | ||
|
||
public async Task<object> Any(GetUserAvatar request) | ||
{ | ||
if (!string.IsNullOrEmpty(request.UserName)) | ||
{ | ||
var profilePath = Db.Scalar<string>(Db.From<ApplicationUser>() | ||
.Where(x => x.UserName == request.UserName) | ||
.Select(x => x.ProfilePath)); | ||
if (!string.IsNullOrEmpty(profilePath)) | ||
{ | ||
if (profilePath.StartsWith("data:")) | ||
{ | ||
return new HttpResult(profilePath, MimeTypes.ImageSvg); | ||
} | ||
if (profilePath.StartsWith('/')) | ||
{ | ||
var localProfilePath = AppData.CombineWith(profilePath); | ||
var file = VirtualFiles.GetFile(localProfilePath); | ||
if (file != null) | ||
{ | ||
return new HttpResult(file, MimeTypes.GetMimeType(file.Extension)); | ||
} | ||
file = r2.GetFile(profilePath); | ||
var bytes = file != null ? await file.ReadAllBytesAsync() : null; | ||
if (bytes is { Length: > 0 }) | ||
{ | ||
await VirtualFiles.WriteFileAsync(localProfilePath, bytes); | ||
return new HttpResult(bytes, MimeTypes.GetMimeType(file!.Extension)); | ||
} | ||
} | ||
} | ||
} | ||
return new HttpResult(Svg.GetImage(Svg.Icons.Users), MimeTypes.ImageSvg); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using ServiceStack; | ||
using ServiceStack.DataAnnotations; | ||
|
||
namespace MyApp.ServiceModel; | ||
|
||
[Tag(Tag.Tasks)] | ||
[ExcludeMetadata] | ||
[Restrict(InternalOnly = true)] | ||
public class DiskTasks : IReturnVoid | ||
{ | ||
public SaveFile? SaveFile { get; set; } | ||
public List<string>? CdnDeleteFiles { get; set; } | ||
} | ||
public class SaveFile | ||
{ | ||
public string FilePath { get; set; } | ||
public Stream Stream { get; set; } | ||
} | ||
|
||
[Tag(Tag.Tasks)] | ||
[ValidateHasRole(Roles.Moderator)] | ||
public class DeleteCdnFilesMq | ||
{ | ||
public List<string> Files { get; set; } | ||
} | ||
|
||
[Tag(Tag.Tasks)] | ||
[ValidateHasRole(Roles.Moderator)] | ||
public class GetCdnFile | ||
{ | ||
public string File { get; set; } | ||
} | ||
|
||
[Tag(Tag.Tasks)] | ||
[ValidateHasRole(Roles.Moderator)] | ||
public class DeleteCdnFile : IReturnVoid | ||
{ | ||
public string File { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace MyApp.ServiceModel; | ||
|
||
public static class Tag | ||
{ | ||
public const string Tasks = nameof(Tasks); | ||
public const string User = nameof(User); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using ServiceStack; | ||
|
||
namespace MyApp.ServiceModel; | ||
|
||
[Tag(Tag.User)] | ||
[ValidateIsAuthenticated] | ||
public class UpdateUserProfile : IPost, IReturn<UpdateUserProfileResponse> | ||
{ | ||
} | ||
|
||
public class UpdateUserProfileResponse | ||
{ | ||
public ResponseStatus ResponseStatus { get; set; } | ||
Check warning on line 13 in MyApp.ServiceModel/User.cs GitHub Actions / build
|
||
} | ||
|
||
[Route("/avatar/{UserName}", "GET")] | ||
public class GetUserAvatar : IGet, IReturn<byte[]> | ||
{ | ||
public string UserName { get; set; } | ||
Check warning on line 19 in MyApp.ServiceModel/User.cs GitHub Actions / build
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.