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" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" />
|
||||||
<!-- Updated to match transitive requirement (>=8.0.1) -->
|
<!-- Updated to match transitive requirement (>=8.0.1) -->
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||||
|
<PackageReference Include="AWSSDK.S3" Version="3.7.400.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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