Add avatar support
This commit is contained in:
47
src/API/Controllers/Avatar/V1/AvatarController.cs
Normal file
47
src/API/Controllers/Avatar/V1/AvatarController.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecNet.Application.Common.Security;
|
||||||
|
using RecNet.Application.Profiles;
|
||||||
|
using AvatarEntity = RecNet.Domain.Profiles.Avatar;
|
||||||
|
|
||||||
|
namespace API.Controllers.Avatar.V1;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]/v1")]
|
||||||
|
public class AvatarController(IProfileService profileService) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<AvatarDTO>> GetAvatar(
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var profileId = User.GetProfileId();
|
||||||
|
|
||||||
|
var avatar = await profileService.GetAvatarAsync(profileId, ct);
|
||||||
|
if (avatar == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("set")]
|
||||||
|
public async Task<ActionResult<AvatarDTO>> UpdateAvatar(
|
||||||
|
[FromBody] AvatarDTO request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var profileId = User.GetProfileId();
|
||||||
|
|
||||||
|
var avatar = await profileService.UpdateAvatarAsync(
|
||||||
|
profileId,
|
||||||
|
new UpdateAvatarCommand(
|
||||||
|
request.OutfitSelections,
|
||||||
|
request.SkinColor,
|
||||||
|
request.HairColor),
|
||||||
|
ct);
|
||||||
|
if (avatar == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,4 +50,4 @@ public class ProfilesController(IProfileService profileService) : ControllerBase
|
|||||||
ExpiresIn = result.ExpiresIn
|
ExpiresIn = result.ExpiresIn
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ public class ApplicationMappingProfile : Profile
|
|||||||
public ApplicationMappingProfile()
|
public ApplicationMappingProfile()
|
||||||
{
|
{
|
||||||
CreateMap<Domain.Profiles.Profile, ProfileDTO>();
|
CreateMap<Domain.Profiles.Profile, ProfileDTO>();
|
||||||
|
CreateMap<Domain.Profiles.Avatar, AvatarDTO>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/RecNet.Application/Profiles/AvatarDto.cs
Normal file
15
src/RecNet.Application/Profiles/AvatarDto.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecNet.Application.Profiles;
|
||||||
|
|
||||||
|
public class AvatarDTO
|
||||||
|
{
|
||||||
|
[JsonPropertyName("OutfitSelections")]
|
||||||
|
public required string OutfitSelections { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("SkinColor")]
|
||||||
|
public required string SkinColor { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("HairColor")]
|
||||||
|
public required string HairColor { get; init; }
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ namespace RecNet.Application.Profiles;
|
|||||||
public interface IProfileService
|
public interface IProfileService
|
||||||
{
|
{
|
||||||
Task<ProfileDTO?> GetProfileAsync(Guid profileId, CancellationToken ct = default);
|
Task<ProfileDTO?> GetProfileAsync(Guid profileId, CancellationToken ct = default);
|
||||||
|
Task<AvatarDTO?> GetAvatarAsync(Guid profileId, CancellationToken ct = default);
|
||||||
|
Task<AvatarDTO?> UpdateAvatarAsync(Guid profileId, UpdateAvatarCommand command, CancellationToken ct = default);
|
||||||
Task<IReadOnlyList<ProfileDTO>> GetProfilesAsync(List<Guid> profileIds, CancellationToken ct = default);
|
Task<IReadOnlyList<ProfileDTO>> GetProfilesAsync(List<Guid> profileIds, CancellationToken ct = default);
|
||||||
Task<LoginProfileResult> LoginAsync(LoginProfileCommand command, CancellationToken ct = default);
|
Task<LoginProfileResult> LoginAsync(LoginProfileCommand command, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,37 @@ public class ProfileService(
|
|||||||
: mapper.Map<ProfileDTO>(profile);
|
: mapper.Map<ProfileDTO>(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AvatarDTO?> GetAvatarAsync(Guid profileId, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var avatar = await profileRepository.GetAvatarByProfileIdAsync(profileId, ct);
|
||||||
|
|
||||||
|
return avatar is null
|
||||||
|
? null
|
||||||
|
: mapper.Map<AvatarDTO>(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AvatarDTO?> UpdateAvatarAsync(
|
||||||
|
Guid profileId,
|
||||||
|
UpdateAvatarCommand command,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var profile = await profileRepository.GetByIdAsync(profileId, ct);
|
||||||
|
if (profile is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
profile.SetAvatar(
|
||||||
|
Avatar.Create(
|
||||||
|
command.OutfitSelections,
|
||||||
|
command.SkinColor,
|
||||||
|
command.HairColor
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await profileRepository.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
return mapper.Map<AvatarDTO>(profile.Avatar);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<ProfileDTO>> GetProfilesAsync(
|
public async Task<IReadOnlyList<ProfileDTO>> GetProfilesAsync(
|
||||||
List<Guid> profileIds,
|
List<Guid> profileIds,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
@@ -44,21 +75,21 @@ public class ProfileService(
|
|||||||
var isValidGameVersion = await gameVersionRepository.IsValidAsync(command.AppVersion, ct);
|
var isValidGameVersion = await gameVersionRepository.IsValidAsync(command.AppVersion, ct);
|
||||||
if (!isValidGameVersion)
|
if (!isValidGameVersion)
|
||||||
return LoginProfileResult.Fail("Invalid app version.");
|
return LoginProfileResult.Fail("Invalid app version.");
|
||||||
|
|
||||||
// 2. Validate Platform Authentication
|
// 2. Validate Platform Authentication
|
||||||
var validator = validators.FirstOrDefault(x => x.PlatformType == command.PlatformType);
|
var validator = validators.FirstOrDefault(x => x.PlatformType == command.PlatformType);
|
||||||
if (validator is null)
|
if (validator is null)
|
||||||
return LoginProfileResult.Fail("Invalid platform type");
|
return LoginProfileResult.Fail("Invalid platform type");
|
||||||
|
|
||||||
var ignoreAuthValidation = await configService.GetAsync("Profiles:IgnoreAuthValidation", true, ct);
|
var ignoreAuthValidation = await configService.GetAsync("Profiles:IgnoreAuthValidation", true, ct);
|
||||||
|
|
||||||
var auth = await validator.ValidateAsync(
|
var auth = await validator.ValidateAsync(
|
||||||
command.PlatformAuthentication,
|
command.PlatformAuthentication,
|
||||||
command.PlatformId,
|
command.PlatformId,
|
||||||
ct);
|
ct);
|
||||||
if (!auth.Succeeded && !ignoreAuthValidation)
|
if (!auth.Succeeded && !ignoreAuthValidation)
|
||||||
return LoginProfileResult.Fail($"Platform Auth Failed: {auth.Message}");
|
return LoginProfileResult.Fail($"Platform Auth Failed: {auth.Message}");
|
||||||
|
|
||||||
// 3. Get or Create Profile
|
// 3. Get or Create Profile
|
||||||
var profile = await profileRepository.GetByPlatform(
|
var profile = await profileRepository.GetByPlatform(
|
||||||
command.PlatformType,
|
command.PlatformType,
|
||||||
@@ -68,7 +99,7 @@ public class ProfileService(
|
|||||||
if (profile is null)
|
if (profile is null)
|
||||||
{
|
{
|
||||||
var name = await nameGenerator.GenerateAsync(ct);
|
var name = await nameGenerator.GenerateAsync(ct);
|
||||||
|
|
||||||
profile = ProfileEntity.Create(
|
profile = ProfileEntity.Create(
|
||||||
name,
|
name,
|
||||||
command.PlatformType,
|
command.PlatformType,
|
||||||
@@ -76,7 +107,7 @@ public class ProfileService(
|
|||||||
|
|
||||||
await profileRepository.AddAsync(profile, ct);
|
await profileRepository.AddAsync(profile, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile.IsBanned)
|
if (profile.IsBanned)
|
||||||
return LoginProfileResult.Fail("Profile is banned");
|
return LoginProfileResult.Fail("Profile is banned");
|
||||||
|
|
||||||
|
|||||||
6
src/RecNet.Application/Profiles/UpdateAvatarCommand.cs
Normal file
6
src/RecNet.Application/Profiles/UpdateAvatarCommand.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace RecNet.Application.Profiles;
|
||||||
|
|
||||||
|
public sealed record UpdateAvatarCommand(
|
||||||
|
string? OutfitSelections,
|
||||||
|
string? SkinColor,
|
||||||
|
string? HairColor);
|
||||||
23
src/RecNet.Domain/Profiles/Avatar.cs
Normal file
23
src/RecNet.Domain/Profiles/Avatar.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace RecNet.Domain.Profiles;
|
||||||
|
|
||||||
|
public class Avatar
|
||||||
|
{
|
||||||
|
private Avatar() { }
|
||||||
|
|
||||||
|
public string OutfitSelections { get; private set; } = string.Empty;
|
||||||
|
public string SkinColor { get; private set; } = string.Empty;
|
||||||
|
public string HairColor { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public static Avatar Empty => new();
|
||||||
|
|
||||||
|
public static Avatar Create(
|
||||||
|
string? outfitSelections,
|
||||||
|
string? skinColor,
|
||||||
|
string? hairColor)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
OutfitSelections = outfitSelections ?? string.Empty,
|
||||||
|
SkinColor = skinColor ?? string.Empty,
|
||||||
|
HairColor = hairColor ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace RecNet.Domain.Profiles;
|
|||||||
public interface IProfileRepository
|
public interface IProfileRepository
|
||||||
{
|
{
|
||||||
Task<Profile?> GetByIdAsync(Guid profileId, CancellationToken ct = default);
|
Task<Profile?> GetByIdAsync(Guid profileId, CancellationToken ct = default);
|
||||||
|
Task<Avatar?> GetAvatarByProfileIdAsync(Guid profileId, CancellationToken ct = default);
|
||||||
Task<IReadOnlyList<Profile>> GetByIdsAsync(List<Guid> profileIds, CancellationToken ct = default);
|
Task<IReadOnlyList<Profile>> GetByIdsAsync(List<Guid> profileIds, CancellationToken ct = default);
|
||||||
|
|
||||||
Task<Profile?> GetByPlatform(PlatformType platform, string platformId, CancellationToken ct = default);
|
Task<Profile?> GetByPlatform(PlatformType platform, string platformId, CancellationToken ct = default);
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public class Profile
|
|||||||
|
|
||||||
public DateTimeOffset CreatedAt { get; private set; }
|
public DateTimeOffset CreatedAt { get; private set; }
|
||||||
|
|
||||||
|
public Avatar Avatar { get; private set; } = Avatar.Empty;
|
||||||
|
|
||||||
public static Profile Create(
|
public static Profile Create(
|
||||||
string name,
|
string name,
|
||||||
PlatformType platform,
|
PlatformType platform,
|
||||||
@@ -81,6 +83,9 @@ public class Profile
|
|||||||
public void RevokeModerator()
|
public void RevokeModerator()
|
||||||
=> IsModerator = false;
|
=> IsModerator = false;
|
||||||
|
|
||||||
|
public void SetAvatar(Avatar avatar)
|
||||||
|
=> Avatar = avatar ?? throw new DomainException("Avatar is required.");
|
||||||
|
|
||||||
private static string RequireValue(string value, string parameterName)
|
private static string RequireValue(string value, string parameterName)
|
||||||
=> !string.IsNullOrWhiteSpace(value) ? value : throw new DomainException($"{parameterName} is required.");
|
=> !string.IsNullOrWhiteSpace(value) ? value : throw new DomainException($"{parameterName} is required.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ public class ProfileConfiguration : IEntityTypeConfiguration<Profile>
|
|||||||
|
|
||||||
builder.Property(x => x.CreatedAt)
|
builder.Property(x => x.CreatedAt)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.OwnsOne(x => x.Avatar);
|
||||||
|
|
||||||
builder.HasIndex(x => new { x.Platform, x.PlatformId })
|
builder.HasIndex(x => new { x.Platform, x.PlatformId })
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|||||||
132
src/RecNet.Infrastructure/Persistence/Migrations/20260620201950_Avatars.Designer.cs
generated
Normal file
132
src/RecNet.Infrastructure/Persistence/Migrations/20260620201950_Avatars.Designer.cs
generated
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using RecNet.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DatabaseContext))]
|
||||||
|
[Migration("20260620201950_Avatars")]
|
||||||
|
partial class Avatars
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Configuration.ServerConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ServerConfigs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.GameVersions.GameVersion", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsValid")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("Version");
|
||||||
|
|
||||||
|
b.ToTable("GameVersions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Profiles.Profile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ProfileId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("DeviceIds")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<bool>("IsBanned")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("IsModerator")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<string>("Platform")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<string>("PlatformId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.HasKey("ProfileId");
|
||||||
|
|
||||||
|
b.HasIndex("Platform", "PlatformId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Profiles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Profiles.Profile", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("RecNet.Domain.Profiles.Avatar", "Avatar", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<Guid>("ProfileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b1.Property<string>("HairColor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("OutfitSelections")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("SkinColor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.HasKey("ProfileId");
|
||||||
|
|
||||||
|
b1.ToTable("Profiles");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ProfileId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Avatar")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Avatars : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Avatar_HairColor",
|
||||||
|
table: "Profiles",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Avatar_OutfitSelections",
|
||||||
|
table: "Profiles",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Avatar_SkinColor",
|
||||||
|
table: "Profiles",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Avatar_HairColor",
|
||||||
|
table: "Profiles");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Avatar_OutfitSelections",
|
||||||
|
table: "Profiles");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Avatar_SkinColor",
|
||||||
|
table: "Profiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,6 +92,37 @@ namespace RecNet.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
b.ToTable("Profiles");
|
b.ToTable("Profiles");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Profiles.Profile", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("RecNet.Domain.Profiles.Avatar", "Avatar", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<Guid>("ProfileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b1.Property<string>("HairColor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("OutfitSelections")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("SkinColor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.HasKey("ProfileId");
|
||||||
|
|
||||||
|
b1.ToTable("Profiles");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ProfileId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Avatar")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ public class ProfileRepository(DatabaseContext dbContext) : IProfileRepository
|
|||||||
=> dbContext.Profiles
|
=> dbContext.Profiles
|
||||||
.FirstOrDefaultAsync(x => x.ProfileId == profileId, ct);
|
.FirstOrDefaultAsync(x => x.ProfileId == profileId, ct);
|
||||||
|
|
||||||
|
public Task<Avatar?> GetAvatarByProfileIdAsync(
|
||||||
|
Guid profileId,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
=> dbContext.Profiles
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.ProfileId == profileId)
|
||||||
|
.Select(x => x.Avatar)
|
||||||
|
.FirstOrDefaultAsync(ct);
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Profile>> GetByIdsAsync(
|
public async Task<IReadOnlyList<Profile>> GetByIdsAsync(
|
||||||
List<Guid> profileIds,
|
List<Guid> profileIds,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
@@ -36,4 +45,4 @@ public class ProfileRepository(DatabaseContext dbContext) : IProfileRepository
|
|||||||
|
|
||||||
public Task SaveChangesAsync(CancellationToken ct = default)
|
public Task SaveChangesAsync(CancellationToken ct = default)
|
||||||
=> dbContext.SaveChangesAsync(ct);
|
=> dbContext.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user