Skip to content

Commit

Permalink
Add short links management + fix hit tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
DominicMaas committed Jan 14, 2025
1 parent 6f1fb84 commit ff71a20
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 78 deletions.
43 changes: 16 additions & 27 deletions src/Website/Controllers/ImagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,12 @@ namespace Website.Controllers;

[Authorize]
[Route("admin/images")]
public class ImagesController : Controller
public class ImagesController(DatabaseContext context, R2 r2, ILogger<ImagesController> logger) : Controller
{
private readonly DatabaseContext _context;
private readonly R2 _r2;
private readonly ILogger<ImagesController> _logger;

public ImagesController(DatabaseContext context, R2 r2, ILogger<ImagesController> logger)
{
_context = context;
_r2 = r2;
_logger = logger;
}

[HttpGet]
public async Task<IActionResult> Index()
{
return View(await _context.Images.ToListAsync());
return View(await context.Images.ToListAsync());
}

[HttpGet("upload")]
Expand Down Expand Up @@ -75,7 +64,7 @@ public async Task<IActionResult> Upload([Bind("DateTaken,Description,ImageFile")
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while processing an image");
logger.LogError(ex, "An error occurred while processing an image");

ModelState.AddModelError(nameof(imageUpload.ImageFile), "An error occurred while processing this image. Please try again later.");
return View(imageUpload);
Expand All @@ -94,11 +83,11 @@ public async Task<IActionResult> Upload([Bind("DateTaken,Description,ImageFile")
try
{
// Attempt to upload the image
await _r2.UploadImageAsync(imageStream, $"i/{imageUpload.Id}.jpg", "image/jpeg", cancellationToken);
await r2.UploadImageAsync(imageStream, $"i/{imageUpload.Id}.jpg", "image/jpeg", cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while uploading an image to R2");
logger.LogError(ex, "An error occurred while uploading an image to R2");

ModelState.AddModelError(nameof(imageUpload.ImageFile), "An error occurred while uploading this image to R2. Please try again later.");
return View(imageUpload);
Expand All @@ -111,8 +100,8 @@ public async Task<IActionResult> Upload([Bind("DateTaken,Description,ImageFile")
return View(imageUpload);
}

_context.Add(imageUpload);
await _context.SaveChangesAsync(cancellationToken);
context.Add(imageUpload);
await context.SaveChangesAsync(cancellationToken);
return RedirectToAction(nameof(Index));
}

Expand All @@ -124,7 +113,7 @@ public async Task<IActionResult> Edit(Guid? id)
return NotFound();
}

var image = await _context.Images.FindAsync(id);
var image = await context.Images.FindAsync(id);
if (image == null)
{
return NotFound();
Expand All @@ -145,8 +134,8 @@ public async Task<IActionResult> Edit(Guid id, [Bind("DateTaken,Description")] I
{
try
{
_context.Update(image);
await _context.SaveChangesAsync();
context.Update(image);
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
Expand All @@ -173,7 +162,7 @@ public async Task<IActionResult> Delete(Guid? id, CancellationToken cancellation
return NotFound();
}

var image = await _context.Images.FirstOrDefaultAsync(m => m.Id == id, cancellationToken);
var image = await context.Images.FirstOrDefaultAsync(m => m.Id == id, cancellationToken);
if (image == null)
{
return NotFound();
Expand All @@ -183,16 +172,16 @@ public async Task<IActionResult> Delete(Guid? id, CancellationToken cancellation

try
{
await _r2.DeleteImageAsync($"i/{image.Id}.jpg", cancellationToken);
await r2.DeleteImageAsync($"i/{image.Id}.jpg", cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while deleting an image from R2");
logger.LogError(ex, "An error occurred while deleting an image from R2");
return NotFound();
}

_context.Images.Remove(image);
await _context.SaveChangesAsync(cancellationToken);
context.Images.Remove(image);
await context.SaveChangesAsync(cancellationToken);

Response.Htmx(headers =>
{
Expand All @@ -204,7 +193,7 @@ public async Task<IActionResult> Delete(Guid? id, CancellationToken cancellation

private bool ImageExists(Guid id)
{
return _context.Images.Any(e => e.Id == id);
return context.Images.Any(e => e.Id == id);
}

public class ImageUpload : Image
Expand Down
13 changes: 3 additions & 10 deletions src/Website/Controllers/RSSController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,19 @@

namespace Website.Controllers;

public class RSSController : Controller
public class RSSController(DatabaseContext context) : Controller
{
private readonly DatabaseContext _context;

public RSSController(DatabaseContext context)
{
_context = context;
}

[ResponseCache(Duration = 1200)]
[HttpGet("/feed/stream.xml")]
public async Task<IActionResult> StreamRSSAsync()
{
var latestStream = await _context.Streams.OrderByDescending(x => x.Posted).FirstOrDefaultAsync();
var latestStream = await context.Streams.OrderByDescending(x => x.Posted).FirstOrDefaultAsync();

var feed = BuildBasicFeed("Stream", "Quick thoughts and ideas", new("https://dominicmaas.co.nz/feed/stream.xml"), latestStream?.Posted ?? default);
var items = new List<SyndicationItem>();


var streams = await _context.Streams.OrderByDescending(x => x.Posted).Take(20).ToListAsync();
var streams = await context.Streams.OrderByDescending(x => x.Posted).Take(20).ToListAsync();
foreach (var stream in streams)
{
items.Add(new SyndicationItem(stream.Title, stream.Content, new Uri($"https://dominicmaas.co.nz/stream/{stream.Id}"), stream.Id.ToString(), stream.Posted));
Expand Down
125 changes: 125 additions & 0 deletions src/Website/Controllers/ShortLinksController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using Htmx;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using Website.Common;
using Website.Models.Database;

namespace Website.Controllers;

[Authorize]
[Route("admin/short-links")]
public class ShortLinksController(DatabaseContext context) : Controller
{
[HttpGet]
public async Task<IActionResult> Index()
{
return View(await context.ShortLinks.Select(x => new ShortLink
{
Id = x.Id,
RedirectLink = x.RedirectLink,
HitsCount = x.Hits.Count()
}).ToListAsync());
}

[HttpGet("create")]
public IActionResult Create()
{
return View();
}

// POST: AdminShortLinks/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost("create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,RedirectLink")] ShortLink shortLink)
{
if (ModelState.IsValid)
{
context.Add(shortLink);
await context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(shortLink);
}

[HttpGet("{id}/edit")]
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}

var shortLink = await context.ShortLinks.FindAsync(id);
if (shortLink == null)
{
return NotFound();
}
return View(shortLink);
}

// POST: AdminShortLinks/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost("{id}/edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Id,RedirectLink")] ShortLink shortLink)
{
if (id != shortLink.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
context.Update(shortLink);
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ShortLinkExists(shortLink.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(shortLink);
}


[HttpPost("{id}/delete"), ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
var shortLink = await context.ShortLinks.FindAsync(id);
if (shortLink == null)
{
return NotFound();
}

context.ShortLinks.Remove(shortLink);
await context.SaveChangesAsync();

Response.Htmx(headers =>
{
headers.Refresh();
});

return Ok();
}

private bool ShortLinkExists(string id)
{
return context.ShortLinks.Any(e => e.Id == id);
}
}
28 changes: 9 additions & 19 deletions src/Website/Controllers/SoundbyteAppController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Website.Controllers;
[Produces("application/json")]
[Route("api/soundbyte")]
[ApiController]
public class SoundbyteAppController : Controller
public class SoundbyteAppController(IConfiguration config, SoundByteAuthenticationService soundByteAuthenticationService) : Controller
{
// These fields are already public within the soundbyte app and code (the client secret is extracted)

Expand All @@ -21,16 +21,6 @@ public class SoundbyteAppController : Controller
public const string YouTubeConnectUrl = "https://accounts.google.com/o/oauth2/token";
public const string YouTubeRedirecturl = "http://localhost/soundbyte";

private readonly IConfiguration _config;
private readonly SoundByteAuthenticationService _soundByteAuthenticationService;


public SoundbyteAppController(IConfiguration config, SoundByteAuthenticationService soundByteAuthenticationService)
{
_config = config;
_soundByteAuthenticationService = soundByteAuthenticationService;
}

[HttpPost("auth/youtube")]
public async Task<IActionResult> AuthYouTube([FromForm] string code, CancellationToken cancellationToken)
{
Expand All @@ -44,7 +34,7 @@ public async Task<IActionResult> AuthYouTube([FromForm] string code, Cancellatio
});
}

var secret = _config["SoundByteAuth:YouTubeClientSecret"];
var secret = config["SoundByteAuth:YouTubeClientSecret"];
if (string.IsNullOrEmpty(secret))
{
return new JsonResult(new
Expand All @@ -55,7 +45,7 @@ public async Task<IActionResult> AuthYouTube([FromForm] string code, Cancellatio
}

// Perform the actual request
var result = await _soundByteAuthenticationService.GetTokenAsync(YouTubeClientId, secret, YouTubeRedirecturl,
var result = await soundByteAuthenticationService.GetTokenAsync(YouTubeClientId, secret, YouTubeRedirecturl,
code, YouTubeConnectUrl, "authorization_code", cancellationToken);

return new JsonResult(new
Expand Down Expand Up @@ -83,7 +73,7 @@ public async Task<IActionResult> AuthSoundCloud([FromForm] string code, Cancella
});
}

var secret = _config["SoundByteAuth:SoundCloudClientSecret"];
var secret = config["SoundByteAuth:SoundCloudClientSecret"];
if (string.IsNullOrEmpty(secret))
{
return new JsonResult(new
Expand All @@ -94,7 +84,7 @@ public async Task<IActionResult> AuthSoundCloud([FromForm] string code, Cancella
}

// Perform the actual request
var result = await _soundByteAuthenticationService.GetTokenAsync(SoundCloudClientId, secret, SoundCloudRedirecturl,
var result = await soundByteAuthenticationService.GetTokenAsync(SoundCloudClientId, secret, SoundCloudRedirecturl,
code, SoundCloudConnectUrl, "authorization_code", cancellationToken);

return new JsonResult(new
Expand Down Expand Up @@ -122,7 +112,7 @@ public async Task<IActionResult> RefreshAuthYouTube([FromForm(Name = "refresh_to
});
}

var secret = _config["SoundByteAuth:YouTubeClientSecret"];
var secret = config["SoundByteAuth:YouTubeClientSecret"];
if (string.IsNullOrEmpty(secret))
{
return new JsonResult(new
Expand All @@ -133,7 +123,7 @@ public async Task<IActionResult> RefreshAuthYouTube([FromForm(Name = "refresh_to
}

// Perform the actual request
var result = await _soundByteAuthenticationService.GetTokenAsync(YouTubeClientId, secret, YouTubeRedirecturl,
var result = await soundByteAuthenticationService.GetTokenAsync(YouTubeClientId, secret, YouTubeRedirecturl,
refreshToken, YouTubeConnectUrl, "refresh_token", cancellationToken);

return new JsonResult(new
Expand Down Expand Up @@ -161,7 +151,7 @@ public async Task<IActionResult> RefreshAuthSoundCloud([FromForm(Name = "refresh
});
}

var secret = _config["SoundByteAuth:SoundCloudClientSecret"];
var secret = config["SoundByteAuth:SoundCloudClientSecret"];
if (string.IsNullOrEmpty(secret))
{
return new JsonResult(new
Expand All @@ -172,7 +162,7 @@ public async Task<IActionResult> RefreshAuthSoundCloud([FromForm(Name = "refresh
}

// Perform the actual request
var result = await _soundByteAuthenticationService.GetTokenAsync(SoundCloudClientId, secret, SoundCloudRedirecturl,
var result = await soundByteAuthenticationService.GetTokenAsync(SoundCloudClientId, secret, SoundCloudRedirecturl,
refreshToken, SoundCloudConnectUrl, "refresh_token", cancellationToken);

return new JsonResult(new
Expand Down
Loading

0 comments on commit ff71a20

Please sign in to comment.