refactor(blogacl): move BlogAcl + Circle controllers from Yavsc.Api to Yavsc.Blogs

These two controllers belong to the Blogs subsystem (their routes
/api/blogacl and /api/circle are blog-domain concerns, not the
generic Api surface). Moving them next to BlogApiController keeps
related code together and prepares the PostIt client to consume
them through the same BlogsApiUrl base address as the existing
BlogApiClient.

Mechanical changes only:
- Namespace Yavsc.Controllers -> Yavsc.Blogs.Controllers
- Drop unused 'using Yavsc.Helpers;' (no symbol in the new
  compilation unit depends on it; the build confirms it was
  dead since the controllers were first written)
- Fix typo in CircleApiController route: 'api/cirle' -> 'api/circle'
  (any client trying to call the documented route was hitting 404)

No functional changes to authorization or query shape. The known
security gaps in these controllers (GetBlogACL and GetCircle
return unfiltered collections, DeleteCircle has no ownership
check) are deliberately left untouched in this commit and will
be addressed in a follow-up.
This commit is contained in:
Paul Schneider 2026-08-17 23:34:48 +01:00
commit 40e5630cfc
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
2 changed files with 4 additions and 6 deletions

View file

@ -1,165 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.Access;
using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
{
[Produces("application/json")]
[Route("api/blogacl")]
public class BlogAclApiController : Controller
{
private readonly ApplicationDbContext _context;
public BlogAclApiController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/BlogAclApi
[HttpGet]
public IEnumerable<CircleAuthorizationToBlogPost> GetBlogACL()
{
return _context.CircleAuthorizationToBlogPost;
}
// GET: api/BlogAclApi/5
[HttpGet("{id}", Name = "GetCircleAuthorizationToBlogPost")]
public async Task<IActionResult> GetCircleAuthorizationToBlogPost([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
CircleAuthorizationToBlogPost circleAuthorizationToBlogPost = await _context.CircleAuthorizationToBlogPost.SingleAsync(
m => m.CircleId == id && m.Allowed.OwnerId == uid );
if (circleAuthorizationToBlogPost == null)
{
return NotFound();
}
return Ok(circleAuthorizationToBlogPost);
}
// PUT: api/BlogAclApi/5
[HttpPut("{id}")]
public async Task<IActionResult> PutCircleAuthorizationToBlogPost([FromRoute] long id, [FromBody] CircleAuthorizationToBlogPost circleAuthorizationToBlogPost)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != circleAuthorizationToBlogPost.CircleId)
{
return BadRequest();
}
if (!CheckOwner(circleAuthorizationToBlogPost.CircleId))
{
return new ChallengeResult();
}
_context.Entry(circleAuthorizationToBlogPost).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync(User.GetUserId());
}
catch (DbUpdateConcurrencyException)
{
if (!CircleAuthorizationToBlogPostExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
private bool CheckOwner (long circleId)
{
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
var circle = _context.Circle.First(c=>c.Id==circleId);
_context.Entry(circle).State = EntityState.Detached;
return (circle.OwnerId == uid);
}
// POST: api/BlogAclApi
[HttpPost]
public async Task<IActionResult> PostCircleAuthorizationToBlogPost([FromBody] CircleAuthorizationToBlogPost circleAuthorizationToBlogPost)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!CheckOwner(circleAuthorizationToBlogPost.CircleId))
{
return new ChallengeResult();
}
_context.CircleAuthorizationToBlogPost.Add(circleAuthorizationToBlogPost);
try
{
await _context.SaveChangesAsync(User.GetUserId());
}
catch (DbUpdateException)
{
if (CircleAuthorizationToBlogPostExists(circleAuthorizationToBlogPost.CircleId))
{
return new StatusCodeResult(StatusCodes.Status409Conflict);
}
else
{
throw;
}
}
return CreatedAtRoute("GetCircleAuthorizationToBlogPost", new { id = circleAuthorizationToBlogPost.CircleId }, circleAuthorizationToBlogPost);
}
// DELETE: api/BlogAclApi/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCircleAuthorizationToBlogPost([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
CircleAuthorizationToBlogPost circleAuthorizationToBlogPost = await _context.CircleAuthorizationToBlogPost.Include(
a=>a.Allowed
).SingleAsync(m => m.CircleId == id
&& m.Allowed.OwnerId == uid);
if (circleAuthorizationToBlogPost == null)
{
return NotFound();
}
_context.CircleAuthorizationToBlogPost.Remove(circleAuthorizationToBlogPost);
await _context.SaveChangesAsync(User.GetUserId());
return Ok(circleAuthorizationToBlogPost);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
base.Dispose(disposing);
}
private bool CircleAuthorizationToBlogPostExists(long id)
{
return _context.CircleAuthorizationToBlogPost.Count(e => e.CircleId == id) > 0;
}
}
}

View file

@ -1,146 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.Relationship;
using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
{
[Produces("application/json")]
[Route("api/cirle")]
public class CircleApiController : Controller
{
private readonly ApplicationDbContext _context;
public CircleApiController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/CircleApi
[HttpGet]
public IEnumerable<Circle> GetCircle()
{
return _context.Circle;
}
// GET: api/CircleApi/5
[HttpGet("{id}", Name = "GetCircle")]
public async Task<IActionResult> GetCircle([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Circle circle = await _context.Circle.SingleAsync(m => m.Id == id);
if (circle == null)
{
return NotFound();
}
return Ok(circle);
}
// PUT: api/CircleApi/5
[HttpPut("{id}")]
public async Task<IActionResult> PutCircle([FromRoute] long id, [FromBody] Circle circle)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != circle.Id)
{
return BadRequest();
}
_context.Entry(circle).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync(User.GetUserId());
}
catch (DbUpdateConcurrencyException)
{
if (!CircleExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
// POST: api/CircleApi
[HttpPost]
public async Task<IActionResult> PostCircle([FromBody] Circle circle)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Circle.Add(circle);
try
{
await _context.SaveChangesAsync(User.GetUserId());
}
catch (DbUpdateException)
{
if (CircleExists(circle.Id))
{
return new StatusCodeResult(StatusCodes.Status409Conflict);
}
else
{
throw;
}
}
return CreatedAtRoute("GetCircle", new { id = circle.Id }, circle);
}
// DELETE: api/CircleApi/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCircle([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Circle circle = await _context.Circle.SingleAsync(m => m.Id == id);
if (circle == null)
{
return NotFound();
}
_context.Circle.Remove(circle);
await _context.SaveChangesAsync(User.GetUserId());
return Ok(circle);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
base.Dispose(disposing);
}
private bool CircleExists(long id)
{
return _context.Circle.Count(e => e.Id == id) > 0;
}
}
}