@page "/urls" @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Caching.Distributed @using System.ComponentModel.DataAnnotations @using System.Text.RegularExpressions @using System.Web @using Humanizer @using JustShortIt.Model @using Microsoft.AspNetCore.WebUtilities @using Microsoft.Extensions.Options @attribute [Authorize] @inject IOptions CustomizationOptions @inject IDistributedCache Db @inject IConfiguration Configuration @inject NavigationManager Navigation Urls - Just Short It

Urls Administration

Inspect URL

New URL

@foreach (var expiration in Enum.GetValues()) { }
@code { [SupplyParameterFromForm(FormName = "inspect")] public InspectModel Inspect { get; set; } = default!; [SupplyParameterFromForm(FormName = "new")] public NewModel New { get; set; } = default!; private string BaseUrl { get; set; } = null!; private string? Message { get; set; } private string? Link { get; set; } private MessageComponent.AlertType MessageType { get; set; } protected override void OnInitialized() { // ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract Inspect ??= new InspectModel(); New ??= new NewModel(); // ReSharper restore NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract New.Id ??= GenerateNewId(); string url = Configuration.GetValue("BaseUrl") ?? throw new ApplicationException("BaseUrl not set"); BaseUrl = new Uri(url, UriKind.Absolute).ToString(); } private async Task Submit_Inspect(EditContext context) { if (string.IsNullOrWhiteSpace(Inspect.Id)) return; if (await Db.GetAsync(Inspect.Id) is null) { Message = "ID does not exist"; MessageType = MessageComponent.AlertType.Error; return; } Navigation.NavigateTo(QueryHelpers.AddQueryString("/inspect", "Id", Inspect.Id)); } private async Task Submit_New() { if (string.IsNullOrWhiteSpace(New.Id)) return; string id = HttpUtility.UrlEncode(New.Id); if (await Db.GetAsync(id) is not null) { Message = "This ID is already taken, sorry!"; MessageType = MessageComponent.AlertType.Error; return; } if (Uri.TryCreate($"{BaseUrl}{id}", UriKind.Absolute, out var link) is false) { Message = "This ID cannot be used in a URL, sorry!"; MessageType = MessageComponent.AlertType.Error; return; } await Db.SetStringAsync(id, New.Url!, new DistributedCacheEntryOptions { AbsoluteExpiration = DateTime.FromBinary(ToUnixTime(New.RedirectExpiration!.Value)) }); Message = $"URL Generated! {link}"; Link = link.ToString(); MessageType = MessageComponent.AlertType.Success; New = new(); New.Id = GenerateNewId(); } private string GenerateNewId() { string base64Guid = Regex.Replace( Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", ""); int length = Math.Min(Math.Max(CustomizationOptions.Value.GeneratedIdLength, 2), 16); return base64Guid[..length]; } #region Models public sealed class InspectModel { [Required(AllowEmptyStrings = false, ErrorMessage = "Id is required.")] [MinLength(2, ErrorMessage = "Id needs to be at least 2 characters long.")] [MaxLength(16, ErrorMessage = "Id needs to be at maximum 16 characters long.")] public string? Id { get; set; } } public sealed class NewModel { [Required(AllowEmptyStrings = false, ErrorMessage = "Id is required.")] [MinLength(2, ErrorMessage = "Id needs to be at least 2 characters long.")] [MaxLength(16, ErrorMessage = "Id needs to be at maximum 16 characters long.")] public string? Id { get; set; } [Required(AllowEmptyStrings = false, ErrorMessage = "Target is required.")] [Url(ErrorMessage = "Target needs to be a valid URL.")] public string? Url { get; set; } [Required(ErrorMessage = "Expiration is required.")] public Expiration? RedirectExpiration { get; set; } } public enum Expiration { OneDay, OneWeek, FourWeeks, OneMonth, OneYear, Never } private static long ToUnixTime(Expiration expiration) => expiration switch{ Expiration.OneDay => DateTime.UtcNow.AddDays(1).ToBinary(), Expiration.OneWeek => DateTime.UtcNow.AddDays(7).ToBinary(), Expiration.FourWeeks => DateTime.UtcNow.AddDays(4*7).ToBinary(), Expiration.OneYear => DateTime.UtcNow.AddYears(1).ToBinary(), Expiration.Never => DateTime.UtcNow.AddYears(1000).ToBinary(), _ => throw new ArgumentOutOfRangeException(nameof(expiration), expiration, null)}; #endregion }