using System.Security.Cryptography; using System.Text; namespace Fitness_core_BE.Helpers { // Short, shareable /sandbox/{token} code — replaces the old raw Guid. Made of a // time-derived prefix (minutes since a fixed epoch, base62) so codes drift forward // over time, plus a random suffix for uniqueness among requests created in the same // minute. Not a security boundary: the sandbox is a temporary trial state, not a // login credential. public static class SandboxTokenHelper { private const string Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private const int TimePartLength = 5; private const int RandomPartLength = 5; private static readonly DateTime Epoch = new(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static string Generate(DateTime createdAtUtc) { long minutesSinceEpoch = Math.Max(0, (long)(createdAtUtc - Epoch).TotalMinutes); string timePart = ToBaseAlphabet(minutesSinceEpoch).PadLeft(TimePartLength, '0'); if (timePart.Length > TimePartLength) timePart = timePart[^TimePartLength..]; return timePart + RandomSuffix(RandomPartLength); } private static string RandomSuffix(int length) { var bytes = new byte[length]; RandomNumberGenerator.Fill(bytes); var sb = new StringBuilder(length); foreach (var b in bytes) sb.Append(Alphabet[b % Alphabet.Length]); return sb.ToString(); } private static string ToBaseAlphabet(long value) { if (value <= 0) return "0"; var sb = new StringBuilder(); while (value > 0) { sb.Insert(0, Alphabet[(int)(value % Alphabet.Length)]); value /= Alphabet.Length; } return sb.ToString(); } } }