feat: Project peready 2.0
This commit is contained in:
12
LctMonolith/Application/Options/S3StorageOptions.cs
Normal file
12
LctMonolith/Application/Options/S3StorageOptions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace LctMonolith.Application.Options;
|
||||
|
||||
public class S3StorageOptions
|
||||
{
|
||||
public string Endpoint { get; set; } = string.Empty;
|
||||
public bool UseSsl { get; set; } = true;
|
||||
public string AccessKey { get; set; } = string.Empty;
|
||||
public string SecretKey { get; set; } = string.Empty;
|
||||
public string Bucket { get; set; } = "avatars";
|
||||
public string? PublicBaseUrl { get; set; } // optional CDN / reverse proxy base
|
||||
public int PresignExpirationMinutes { get; set; } = 60;
|
||||
}
|
||||
67
LctMonolith/Controllers/ProfileController.cs
Normal file
67
LctMonolith/Controllers/ProfileController.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Security.Claims;
|
||||
using LctMonolith.Models.Database;
|
||||
using LctMonolith.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LctMonolith.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/profile")]
|
||||
[Authorize]
|
||||
public class ProfileController : ControllerBase
|
||||
{
|
||||
private readonly IProfileService _profiles;
|
||||
public ProfileController(IProfileService profiles) => _profiles = profiles;
|
||||
|
||||
private Guid CurrentUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
public class UpdateProfileDto
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public DateOnly? BirthDate { get; set; }
|
||||
public string? About { get; set; }
|
||||
public string? Location { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
public async Task<IActionResult> GetMe(CancellationToken ct)
|
||||
{
|
||||
var p = await _profiles.GetByUserIdAsync(CurrentUserId(), ct);
|
||||
if (p == null) return NotFound();
|
||||
return Ok(p);
|
||||
}
|
||||
|
||||
[HttpGet("{userId:guid}")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> GetByUser(Guid userId, CancellationToken ct)
|
||||
{
|
||||
var p = await _profiles.GetByUserIdAsync(userId, ct);
|
||||
return p == null ? NotFound() : Ok(p);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Upsert(UpdateProfileDto dto, CancellationToken ct)
|
||||
{
|
||||
var p = await _profiles.UpsertAsync(CurrentUserId(), dto.FirstName, dto.LastName, dto.BirthDate, dto.About, dto.Location, ct);
|
||||
return Ok(p);
|
||||
}
|
||||
|
||||
[HttpPost("avatar")]
|
||||
[RequestSizeLimit(7_000_000)] // ~7MB
|
||||
public async Task<IActionResult> UploadAvatar(IFormFile file, CancellationToken ct)
|
||||
{
|
||||
if (file == null || file.Length == 0) return BadRequest("File required");
|
||||
await using var stream = file.OpenReadStream();
|
||||
var p = await _profiles.UpdateAvatarAsync(CurrentUserId(), stream, file.ContentType, file.FileName, ct);
|
||||
return Ok(new { p.AvatarUrl });
|
||||
}
|
||||
|
||||
[HttpDelete("avatar")]
|
||||
public async Task<IActionResult> DeleteAvatar(CancellationToken ct)
|
||||
{
|
||||
var ok = await _profiles.DeleteAvatarAsync(CurrentUserId(), ct);
|
||||
return ok ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" />
|
||||
<!-- Updated to match transitive requirement (>=8.0.1) -->
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.400.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
21
LctMonolith/Models/Database/Profile.cs
Normal file
21
LctMonolith/Models/Database/Profile.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace LctMonolith.Models.Database;
|
||||
|
||||
public class Profile
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public AppUser User { get; set; } = null!;
|
||||
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public DateOnly? BirthDate { get; set; }
|
||||
public string? About { get; set; }
|
||||
public string? Location { get; set; }
|
||||
|
||||
// Avatar in S3 / MinIO
|
||||
public string? AvatarS3Key { get; set; }
|
||||
public string? AvatarUrl { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
8
LctMonolith/Services/Contracts/IFileStorageService.cs
Normal file
8
LctMonolith/Services/Contracts/IFileStorageService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace LctMonolith.Services.Interfaces;
|
||||
|
||||
public interface IFileStorageService
|
||||
{
|
||||
Task<string> UploadAsync(Stream content, string contentType, string keyPrefix, CancellationToken ct = default);
|
||||
Task DeleteAsync(string key, CancellationToken ct = default);
|
||||
Task<string> GetPresignedUrlAsync(string key, TimeSpan? expires = null, CancellationToken ct = default);
|
||||
}
|
||||
11
LctMonolith/Services/Contracts/IProfileService.cs
Normal file
11
LctMonolith/Services/Contracts/IProfileService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace LctMonolith.Services.Interfaces;
|
||||
|
||||
using LctMonolith.Models.Database;
|
||||
|
||||
public interface IProfileService
|
||||
{
|
||||
Task<Profile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default);
|
||||
Task<Profile> UpsertAsync(Guid userId, string? firstName, string? lastName, DateOnly? birthDate, string? about, string? location, CancellationToken ct = default);
|
||||
Task<Profile> UpdateAvatarAsync(Guid userId, Stream fileStream, string contentType, string? fileName, CancellationToken ct = default);
|
||||
Task<bool> DeleteAvatarAsync(Guid userId, CancellationToken ct = default);
|
||||
}
|
||||
121
LctMonolith/Services/ProfileService.cs
Normal file
121
LctMonolith/Services/ProfileService.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using LctMonolith.Database.UnitOfWork;
|
||||
using LctMonolith.Models.Database;
|
||||
using LctMonolith.Services.Interfaces;
|
||||
using Serilog;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace LctMonolith.Services;
|
||||
|
||||
public class ProfileService : IProfileService
|
||||
{
|
||||
private readonly IUnitOfWork _uow;
|
||||
private readonly IFileStorageService _storage;
|
||||
|
||||
public ProfileService(IUnitOfWork uow, IFileStorageService storage)
|
||||
{
|
||||
_uow = uow;
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
public async Task<Profile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _uow.Profiles.Query(p => p.UserId == userId).FirstOrDefaultAsync(ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Profile get failed {UserId}", userId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Profile> UpsertAsync(Guid userId, string? firstName, string? lastName, DateOnly? birthDate, string? about, string? location, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var profile = await _uow.Profiles.Query(p => p.UserId == userId).FirstOrDefaultAsync(ct);
|
||||
if (profile == null)
|
||||
{
|
||||
profile = new Profile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = userId,
|
||||
FirstName = firstName,
|
||||
LastName = lastName,
|
||||
BirthDate = birthDate,
|
||||
About = about,
|
||||
Location = location
|
||||
};
|
||||
await _uow.Profiles.AddAsync(profile, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.FirstName = firstName;
|
||||
profile.LastName = lastName;
|
||||
profile.BirthDate = birthDate;
|
||||
profile.About = about;
|
||||
profile.Location = location;
|
||||
profile.UpdatedAt = DateTime.UtcNow;
|
||||
_uow.Profiles.Update(profile);
|
||||
}
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
return profile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Profile upsert failed {UserId}", userId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Profile> UpdateAvatarAsync(Guid userId, Stream fileStream, string contentType, string? fileName, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var profile = await _uow.Profiles.Query(p => p.UserId == userId).FirstOrDefaultAsync(ct) ??
|
||||
await UpsertAsync(userId, null, null, null, null, null, ct);
|
||||
|
||||
// Delete old if exists
|
||||
if (!string.IsNullOrWhiteSpace(profile.AvatarS3Key))
|
||||
{
|
||||
await _storage.DeleteAsync(profile.AvatarS3Key!, ct);
|
||||
}
|
||||
var key = await _storage.UploadAsync(fileStream, contentType, $"avatars/{userId}", ct);
|
||||
var url = await _storage.GetPresignedUrlAsync(key, TimeSpan.FromHours(6), ct);
|
||||
profile.AvatarS3Key = key;
|
||||
profile.AvatarUrl = url;
|
||||
profile.UpdatedAt = DateTime.UtcNow;
|
||||
_uow.Profiles.Update(profile);
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
return profile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Avatar update failed {UserId}", userId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAvatarAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var profile = await _uow.Profiles.Query(p => p.UserId == userId).FirstOrDefaultAsync(ct);
|
||||
if (profile == null || string.IsNullOrWhiteSpace(profile.AvatarS3Key)) return false;
|
||||
await _storage.DeleteAsync(profile.AvatarS3Key!, ct);
|
||||
profile.AvatarS3Key = null;
|
||||
profile.AvatarUrl = null;
|
||||
profile.UpdatedAt = DateTime.UtcNow;
|
||||
_uow.Profiles.Update(profile);
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Delete avatar failed {UserId}", userId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
LctMonolith/Services/S3FileStorageService.cs
Normal file
102
LctMonolith/Services/S3FileStorageService.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using Amazon;
|
||||
using Microsoft.Extensions.Options;
|
||||
using LctMonolith.Application.Options;
|
||||
using LctMonolith.Services.Interfaces;
|
||||
using Serilog;
|
||||
|
||||
namespace LctMonolith.Services;
|
||||
|
||||
public class S3FileStorageService : IFileStorageService, IDisposable
|
||||
{
|
||||
private readonly S3StorageOptions _opts;
|
||||
private readonly IAmazonS3 _client;
|
||||
private bool _bucketChecked;
|
||||
|
||||
public S3FileStorageService(IOptions<S3StorageOptions> options)
|
||||
{
|
||||
_opts = options.Value;
|
||||
var cfg = new AmazonS3Config
|
||||
{
|
||||
ServiceURL = _opts.Endpoint,
|
||||
ForcePathStyle = true,
|
||||
UseHttp = !_opts.UseSsl,
|
||||
Timeout = TimeSpan.FromSeconds(30),
|
||||
MaxErrorRetry = 2,
|
||||
};
|
||||
_client = new AmazonS3Client(_opts.AccessKey, _opts.SecretKey, cfg);
|
||||
}
|
||||
|
||||
private async Task EnsureBucketAsync(CancellationToken ct)
|
||||
{
|
||||
if (_bucketChecked) return;
|
||||
try
|
||||
{
|
||||
var list = await _client.ListBucketsAsync(ct);
|
||||
if (!list.Buckets.Any(b => string.Equals(b.BucketName, _opts.Bucket, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await _client.PutBucketAsync(new PutBucketRequest { BucketName = _opts.Bucket }, ct);
|
||||
}
|
||||
_bucketChecked = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed ensuring bucket {Bucket}", _opts.Bucket);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> UploadAsync(Stream content, string contentType, string keyPrefix, CancellationToken ct = default)
|
||||
{
|
||||
await EnsureBucketAsync(ct);
|
||||
var key = $"{keyPrefix.Trim('/')}/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid():N}";
|
||||
var putReq = new PutObjectRequest
|
||||
{
|
||||
BucketName = _opts.Bucket,
|
||||
Key = key,
|
||||
InputStream = content,
|
||||
ContentType = contentType
|
||||
};
|
||||
await _client.PutObjectAsync(putReq, ct);
|
||||
Log.Information("Uploaded object {Key} to bucket {Bucket}", key, _opts.Bucket);
|
||||
return key;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string key, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) return;
|
||||
try
|
||||
{
|
||||
await _client.DeleteObjectAsync(_opts.Bucket, key, ct);
|
||||
Log.Information("Deleted object {Key}", key);
|
||||
}
|
||||
catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public Task<string> GetPresignedUrlAsync(string key, TimeSpan? expires = null, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
|
||||
if (!string.IsNullOrWhiteSpace(_opts.PublicBaseUrl))
|
||||
{
|
||||
var url = _opts.PublicBaseUrl!.TrimEnd('/') + "/" + key;
|
||||
return Task.FromResult(url);
|
||||
}
|
||||
var req = new GetPreSignedUrlRequest
|
||||
{
|
||||
BucketName = _opts.Bucket,
|
||||
Key = key,
|
||||
Expires = DateTime.UtcNow.Add(expires ?? TimeSpan.FromMinutes(_opts.PresignExpirationMinutes))
|
||||
};
|
||||
var urlSigned = _client.GetPreSignedURL(req);
|
||||
return Task.FromResult(urlSigned);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
927
LctMonolith/openapi-gamification.yaml
Normal file
927
LctMonolith/openapi-gamification.yaml
Normal file
@@ -0,0 +1,927 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: LctMonolith Gamification API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Comprehensive REST API for gamification module (players, missions, ranks, skills, rewards, dialogue, inventory, store, analytics, notifications, auth).
|
||||
Authentication via JWT Bearer (Authorization: Bearer <token>). Admin endpoints require role=Admin.
|
||||
servers:
|
||||
- url: https://localhost:5001
|
||||
description: Local HTTPS
|
||||
- url: http://localhost:5000
|
||||
description: Local HTTP
|
||||
security:
|
||||
- bearerAuth: []
|
||||
tags:
|
||||
- name: Auth
|
||||
- name: Players
|
||||
- name: Ranks
|
||||
- name: Skills
|
||||
- name: MissionCategories
|
||||
- name: Missions
|
||||
- name: Rewards
|
||||
- name: Dialogue
|
||||
- name: Inventory
|
||||
- name: Store
|
||||
- name: Notifications
|
||||
- name: Analytics
|
||||
- name: Profile
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
schemas:
|
||||
Id: { type: string, format: uuid }
|
||||
TokenPair:
|
||||
type: object
|
||||
properties:
|
||||
accessToken: { type: string }
|
||||
refreshToken: { type: string }
|
||||
expiresAt: { type: string, format: date-time }
|
||||
refreshExpiresAt: { type: string, format: date-time, nullable: true }
|
||||
Rank:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
title: { type: string }
|
||||
expNeeded: { type: integer }
|
||||
Skill:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
title: { type: string }
|
||||
Player:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
userId: { $ref: '#/components/schemas/Id' }
|
||||
rankId: { $ref: '#/components/schemas/Id' }
|
||||
experience: { type: integer }
|
||||
mana: { type: integer }
|
||||
PlayerSkill:
|
||||
type: object
|
||||
properties:
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
skillId: { $ref: '#/components/schemas/Id' }
|
||||
score: { type: integer }
|
||||
Mission:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
title: { type: string }
|
||||
description: { type: string }
|
||||
missionCategoryId: { $ref: '#/components/schemas/Id' }
|
||||
parentMissionId: { $ref: '#/components/schemas/Id', nullable: true }
|
||||
expReward: { type: integer }
|
||||
manaReward: { type: integer }
|
||||
MissionSkillReward:
|
||||
type: object
|
||||
properties:
|
||||
missionId: { $ref: '#/components/schemas/Id' }
|
||||
skillId: { $ref: '#/components/schemas/Id' }
|
||||
value: { type: integer }
|
||||
MissionItemReward:
|
||||
type: object
|
||||
properties:
|
||||
missionId: { $ref: '#/components/schemas/Id' }
|
||||
itemId: { $ref: '#/components/schemas/Id' }
|
||||
SkillProgress:
|
||||
type: object
|
||||
properties:
|
||||
skillId: { $ref: '#/components/schemas/Id' }
|
||||
skillTitle: { type: string }
|
||||
previousLevel: { type: integer }
|
||||
newLevel: { type: integer }
|
||||
MissionCompletionResult:
|
||||
type: object
|
||||
properties:
|
||||
success: { type: boolean }
|
||||
message: { type: string }
|
||||
experienceGained: { type: integer }
|
||||
manaGained: { type: integer }
|
||||
skillsProgress: { type: array, items: { $ref: '#/components/schemas/SkillProgress' } }
|
||||
unlockedMissions: { type: array, items: { $ref: '#/components/schemas/Id' } }
|
||||
PlayerProgress:
|
||||
type: object
|
||||
properties:
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
playerName: { type: string }
|
||||
currentRank: { $ref: '#/components/schemas/Rank', nullable: true }
|
||||
totalExperience: { type: integer }
|
||||
totalMana: { type: integer }
|
||||
completedMissions: { type: integer }
|
||||
totalAvailableMissions: { type: integer }
|
||||
skillLevels:
|
||||
type: object
|
||||
additionalProperties: { type: integer }
|
||||
StoreItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
name: { type: string }
|
||||
description: { type: string, nullable: true }
|
||||
price: { type: integer }
|
||||
isActive: { type: boolean }
|
||||
stock: { type: integer, nullable: true }
|
||||
UserInventoryItem:
|
||||
type: object
|
||||
properties:
|
||||
userId: { $ref: '#/components/schemas/Id' }
|
||||
storeItemId: { $ref: '#/components/schemas/Id' }
|
||||
quantity: { type: integer }
|
||||
acquiredAt: { type: string, format: date-time }
|
||||
Notification:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
type: { type: string }
|
||||
title: { type: string }
|
||||
message: { type: string }
|
||||
isRead: { type: boolean }
|
||||
createdAt: { type: string, format: date-time }
|
||||
readAt: { type: string, format: date-time, nullable: true }
|
||||
AnalyticsSummary:
|
||||
type: object
|
||||
properties:
|
||||
totalUsers: { type: integer }
|
||||
totalMissions: { type: integer }
|
||||
totalStoreItems: { type: integer }
|
||||
totalExperience: { type: integer }
|
||||
completedMissions: { type: integer }
|
||||
generatedAtUtc: { type: string, format: date-time }
|
||||
Profile:
|
||||
type: object
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
userId: { $ref: '#/components/schemas/Id' }
|
||||
firstName: { type: string, nullable: true }
|
||||
lastName: { type: string, nullable: true }
|
||||
birthDate: { type: string, format: date, nullable: true }
|
||||
about: { type: string, nullable: true }
|
||||
location: { type: string, nullable: true }
|
||||
avatarUrl: { type: string, nullable: true }
|
||||
createdAt: { type: string, format: date-time }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
paths:
|
||||
/api/auth/register:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Register user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email: { type: string, format: email }
|
||||
password: { type: string }
|
||||
firstName: { type: string }
|
||||
lastName: { type: string }
|
||||
required: [email,password]
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/TokenPair' } } } }
|
||||
/api/auth/login:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Login
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email: { type: string, format: email }
|
||||
password: { type: string }
|
||||
required: [email,password]
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/TokenPair' } } } }
|
||||
/api/auth/refresh:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Refresh access token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
refreshToken: { type: string }
|
||||
required: [refreshToken]
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/TokenPair' } } } }
|
||||
/api/auth/revoke:
|
||||
post:
|
||||
tags: [Auth]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Revoke refresh token
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
refreshToken: { type: string }
|
||||
responses:
|
||||
'204': { description: No Content }
|
||||
/api/auth/me:
|
||||
get:
|
||||
tags: [Auth]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Current user id
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
/api/analytics/summary:
|
||||
get:
|
||||
tags: [Analytics]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Aggregated analytics summary
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/AnalyticsSummary' } } } }
|
||||
/api/players/{playerId}:
|
||||
get:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get player by id
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
'404': { description: Not Found }
|
||||
/api/players/{playerId}/progress:
|
||||
get:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get player overall progress
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/PlayerProgress' } } } }
|
||||
'404': { description: Not Found }
|
||||
/api/players/user/{userId}:
|
||||
get:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get player by user id
|
||||
parameters:
|
||||
- in: path
|
||||
name: userId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
'404': { description: Not Found }
|
||||
/api/players:
|
||||
post:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create player (Admin)
|
||||
parameters: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
userId: { $ref: '#/components/schemas/Id' }
|
||||
username: { type: string }
|
||||
required: [userId,username]
|
||||
responses:
|
||||
'201': { description: Created }
|
||||
x-roles: [Admin]
|
||||
/api/players/{playerId}/experience:
|
||||
post:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Adjust player experience (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
value: { type: integer }
|
||||
required: [value]
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
x-roles: [Admin]
|
||||
/api/players/{playerId}/mana:
|
||||
post:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Adjust player mana (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
value: { type: integer }
|
||||
required: [value]
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
x-roles: [Admin]
|
||||
/api/players/top:
|
||||
get:
|
||||
tags: [Players]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Top players by experience
|
||||
parameters:
|
||||
- in: query
|
||||
name: count
|
||||
schema: { type: integer, default: 10 }
|
||||
responses:
|
||||
'200': { description: OK }
|
||||
/api/ranks:
|
||||
get:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: List ranks
|
||||
responses: { '200': { description: OK } }
|
||||
post:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create rank (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
expNeeded: { type: integer }
|
||||
required: [title,expNeeded]
|
||||
responses: { '201': { description: Created } }
|
||||
x-roles: [Admin]
|
||||
/api/ranks/{id}:
|
||||
get:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get rank
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
put:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update rank (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
expNeeded: { type: integer }
|
||||
required: [title,expNeeded]
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
delete:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete rank (Admin)
|
||||
responses: { '204': { description: No Content }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
/api/ranks/validate-advance/{playerId}/{targetRankId}:
|
||||
get:
|
||||
tags: [Ranks]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Validate advancement
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
- in: path
|
||||
name: targetRankId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/skills:
|
||||
get:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: List skills
|
||||
responses: { '200': { description: OK } }
|
||||
post:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create skill (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
required: [title]
|
||||
responses: { '201': { description: Created } }
|
||||
x-roles: [Admin]
|
||||
/api/skills/{id}:
|
||||
get:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get skill
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
put:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update skill (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
required: [title]
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
delete:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete skill (Admin)
|
||||
responses: { '204': { description: No Content }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
/api/skills/player/{playerId}:
|
||||
get:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: List player skills
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/skills/player/{playerId}/{skillId}:
|
||||
post:
|
||||
tags: [Skills]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update player skill (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
level: { type: integer }
|
||||
required: [level]
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
- in: path
|
||||
name: skillId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
x-roles: [Admin]
|
||||
/api/mission-categories:
|
||||
get:
|
||||
tags: [MissionCategories]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: List mission categories
|
||||
responses: { '200': { description: OK } }
|
||||
post:
|
||||
tags: [MissionCategories]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create mission category (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
required: [title]
|
||||
responses: { '201': { description: Created } }
|
||||
x-roles: [Admin]
|
||||
/api/mission-categories/{id}:
|
||||
get:
|
||||
tags: [MissionCategories]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get mission category
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
put:
|
||||
tags: [MissionCategories]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update mission category (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
required: [title]
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
delete:
|
||||
tags: [MissionCategories]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete mission category (Admin)
|
||||
responses: { '204': { description: No Content }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
/api/missions/{id}:
|
||||
get:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get mission
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
put:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update mission (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
description: { type: string }
|
||||
missionCategoryId: { $ref: '#/components/schemas/Id' }
|
||||
parentMissionId: { $ref: '#/components/schemas/Id', nullable: true }
|
||||
expReward: { type: integer }
|
||||
manaReward: { type: integer }
|
||||
required: [title,missionCategoryId,expReward,manaReward]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
delete:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete mission (Admin)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '204': { description: No Content }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
/api/missions/category/{categoryId}:
|
||||
get:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Missions by category
|
||||
parameters:
|
||||
- in: path
|
||||
name: categoryId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/missions/player/{playerId}/available:
|
||||
get:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Available missions for player
|
||||
parameters:
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/missions/{missionId}/rank-rules:
|
||||
get:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Rank rules for mission
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/missions:
|
||||
post:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create mission (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
description: { type: string }
|
||||
missionCategoryId: { $ref: '#/components/schemas/Id' }
|
||||
parentMissionId: { $ref: '#/components/schemas/Id', nullable: true }
|
||||
expReward: { type: integer }
|
||||
manaReward: { type: integer }
|
||||
required: [title,missionCategoryId,expReward,manaReward]
|
||||
responses: { '201': { description: Created } }
|
||||
x-roles: [Admin]
|
||||
/api/missions/{id}:
|
||||
put:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Update mission (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
description: { type: string }
|
||||
missionCategoryId: { $ref: '#/components/schemas/Id' }
|
||||
parentMissionId: { $ref: '#/components/schemas/Id', nullable: true }
|
||||
expReward: { type: integer }
|
||||
manaReward: { type: integer }
|
||||
required: [title,missionCategoryId,expReward,manaReward]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
delete:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete mission (Admin)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '204': { description: No Content }, '404': { description: Not Found } }
|
||||
x-roles: [Admin]
|
||||
/api/missions/{missionId}/complete:
|
||||
post:
|
||||
tags: [Missions]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Complete mission
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
proof: { nullable: true }
|
||||
required: [playerId]
|
||||
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/MissionCompletionResult' } } } }, '400': { description: Bad Request } }
|
||||
/api/rewards/mission/{missionId}/skills:
|
||||
get:
|
||||
tags: [Rewards]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Mission skill rewards
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/rewards/mission/{missionId}/items:
|
||||
get:
|
||||
tags: [Rewards]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Mission item rewards
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/rewards/mission/{missionId}/can-claim/{playerId}:
|
||||
get:
|
||||
tags: [Rewards]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Can claim mission rewards
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
- in: path
|
||||
name: playerId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/rewards/mission/{missionId}/claim:
|
||||
post:
|
||||
tags: [Rewards]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Claim mission rewards
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
required: [playerId]
|
||||
responses: { '200': { description: OK }, '409': { description: Conflict } }
|
||||
/api/rewards/mission/{missionId}/force-distribute:
|
||||
post:
|
||||
tags: [Rewards]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Force distribute mission rewards (Admin)
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
required: [playerId]
|
||||
responses: { '200': { description: OK } }
|
||||
x-roles: [Admin]
|
||||
/api/dialogue/mission/{missionId}:
|
||||
get:
|
||||
tags: [Dialogue]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get dialogue by mission
|
||||
parameters:
|
||||
- in: path
|
||||
name: missionId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
/api/dialogue/message/{messageId}:
|
||||
get:
|
||||
tags: [Dialogue]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get dialogue message
|
||||
parameters:
|
||||
- in: path
|
||||
name: messageId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK }, '404': { description: Not Found } }
|
||||
/api/dialogue/message/{messageId}/options:
|
||||
get:
|
||||
tags: [Dialogue]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get dialogue response options
|
||||
parameters:
|
||||
- in: path
|
||||
name: messageId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses: { '200': { description: OK } }
|
||||
/api/dialogue/message/{messageId}/respond:
|
||||
post:
|
||||
tags: [Dialogue]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Respond to dialogue
|
||||
parameters:
|
||||
- in: path
|
||||
name: messageId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
responseOptionId: { $ref: '#/components/schemas/Id' }
|
||||
playerId: { $ref: '#/components/schemas/Id' }
|
||||
required: [responseOptionId,playerId]
|
||||
responses: { '200': { description: OK } }
|
||||
/api/dialogue:
|
||||
post:
|
||||
tags: [Dialogue]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Create dialogue (Admin)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
missionId: { $ref: '#/components/schemas/Id' }
|
||||
initialDialogueMessageId: { $ref: '#/components/schemas/Id' }
|
||||
interimDialogueMessageId: { $ref: '#/components/schemas/Id' }
|
||||
endDialogueMessageId: { $ref: '#/components/schemas/Id' }
|
||||
required: [missionId,initialDialogueMessageId,interimDialogueMessageId,endDialogueMessageId]
|
||||
responses: { '201': { description: Created } }
|
||||
x-roles: [Admin]
|
||||
/api/profile/me:
|
||||
get:
|
||||
tags: [Profile]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get current user profile
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Profile' } } } }
|
||||
'404': { description: Not Found }
|
||||
/api/profile/{userId}:
|
||||
get:
|
||||
tags: [Profile]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Get profile by user id (Admin)
|
||||
parameters:
|
||||
- in: path
|
||||
name: userId
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Profile' } } } }
|
||||
'404': { description: Not Found }
|
||||
x-roles: [Admin]
|
||||
/api/profile:
|
||||
put:
|
||||
tags: [Profile]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Upsert current user profile
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
firstName: { type: string, nullable: true }
|
||||
lastName: { type: string, nullable: true }
|
||||
birthDate: { type: string, format: date, nullable: true }
|
||||
about: { type: string, nullable: true }
|
||||
location: { type: string, nullable: true }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Profile' } } } }
|
||||
/api/profile/avatar:
|
||||
post:
|
||||
tags: [Profile]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Upload avatar image (multipart/form-data)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { type: object, properties: { avatarUrl: { type: string } } } } } }
|
||||
'400': { description: Bad Request }
|
||||
delete:
|
||||
tags: [Profile]
|
||||
security: [ { bearerAuth: [] } ]
|
||||
summary: Delete avatar image
|
||||
responses:
|
||||
'204': { description: No Content }
|
||||
'404': { description: Not Found }
|
||||
Reference in New Issue
Block a user