✨ Posting
This commit is contained in:
@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
|
||||
@ -12,28 +13,78 @@ public enum PostType
|
||||
Video
|
||||
}
|
||||
|
||||
public enum PostVisibility
|
||||
{
|
||||
Public,
|
||||
Friends,
|
||||
Unlisted,
|
||||
Private
|
||||
}
|
||||
|
||||
public class Post : ModelBase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
[MaxLength(128)] public string? Language { get; set; }
|
||||
public Instant? EditedAt { get; set; }
|
||||
public Instant? PublishedAt { get; set; }
|
||||
public PostVisibility Visibility { get; set; } = PostVisibility.Public;
|
||||
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string? Content { get; set; }
|
||||
|
||||
|
||||
public PostType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] Dictionary<string, object>? Meta { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
|
||||
public int ViewsUnique { get; set; }
|
||||
public int ViewsTotal { get; set; }
|
||||
public int Upvotes { get; set; }
|
||||
public int Downvotes { get; set; }
|
||||
|
||||
public long? ThreadedPostId { get; set; }
|
||||
public Post? ThreadedPost { get; set; }
|
||||
public long? RepliedPostId { get; set; }
|
||||
public Post? RepliedPost { get; set; }
|
||||
public long? ForwardedPostId { get; set; }
|
||||
public Post? ForwardedPost { get; set; }
|
||||
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
||||
|
||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||
public Publisher Publisher { get; set; } = null!;
|
||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||
public ICollection<PostTag> Tags { get; set; } = new List<PostTag>();
|
||||
public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
|
||||
public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
||||
|
||||
public bool Empty => Content?.Trim() is { Length: 0 } && Attachments.Count == 0 && ForwardedPostId == null;
|
||||
}
|
||||
|
||||
public class PostTag : ModelBase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||
}
|
||||
|
||||
public class PostCategory : ModelBase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||
}
|
||||
|
||||
public class PostCollection : ModelBase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||
[MaxLength(256)] public string? Name { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
|
||||
public Publisher Publisher { get; set; } = null!;
|
||||
|
||||
public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||
}
|
||||
|
||||
public enum PostReactionAttitude
|
||||
|
225
DysonNetwork.Sphere/Post/PostController.cs
Normal file
225
DysonNetwork.Sphere/Post/PostController.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
|
||||
[ApiController]
|
||||
[Route("/posts")]
|
||||
public class PostController(AppDatabase db, PostService ps) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<List<Post>>> ListPosts([FromQuery] int offset = 0, [FromQuery] int take = 20)
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
|
||||
var totalCount = await db.Posts
|
||||
.CountAsync();
|
||||
var posts = await db.Posts
|
||||
.Include(e => e.Publisher)
|
||||
.Include(e => e.ThreadedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Attachments)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.FilterWithVisibility(currentUser, isListing: true)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
return Ok(posts);
|
||||
}
|
||||
|
||||
[HttpGet("{id:long}")]
|
||||
public async Task<ActionResult<Post>> GetPost(long id)
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
.Include(e => e.Publisher)
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ThreadedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Tags)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Attachments)
|
||||
.FilterWithVisibility(currentUser)
|
||||
.FirstOrDefaultAsync();
|
||||
if (post is null) return NotFound();
|
||||
|
||||
return Ok(post);
|
||||
}
|
||||
|
||||
[HttpGet("{id:long}/replies")]
|
||||
public async Task<ActionResult<List<Post>>> ListReplies(long id, [FromQuery] int offset = 0,
|
||||
[FromQuery] int take = 20)
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (post is null) return NotFound();
|
||||
|
||||
var totalCount = await db.Posts
|
||||
.Where(e => e.RepliedPostId == post.Id)
|
||||
.CountAsync();
|
||||
var posts = await db.Posts
|
||||
.Where(e => e.RepliedPostId == id)
|
||||
.Include(e => e.Publisher)
|
||||
.Include(e => e.ThreadedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Attachments)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.FilterWithVisibility(currentUser, isListing: true)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
return Ok(posts);
|
||||
}
|
||||
|
||||
public class PostRequest
|
||||
{
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public PostVisibility? Visibility { get; set; }
|
||||
public PostType? Type { get; set; }
|
||||
[MaxLength(16)] public List<string>? Tags { get; set; }
|
||||
[MaxLength(8)] public List<string>? Categories { get; set; }
|
||||
[MaxLength(32)] public List<string>? Attachments { get; set; }
|
||||
public Dictionary<string, object>? Meta { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Post>> CreatePost(
|
||||
[FromBody] PostRequest request,
|
||||
[FromHeader(Name = "X-Pub")] string? publisherName
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
Publisher? publisher;
|
||||
if (publisherName is null)
|
||||
{
|
||||
// Use the first personal publisher
|
||||
publisher = await db.Publishers.FirstOrDefaultAsync(e =>
|
||||
e.AccountId == currentUser.Id && e.PublisherType == PublisherType.Individual);
|
||||
}
|
||||
else
|
||||
{
|
||||
publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == publisherName);
|
||||
if (publisher is null) return BadRequest("Publisher was not found.");
|
||||
var member =
|
||||
await db.PublisherMembers.FirstOrDefaultAsync(e =>
|
||||
e.AccountId == currentUser.Id && e.PublisherId == publisher.Id);
|
||||
if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified.");
|
||||
if (member.Role < PublisherMemberRole.Editor)
|
||||
return StatusCode(403, "You need at least be an editor to post as this publisher.");
|
||||
}
|
||||
|
||||
if (publisher is null) return BadRequest("Publisher was not found.");
|
||||
|
||||
var post = new Post
|
||||
{
|
||||
Title = request.Title,
|
||||
Description = request.Description,
|
||||
Content = request.Content,
|
||||
Visibility = request.Visibility ?? PostVisibility.Public,
|
||||
Type = request.Type ?? PostType.Moment,
|
||||
Meta = request.Meta,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
post = await ps.PostAsync(
|
||||
post,
|
||||
attachments: request.Attachments,
|
||||
tags: request.Tags,
|
||||
categories: request.Categories
|
||||
);
|
||||
}
|
||||
catch (InvalidOperationException err)
|
||||
{
|
||||
return BadRequest(err.Message);
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
[HttpPatch("{id:long}")]
|
||||
public async Task<ActionResult<Post>> UpdatePost(long id, [FromBody] PostRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
.Include(e => e.Publisher)
|
||||
.Include(e => e.Attachments)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.FirstOrDefaultAsync();
|
||||
if (post is null) return NotFound();
|
||||
|
||||
var member = await db.PublisherMembers
|
||||
.FirstOrDefaultAsync(e => e.AccountId == currentUser.Id && e.PublisherId == post.Publisher.Id);
|
||||
if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified.");
|
||||
if (member.Role < PublisherMemberRole.Editor)
|
||||
return StatusCode(403, "You need at least be an editor to edit this publisher's post.");
|
||||
|
||||
if (request.Title is not null) post.Title = request.Title;
|
||||
if (request.Description is not null) post.Description = request.Description;
|
||||
if (request.Content is not null) post.Content = request.Content;
|
||||
if (request.Visibility is not null) post.Visibility = request.Visibility.Value;
|
||||
if (request.Type is not null) post.Type = request.Type.Value;
|
||||
if (request.Meta is not null) post.Meta = request.Meta;
|
||||
|
||||
try
|
||||
{
|
||||
post = await ps.UpdatePostAsync(
|
||||
post,
|
||||
attachments: request.Attachments,
|
||||
tags: request.Tags,
|
||||
categories: request.Categories
|
||||
);
|
||||
}
|
||||
catch (InvalidOperationException err)
|
||||
{
|
||||
return BadRequest(err.Message);
|
||||
}
|
||||
|
||||
return Ok(post);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:long}")]
|
||||
public async Task<ActionResult<Post>> DeletePost(long id)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
.Include(e => e.Attachments)
|
||||
.FirstOrDefaultAsync();
|
||||
if (post is null) return NotFound();
|
||||
|
||||
var member = await db.PublisherMembers
|
||||
.FirstOrDefaultAsync(e => e.AccountId == currentUser.Id && e.PublisherId == post.Publisher.Id);
|
||||
if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified.");
|
||||
if (member.Role < PublisherMemberRole.Editor)
|
||||
return StatusCode(403, "You need at least be an editor to delete the publisher's post.");
|
||||
|
||||
await ps.DeletePostAsync(post);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
@ -1,6 +1,157 @@
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
|
||||
public class PostService(AppDatabase db)
|
||||
public class PostService(AppDatabase db, FileService fs)
|
||||
{
|
||||
|
||||
public async Task<Post> PostAsync(
|
||||
Post post,
|
||||
List<string>? attachments = null,
|
||||
List<string>? tags = null,
|
||||
List<string>? categories = null
|
||||
)
|
||||
{
|
||||
if (attachments is not null)
|
||||
{
|
||||
post.Attachments = await db.Files.Where(e => attachments.Contains(e.Id)).ToListAsync();
|
||||
// Re-order the list to match the id list places
|
||||
post.Attachments = attachments
|
||||
.Select(id => post.Attachments.First(a => a.Id == id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (tags is not null)
|
||||
{
|
||||
var existingTags = await db.PostTags.Where(e => tags.Contains(e.Slug)).ToListAsync();
|
||||
|
||||
// Determine missing slugs
|
||||
var existingSlugs = existingTags.Select(t => t.Slug).ToHashSet();
|
||||
var missingSlugs = tags.Where(slug => !existingSlugs.Contains(slug)).ToList();
|
||||
|
||||
var newTags = missingSlugs.Select(slug => new PostTag { Slug = slug }).ToList();
|
||||
if (newTags.Count > 0)
|
||||
{
|
||||
await db.PostTags.AddRangeAsync(newTags);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
post.Tags = existingTags.Concat(newTags).ToList();
|
||||
}
|
||||
|
||||
if (categories is not null)
|
||||
{
|
||||
post.Categories = await db.PostCategories.Where(e => categories.Contains(e.Slug)).ToListAsync();
|
||||
if (post.Categories.Count != categories.Distinct().Count())
|
||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||
}
|
||||
|
||||
if (post.Empty)
|
||||
throw new InvalidOperationException("Cannot create a post with barely no content.");
|
||||
|
||||
// TODO Notify the subscribers
|
||||
|
||||
db.Posts.Add(post);
|
||||
await db.SaveChangesAsync();
|
||||
await fs.MarkUsageRangeAsync(post.Attachments, 1);
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
public async Task<Post> UpdatePostAsync(
|
||||
Post post,
|
||||
List<string>? attachments = null,
|
||||
List<string>? tags = null,
|
||||
List<string>? categories = null
|
||||
)
|
||||
{
|
||||
post.EditedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
|
||||
if (attachments is not null)
|
||||
{
|
||||
var records = await db.Files.Where(e => attachments.Contains(e.Id)).ToListAsync();
|
||||
|
||||
var previous = post.Attachments.ToDictionary(f => f.Id);
|
||||
var current = records.ToDictionary(f => f.Id);
|
||||
|
||||
// Detect added files
|
||||
var added = current.Keys.Except(previous.Keys).Select(id => current[id]).ToList();
|
||||
// Detect removed files
|
||||
var removed = previous.Keys.Except(current.Keys).Select(id => previous[id]).ToList();
|
||||
|
||||
// Update attachments
|
||||
post.Attachments = attachments.Select(id => current[id]).ToList();
|
||||
|
||||
// Call mark usage
|
||||
await fs.MarkUsageRangeAsync(added, 1);
|
||||
await fs.MarkUsageRangeAsync(removed, -1);
|
||||
}
|
||||
|
||||
if (tags is not null)
|
||||
{
|
||||
var existingTags = await db.PostTags.Where(e => tags.Contains(e.Slug)).ToListAsync();
|
||||
|
||||
// Determine missing slugs
|
||||
var existingSlugs = existingTags.Select(t => t.Slug).ToHashSet();
|
||||
var missingSlugs = tags.Where(slug => !existingSlugs.Contains(slug)).ToList();
|
||||
|
||||
var newTags = missingSlugs.Select(slug => new PostTag { Slug = slug }).ToList();
|
||||
if (newTags.Count > 0)
|
||||
{
|
||||
await db.PostTags.AddRangeAsync(newTags);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
post.Tags = existingTags.Concat(newTags).ToList();
|
||||
}
|
||||
|
||||
if (categories is not null)
|
||||
{
|
||||
post.Categories = await db.PostCategories.Where(e => categories.Contains(e.Slug)).ToListAsync();
|
||||
if (post.Categories.Count != categories.Distinct().Count())
|
||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||
}
|
||||
|
||||
if (post.Empty)
|
||||
throw new InvalidOperationException("Cannot edit a post to barely no content.");
|
||||
|
||||
db.Update(post);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
public async Task DeletePostAsync(Post post)
|
||||
{
|
||||
db.Posts.Remove(post);
|
||||
await db.SaveChangesAsync();
|
||||
await fs.MarkUsageRangeAsync(post.Attachments, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PostQueryExtensions
|
||||
{
|
||||
public static IQueryable<Post> FilterWithVisibility(this IQueryable<Post> source, Account.Account? currentUser,
|
||||
bool isListing = false)
|
||||
{
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
|
||||
source = isListing switch
|
||||
{
|
||||
true when currentUser is not null => source.Where(e =>
|
||||
e.Visibility != PostVisibility.Unlisted || e.Publisher.AccountId == currentUser.Id),
|
||||
true => source.Where(e => e.Visibility != PostVisibility.Unlisted),
|
||||
_ => source
|
||||
};
|
||||
|
||||
if (currentUser is null)
|
||||
return source
|
||||
.Where(e => e.PublishedAt != null && now >= e.PublishedAt)
|
||||
.Where(e => e.Visibility == PostVisibility.Public);
|
||||
|
||||
return source
|
||||
.Where(e => e.PublishedAt != null && now >= e.PublishedAt && e.Publisher.AccountId == currentUser.Id)
|
||||
.Where(e => e.Visibility != PostVisibility.Private || e.Publisher.AccountId == currentUser.Id);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
@ -12,6 +13,7 @@ public enum PublisherType
|
||||
Organizational
|
||||
}
|
||||
|
||||
[Index(nameof(Name), IsUnique = true)]
|
||||
public class Publisher : ModelBase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
@ -24,7 +26,10 @@ public class Publisher : ModelBase
|
||||
public CloudFile? Background { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
||||
[JsonIgnore] public ICollection<PublisherMember> Members { get; set; } = new List<PublisherMember>();
|
||||
|
||||
public long? AccountId { get; set; }
|
||||
[JsonIgnore] public Account.Account? Account { get; set; }
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Casbin;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -9,13 +10,13 @@ namespace DysonNetwork.Sphere.Post;
|
||||
|
||||
[ApiController]
|
||||
[Route("/publishers")]
|
||||
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs) : ControllerBase
|
||||
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs, IEnforcer enforcer)
|
||||
: ControllerBase
|
||||
{
|
||||
[HttpGet("{name}")]
|
||||
public async Task<ActionResult<Publisher>> GetPublisher(string name)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
var userId = currentUser.Id;
|
||||
|
||||
var publisher = await db.Publishers
|
||||
.Where(e => e.Name == name)
|
||||
@ -56,7 +57,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
|
||||
return members.ToList();
|
||||
}
|
||||
|
||||
|
||||
public class PublisherMemberRequest
|
||||
{
|
||||
[Required] public long RelatedUserId { get; set; }
|
||||
@ -65,11 +66,12 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
|
||||
[HttpPost("invites/{name}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<PublisherMember>> InviteMember(string name, [FromBody] PublisherMemberRequest request)
|
||||
public async Task<ActionResult<PublisherMember>> InviteMember(string name,
|
||||
[FromBody] PublisherMemberRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
var userId = currentUser.Id;
|
||||
|
||||
|
||||
var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
|
||||
if (relatedUser is null) return BadRequest("Related user was not found");
|
||||
|
||||
@ -86,7 +88,8 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
.FirstOrDefaultAsync();
|
||||
if (member is null) return StatusCode(403, "You are not even a member of the targeted publisher.");
|
||||
if (member.Role < PublisherMemberRole.Manager)
|
||||
return StatusCode(403, "You need at least be a manager to invite other members to collaborate this publisher.");
|
||||
return StatusCode(403,
|
||||
"You need at least be a manager to invite other members to collaborate this publisher.");
|
||||
if (member.Role < request.Role)
|
||||
return StatusCode(403, "You cannot invite member has higher permission than yours.");
|
||||
|
||||
@ -98,10 +101,10 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
PublisherId = publisher.Id,
|
||||
Role = request.Role,
|
||||
};
|
||||
|
||||
|
||||
db.PublisherMembers.Add(newMember);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
return Ok(newMember);
|
||||
}
|
||||
|
||||
@ -111,35 +114,35 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
var userId = currentUser.Id;
|
||||
|
||||
|
||||
var member = await db.PublisherMembers
|
||||
.Where(m => m.AccountId == userId)
|
||||
.Where(m => m.Publisher.Name == name)
|
||||
.Where(m => m.JoinedAt == null)
|
||||
.FirstOrDefaultAsync();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
|
||||
member.JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
db.Update(member);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
return Ok(member);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("invites/{name}/decline")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> DeclineMemberInvite(string name)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
var userId = currentUser.Id;
|
||||
|
||||
|
||||
var member = await db.PublisherMembers
|
||||
.Where(m => m.AccountId == userId)
|
||||
.Where(m => m.Publisher.Name == name)
|
||||
.Where(m => m.JoinedAt == null)
|
||||
.FirstOrDefaultAsync();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
|
||||
db.PublisherMembers.Remove(member);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -161,6 +164,8 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
public async Task<ActionResult<Publisher>> CreatePublisherIndividual(PublisherRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
if (!await enforcer.EnforceAsync(currentUser.Id.ToString(), "global", "publishers", "create"))
|
||||
return StatusCode(403);
|
||||
|
||||
var takenName = request.Name ?? currentUser.Name;
|
||||
var duplicateNameCount = await db.Publishers
|
||||
@ -276,10 +281,10 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
await fs.MarkUsageAsync(publisher.Picture, -1);
|
||||
if (publisher.Background is not null)
|
||||
await fs.MarkUsageAsync(publisher.Background, -1);
|
||||
|
||||
|
||||
db.Publishers.Remove(publisher);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user