flower-story/Server/Controller/EventApiController.cs
2024-10-12 14:34:11 +08:00

905 lines
29 KiB
C#

using Blahblah.FlowerStory.Server.Data;
using Blahblah.FlowerStory.Server.Data.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace Blahblah.FlowerStory.Server.Controller;
/// <summary>
/// 事件相关 API 服务
/// </summary>
[ApiController]
[Produces("application/json")]
[Route("api/event")]
public class EventApiController(FlowerDatabase database, ILogger<EventApiController>? logger = null) : BaseController<EventApiController>(database, logger)
{
/// <summary>
/// 获取符合条件的公共事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// GET /api/event/query
/// Authorization: authorization id
///
/// 参数:
///
/// fid: int?
/// eid: int?
/// key: string?
/// from: long?
/// to: long?
/// p: bool?
/// size: int?
///
/// </remarks>
/// <param name="flowerId">花草唯一 id</param>
/// <param name="eventId">事件类型 id</param>
/// <param name="key">查询关键字</param>
/// <param name="from">起始日期</param>
/// <param name="to">结束日期</param>
/// <param name="includePhoto">是否包含图片</param>
/// <param name="pageSize">分页大小</param>
/// <returns>会话有效则返回符合条件的事件集</returns>
/// <response code="200">返回符合条件的事件集</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("query", Name = "queryEvents")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpGet]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public ActionResult<RecordItem[]> GetRecords(
[FromQuery(Name = "fid")] int? flowerId,
[FromQuery(Name = "eid")] int? eventId,
[FromQuery] string? key,
[FromQuery] long? from,
[FromQuery] long? to,
[FromQuery(Name = "p")] bool? includePhoto,
[FromQuery(Name = "size")] int? pageSize = 20)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
SaveDatabase();
var records = database.Records.Where(r => r.IsHidden != true);
if (flowerId != null)
{
records = records.Where(r => r.FlowerId == flowerId);
}
if (eventId != null)
{
records = records.Where(r => r.EventId == eventId);
}
if (key != null)
{
records = records.Where(r =>
r.ByUserName != null && r.ByUserName.Contains(key, StringComparison.CurrentCultureIgnoreCase) ||
r.Memo != null && r.Memo.Contains(key, StringComparison.CurrentCultureIgnoreCase));
}
if (from != null)
{
records = records.Where(r => r.DateUnixTime >= from);
}
if (to != null)
{
records = records.Where(r => r.DateUnixTime <= to);
}
if (includePhoto == true)
{
records = records.Include(r => r.Photos);
}
records = records.Select(r => new RecordItem
{
Id = r.Id,
OwnerId = r.OwnerId,
ByUserId = r.ByUserId,
DateUnixTime = r.DateUnixTime,
EventId = r.EventId,
FlowerId = r.FlowerId,
IsHidden = r.IsHidden,
Latitude = r.Latitude,
Longitude = r.Longitude,
Title = r.Title,
Memo = r.Memo,
ByUserName = string.IsNullOrEmpty(r.ByUserName) && r.ByUserId != null ? database.Users.Single(u => u.Id == r.ByUserId).Name : r.ByUserName,
Photos = r.Photos,
LikeCount = r.LikeCount,
FavoriteCount = r.FavoriteCount,
CommentCount = r.CommentCount
});
var size = pageSize ?? 20;
records = records.OrderByDescending(r => r.DateUnixTime).Take(size);
var array = records.ToArray();
foreach (var r in array)
{
if (string.IsNullOrEmpty(r.ByUserName))
{
r.ByUserName = user.Name;
}
if (r.Photos == null)
{
continue;
}
foreach (var photo in r.Photos)
{
photo.Url = $"photo/flower/{r.FlowerId}/{photo.Path}?thumb=1";
}
}
return Ok(array);
}
/// <summary>
/// 查询事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// GET /api/event/get
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
/// photo: bool?
///
/// </remarks>
/// <param name="id">事件唯一 id</param>
/// <param name="includePhoto">是否包含图片</param>
/// <returns>会话有效则返回查询到的事件对象</returns>
/// <response code="200">返回查询到的事件对象</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者未找到事件</response>
[Route("get", Name = "getEvent")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpGet]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public ActionResult<RecordItem> GetEvent(
[FromQuery][Required] int id,
[FromQuery(Name = "photo")] bool? includePhoto)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
SaveDatabase();
var item = database.Records.Find(id);
if (item == null)
{
return NotFound($"Event id {id} not found");
}
if (includePhoto == true)
{
item.Photos = [.. database.Photos.Where(p => p.RecordId == item.Id)];
foreach (var photo in item.Photos)
{
photo.Url = $"photo/flower/{item.FlowerId}/{photo.Path}";
}
}
return Ok(item);
}
/// <summary>
/// 移除用户的事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// DELETE /api/event/remove
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
///
/// </remarks>
/// <param name="id">事件唯一 id</param>
/// <returns>会话有效则返回操作影响的数据库行数</returns>
/// <response code="200">返回操作影响的数据库行数</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("remove", Name = "removeEvent")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpDelete]
public ActionResult<int> RemoveEvent([FromQuery][Required] int id)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
var count = database.Records.Where(r => r.OwnerId == user.Id && r.Id == id).ExecuteDelete();
SaveDatabase();
return Ok(count);
}
/// <summary>
/// 批量移除用户的事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// POST /api/event/removeany
/// Authorization: authorization id
/// [
/// 2, 4, 5, 11
/// ]
///
/// </remarks>
/// <param name="ids">要移除的事件唯一 id 的数组</param>
/// <returns>会话有效则返回操作影响的数据库行数</returns>
/// <response code="200">返回操作影响的数据库行数</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("removeany", Name = "removeEvents")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPost]
[Consumes("application/json")]
public ActionResult<int> RemoveEvents([FromBody] int[] ids)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
var count = database.Records.Where(r => r.OwnerId == user.Id && ids.Contains(r.Id)).ExecuteDelete();
SaveDatabase();
return Ok(count);
}
/// <summary>
/// 用户添加事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// POST /api/event/add
/// Authorization: authorization id
///
/// 参数:
///
/// flowerId: 1
/// eventId: 4 // 浇水
/// byUser: "朋友"
/// memo: "快干死了"
/// lon: 29.5462794
/// lat: 106.5380034
/// photos: &lt;photo&gt;[]
///
/// </remarks>
/// <param name="event">事件参数</param>
/// <returns>添加成功则返回已添加的事件对象</returns>
/// <response code="200">返回已添加的事件对象</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("add", Name = "addEvent")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPost]
[Consumes("multipart/form-data")]
// 5 photos
[RequestSizeLimit(75 * 1024 * 1024)]
public async Task<ActionResult<RecordItem>> AddEvent([FromForm] EventParameter @event)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
var now = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var item = new RecordItem
{
OwnerId = user.Id,
FlowerId = @event.FlowerId,
EventId = @event.CategoryId,
DateUnixTime = now,
IsHidden = @event.IsHidden,
ByUserId = @event.ByUser == null ? user.Id : null,
ByUserName = @event.ByUser,
Title = @event.Title,
Memo = @event.Memo,
Latitude = @event.Latitude,
Longitude = @event.Longitude
};
database.Records.Add(item);
SaveDatabase();
if (@event.Photos?.Length > 0)
{
try
{
await ExecuteTransaction(async token =>
{
foreach (var photo in @event.Photos)
{
var file = WrapFormFile(photo);
if (file == null)
{
continue;
}
var p = new PhotoItem
{
OwnerId = user.Id,
FlowerId = @event.FlowerId,
RecordId = item.Id,
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
DateUploadUnixTime = now,
Width = file.Width,
Height = file.Height
};
AddPhotoItem(p);
await WriteToFile(@event.FlowerId, file, token: token);
}
});
}
catch (Exception ex)
{
return Problem(ex.ToString(), "api/event/add");
// TODO: Logger
}
}
return Ok(item);
}
/// <summary>
/// 修改事件
/// </summary>
/// <remarks>
/// 请求示例:
///
/// PUT /api/event/update
/// Authorization: authorization id
///
/// 参数:
///
/// id: 1
/// flowerId: 1
/// eventId": 5 // 施肥
/// byUser: null
/// memo: "花多多1号"
/// lon: 29.5462794
/// lat: 106.5380034
/// photos: &lt;photo&gt;[]
///
/// </remarks>
/// <param name="update">修改参数</param>
/// <returns>修改成功则返回已修改的事件对象</returns>
/// <response code="200">返回已修改的事件对象</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者未找到将修改的事件对象</response>
[Route("update", Name = "updateEvent")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPut]
[Consumes("multipart/form-data")]
// 5 photos
[RequestSizeLimit(75 * 1024 * 1024)]
public async Task<ActionResult<RecordItem>> Update([FromForm] EventUpdateParameter update)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
var record = database.Records.SingleOrDefault(r => r.Id == update.Id && r.OwnerId == user.Id);
if (record == null)
{
return NotFound($"Event id {update.Id} not found");
}
var now = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
record.FlowerId = update.FlowerId;
record.EventId = update.CategoryId;
record.DateUnixTime = now;
record.IsHidden = update.IsHidden;
if (update.ByUser == null)
{
record.ByUserId = user.Id;
record.ByUserName = null;
}
else
{
record.ByUserId = null;
record.ByUserName = update.ByUser;
}
record.Title = update.Title;
record.Memo = update.Memo;
record.Latitude = update.Latitude;
record.Longitude = update.Longitude;
if (update.Photos?.Length > 0)
{
try
{
await ExecuteTransaction(async token =>
{
foreach (var photo in update.Photos)
{
var file = WrapFormFile(photo);
if (file == null)
{
continue;
}
var photos = database.Photos.Where(p => p.RecordId == record.Id).ToList();
if (photos.Count > 0)
{
database.Photos.Where(p => p.RecordId == record.Id).ExecuteDelete();
foreach (var p in photos)
{
DeleteFile(update.FlowerId, p.Path);
}
}
SaveDatabase();
var cover = new PhotoItem
{
OwnerId = user.Id,
FlowerId = update.FlowerId,
RecordId = record.Id,
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
DateUploadUnixTime = now,
Width = file.Width,
Height = file.Height
};
AddPhotoItem(cover);
await WriteToFile(update.FlowerId, file, token: token);
}
});
}
catch (Exception ex)
{
return Problem(ex.ToString(), "api/event/update");
// TODO: Logger
}
}
else
{
SaveDatabase();
}
return Ok(user);
}
/// <summary>
/// 添加事件关联照片
/// </summary>
/// <remarks>
/// 请求示例:
///
/// POST /api/event/add_photo
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
/// photo: IFormFile
///
/// </remarks>
/// <param name="id">事件唯一 id</param>
/// <param name="photo">图片</param>
/// <returns>添加成功则返回 HTTP 204</returns>
/// <response code="204">添加成功</response>
/// <response code="400">图片格式非法</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者关联的事件</response>
/// <response code="413">提交正文过大</response>
[Route("add_photo", Name = "addEventPhoto")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPost]
[Consumes("multipart/form-data")]
[RequestSizeLimit(15 * 1024 * 1024)]
public async Task<ActionResult> UploadPhoto([Required][FromQuery] int id, [Required] IFormFile photo)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
SaveDatabase();
var record = database.Records.SingleOrDefault(r => r.Id == id && r.OwnerId == user.Id);
if (record == null)
{
return NotFound($"Record id {id} not found");
}
if (photo.Length > 0)
{
var file = WrapFormFile(photo);
if (file == null)
{
return BadRequest();
}
try
{
await ExecuteTransaction(async token =>
{
var p = new PhotoItem
{
OwnerId = user.Id,
FlowerId = record.FlowerId,
RecordId = id,
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Width = file.Width,
Height = file.Height
};
AddPhotoItem(p);
await WriteToFile(record.FlowerId, file, token: token);
});
}
catch (Exception ex)
{
return Problem(ex.ToString(), "api/event/add_photo");
// TODO: Logger
}
}
return NoContent();
}
/// <summary>
/// 批量添加事件关联照片,总大小限制 75MB
/// </summary>
/// <remarks>
/// 请求示例:
///
/// POST /api/event/add_photos
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
/// photos: IFormFile[]
///
/// </remarks>
/// <param name="id">事件唯一 id</param>
/// <param name="photos">图片集</param>
/// <returns>添加成功则返回 HTTP 204</returns>
/// <response code="204">添加成功</response>
/// <response code="400">图片参数非法或图片格式非法</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者关联的事件</response>
/// <response code="413">提交正文过大</response>
[Route("add_photos", Name = "addEventPhotos")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPost]
[Consumes("multipart/form-data")]
// 5 photos
[RequestSizeLimit(75 * 1024 * 1024)]
public async Task<ActionResult> UploadPhotos([Required][FromQuery] int id, [Required] IFormFile[] photos)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
if (photos == null || photos.Length == 0)
{
return BadRequest();
}
var record = database.Records.SingleOrDefault(r => r.Id == id && r.OwnerId == user.Id);
if (record == null)
{
return NotFound($"Record id {id} not found");
}
SaveDatabase();
try
{
await ExecuteTransaction(async token =>
{
foreach (var photo in photos)
{
if (photo.Length > 0)
{
var file = WrapFormFile(photo) ?? throw new BadHttpRequestException(photo?.FileName ?? string.Empty);
var p = new PhotoItem
{
OwnerId = user.Id,
FlowerId = record.FlowerId,
RecordId = id,
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Width = file.Width,
Height = file.Height
};
AddPhotoItem(p);
await WriteToFile(record.FlowerId, file, token: token);
}
}
});
}
catch (BadHttpRequestException bex)
{
return BadRequest(bex.Message);
}
catch (Exception ex)
{
return Problem(ex.ToString(), "api/event/add_photos");
// TODO: Logger
}
return NoContent();
}
/// <summary>
/// 移除事件关联照片
/// </summary>
/// <remarks>
/// 请求示例:
///
/// DELETE /api/event/remove_photo
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
///
/// </remarks>
/// <param name="id">图片唯一 id</param>
/// <returns>移除成功则返回 HTTP 204</returns>
/// <response code="204">移除成功</response>
/// <response code="401">未找到登录会话或已过期或图片所有者不符</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者照片</response>
[Route("remove_photo", Name = "removeEventPhoto")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpDelete]
public ActionResult<int> RemoveEventPhoto([FromQuery][Required] int id)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
var photo = database.Photos.Find(id);
if (photo == null)
{
return NotFound();
}
var item = database.Flowers.Find(photo.FlowerId);
if (item == null || item.OwnerId != user.Id)
{
return Unauthorized();
}
database.Photos.Remove(photo);
SaveDatabase();
DeleteFile(photo.FlowerId ?? TempId, photo.Path);
return NoContent();
}
/// <summary>
/// 批量移除事件关联的照片
/// </summary>
/// <remarks>
/// 请求示例:
///
/// POST /api/event/remove_photos
/// Authorization: authorization id
/// [
/// 2, 4, 5, 11
/// ]
///
/// </remarks>
/// <param name="ids">要移除的事件关联图片唯一 id 的数组</param>
/// <returns>会话有效则返回操作影响的数据库行数</returns>
/// <response code="200">返回操作影响的数据库行数</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("remove_photos", Name = "removeEventPhotos")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpPost]
[Consumes("application/json")]
public ActionResult<int> RemoveEventPhotos([FromBody] int[] ids)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
if (database.Photos.Any(p => ids.Contains(p.Id) && database.Records.Any(r => r.Id == p.RecordId && r.OwnerId != user.Id)))
{
return Unauthorized();
}
var photos = database.Photos.Where(p => ids.Contains(p.Id)).ToList();
var count = database.Photos.Where(p => ids.Contains(p.Id)).ExecuteDelete();
SaveDatabase();
foreach (var photo in photos)
{
DeleteFile(photo.FlowerId ?? TempId, photo.Path);
}
return Ok(count);
}
/// <summary>
/// 获取事件关联的照片列表
/// </summary>
/// <remarks>
/// 请求示例:
///
/// GET /api/event/photos
/// Authorization: authorization id
///
/// 参数:
///
/// id: int
///
/// </remarks>
/// <param name="id">事件唯一 id</param>
/// <returns>验证通过则返回事件关联的照片列表</returns>
/// <response code="200">返回事件关联的照片列表</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者未找到事件对象</response>
[Route("photos", Name = "getEventPhotos")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesErrorResponseType(typeof(ErrorResponse))]
[HttpGet]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public ActionResult<PhotoItem[]> GetPhotos([Required][FromQuery] int id)
{
var (result, user) = CheckPermission();
if (result != null)
{
return result;
}
if (user == null)
{
return NotFound();
}
SaveDatabase();
var photos = database.Photos.Where(p => p.RecordId == id && database.Records.Any(r => r.Id == id && r.OwnerId == user.Id));
return Ok(photos);
}
}