global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.Data.SqlClient; global using System.Text; global using System.Text.Json.Nodes; global using static perubahan.Commons; global using static perubahan.Logging; global using static perubahan.Middlewares; global using static perubahan.Regices; global using System.Collections.Concurrent; global using System.Security.Cryptography; using System.Text.Json; using System.Runtime.InteropServices; namespace perubahan; internal static class Commons { internal readonly static string VerNum = "0.1.250515.1559"; internal static ConcurrentDictionary UserAccounts = []; internal static List Deployments = []; internal static List Agents = []; internal static readonly string SecretKey = "userandomlaterlikethecommented"; //RandomNumberGenerator.GetHexString(32); internal static string CS = string.Empty ; internal static JsonNode Settings = JsonNode.Parse("{}")!; internal static readonly CookieOptions HttpOnly; internal static readonly CookieOptions Delete; // internal static EventStore EventsMarker = new (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString("X"),DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString("X")); internal static readonly CancellationTokenSource CTS = new(); static Commons() { Delete = new () { Domain = "", Path = "/", Expires = DateTime.Parse("2023-06-14") }; HttpOnly = new () { Domain = "", Path = "/", HttpOnly = true, IsEssential = true }; Console.CancelKeyPress += (sender, e) => { e.Cancel = true; // Prevents immediate termination CTS.Cancel(); }; AppDomain.CurrentDomain.ProcessExit += (sender, e) => { CTS.Cancel(); }; } internal static async Task RunNonQueryAsync(string ConnectionString, string SQL, Action? Configure = null, CancellationToken Token = default) { await using SqlConnection Conn = new(ConnectionString); await Conn.OpenAsync(Token); await using SqlCommand Cmd = new(SQL, Conn); Configure?.Invoke(Cmd); return await Cmd.ExecuteNonQueryAsync(Token); } internal static async Task RunReaderAsync(string ConnectionString, string SQL, Action? Configure = null, CancellationToken Token = default) { SqlConnection Conn = new(ConnectionString); await Conn.OpenAsync(Token); SqlCommand Cmd = new(SQL, Conn); Configure?.Invoke(Cmd); return await Cmd.ExecuteReaderAsync(System.Data.CommandBehavior.CloseConnection, Token); } internal static async Task RunScalarAsync(string ConnectionString, string SQL, Action? Configure = null, CancellationToken Token = default) { await using SqlConnection Conn = new(ConnectionString); await Conn.OpenAsync(Token); await using SqlCommand Cmd = new(SQL, Conn); Configure?.Invoke(Cmd); return await Cmd.ExecuteScalarAsync(Token); } public static async Task RunTransactionAsync(string ConnStr, Func Logic, CancellationToken Token) { using SqlConnection Conn = new(ConnStr); await Conn.OpenAsync(Token); using SqlTransaction Tran = Conn.BeginTransaction(); try { await Logic(Conn, Tran); await Tran.CommitAsync(Token); } catch (SqlException) { await Tran.RollbackAsync(Token); throw; // if (ex.State != 255) throw; //state = 255 is custom sql error, designed just for this purpose of not rethrowing. } catch { throw; } } internal static string GenerateUuidV7() { Span uuidBytes = stackalloc byte[16]; long time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); RandomNumberGenerator.Fill(uuidBytes[7..]); if (BitConverter.IsLittleEndian) { MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref time, 1))[..6].CopyTo(uuidBytes[..6]); uuidBytes[..6].Reverse(); } else { MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref time, 1))[2..8].CopyTo(uuidBytes[..6]); } uuidBytes[6] = (byte)((uuidBytes[6] & 0x0F) | 0x70); // Set version to 7 uuidBytes[8] = (byte)((uuidBytes[8] & 0x3F) | 0x80); // Set variant to 10xx return Convert.ToHexString(uuidBytes).ToLower().Insert(8, "-").Insert(13, "-").Insert(18, "-").Insert(23, "-"); } internal static Dictionary? JsonElementToDict(JsonElement Element, string[] Keys) { Dictionary? Result = new(StringComparer.OrdinalIgnoreCase); foreach (string Key in Keys) { if (!Element.TryGetProperty(Key, out JsonElement Value)) { return null; } Result[Key] = Value.Clone(); } return Result; } internal static async Task UpdateCache() { Console.WriteLine("Updating app cache."); Console.Write(" Caching User Accounts... "); await using SqlConnection FConn = new(CS); await FConn.OpenAsync().ConfigureAwait(false); using SqlCommand FComm = new("SELECT * FROM useraccounts",FConn); await using (SqlDataReader URead = await FComm.ExecuteReaderAsync(CTS.Token).ConfigureAwait(false)) { UserAccounts.Clear(); while (await URead.ReadAsync(CTS.Token).ConfigureAwait(false)) { _ = UserAccounts.TryAdd((string)URead["uname"], new User((string)URead["uname"], (string)URead["agentid"], (string)URead["pass"], (byte)URead["level"], (bool)URead["active"])); } } Console.WriteLine("Done."); Console.Write(" Caching Unit Kerja... "); FComm.CommandText = "SELECT * FROM deployment"; await using (SqlDataReader SRead = await FComm.ExecuteReaderAsync(CTS.Token).ConfigureAwait(false)) { Deployments = await SRead.ToListAsync(r=>new( (short)r["deplid"], (string)r["unitkerja"] ),CTS.Token); } Console.WriteLine("Done."); Console.Write(" Caching Agents Info... "); FComm.CommandText = "SELECT * FROM agents"; await using (SqlDataReader SRead = await FComm.ExecuteReaderAsync(CTS.Token).ConfigureAwait(false)) { Agents = await SRead.ToListAsync(r=>new( (string)r["agentid"], (string)r["name"], (string)r["jabatan"], (short)r["deplid"], (string)r["skangkat"], DateOnly.FromDateTime((DateTime)r["tmt"]), r["skperubahan"] == DBNull.Value ? null : (string)r["skperubahan"], r["tgperubahan"] == DBNull.Value ? null : DateOnly.FromDateTime((DateTime)r["tgperubahan"]), r["vision"] == DBNull.Value ? null : (string)r["vision"], r["mission"] == DBNull.Value ? null : (string)r["mission"], r["photourl"] == DBNull.Value ? null : (string)r["photourl"] ),CTS.Token); } Console.WriteLine("Done."); Console.WriteLine("App cache updated."); return; } } internal static class Logging { private static readonly object Lock = new(); internal static async void WriteLog(Exception ex, string location = "") { await Task.Run(()=>{ string Time = $"{DateTime.Now:yyy-MM-dd HH:mm:ss}"; string msg = $"{Time} [{"Error".PadRight(7)[0..7]}] {ex.Message}{(location.Length > 0 ? $" {location}" : "")}{Environment.NewLine}"; Console.Write(msg); try { byte[] msgbytes = Encoding.UTF8.GetBytes(msg); lock (Lock) { using FileStream LogWriter = new($"{AppContext.BaseDirectory}/{DateTime.Now:yyyyMMdd}.log", FileMode.Append); LogWriter.Write(msgbytes, 0, msgbytes.Length); } } finally { } }); } internal static async void WriteLog(string exm, string location = "") { await Task.Run(()=>{ string Time = $"{DateTime.Now:yyy-MM-dd HH:mm:ss}"; string msg = $"{Time} [{"Info".PadRight(7)[0..7]}] {exm}{(location.Length > 0 ? $" {location}" : "")}{Environment.NewLine}"; Console.Write(msg); try { byte[] msgbytes = Encoding.UTF8.GetBytes(msg); lock (Lock) { using FileStream LogWriter = new($"{AppContext.BaseDirectory}/{DateTime.Now:yyyyMMdd}.log", FileMode.Append); LogWriter.Write(msgbytes, 0, msgbytes.Length); } } finally { } }); } } internal static class DataReaderExtensions { internal static async Task> ToListAsync( this SqlDataReader reader, Func map, CancellationToken cancellationToken = default) { List list = []; while (await reader.ReadAsync(cancellationToken)) { list.Add(map(reader)); } return list; } } internal static class HttpContextExtensions { internal static async Task RequestValidated(this HttpContext Context, int RequiredLevel = 0, string ValidMethod = "GET", bool CheckJson = false) { if (!ValidMethod.Equals(Context.Request.Method, StringComparison.OrdinalIgnoreCase)) { await Context.WriteJsonResponse(StatusCodes.Status405MethodNotAllowed, "Method Not Allowed."); return false; } if (CheckJson && !Context.Request.HasJsonContentType()) { await Context.WriteJsonResponse(StatusCodes.Status415UnsupportedMediaType, $"Supports only explicitly set application/json content-type, but received {Context.Request.ContentType ?? "request with no content-type set"} instead."); return false; } if (!Auth.IsAuthorized(Context, RequiredLevel)) { await Context.WriteJsonResponse(StatusCodes.Status401Unauthorized, "Unauthorized."); return false; } return true; } internal static async Task RequestValidated(this HttpContext Context, string RequiredUserName, string ValidMethod = "GET", bool CheckJson = false) { if (!ValidMethod.Equals(Context.Request.Method, StringComparison.OrdinalIgnoreCase)) { await Context.WriteJsonResponse(StatusCodes.Status405MethodNotAllowed, "Method Not Allowed."); return false; } if (CheckJson && !Context.Request.HasJsonContentType()) { await Context.WriteJsonResponse(StatusCodes.Status415UnsupportedMediaType, $"Supports only explicitly set application/json content-type, but received {Context.Request.ContentType ?? "request with no content-type set"} instead."); return false; } if (!Auth.IsAuthorized(Context, RequiredUserName)) { await Context.WriteJsonResponse(StatusCodes.Status401Unauthorized, "Unauthorized."); return false; } return true; } internal static async Task WriteJsonResponse(this HttpContext Context, int Status, string Message, object Data) { Context.Response.StatusCode = Status; await Context.Response.WriteAsJsonAsync(new ApiResponse(Status, Message, Data), SGContext.Default.ApiResponse, cancellationToken: CTS.Token); } internal static async Task WriteJsonResponse(this HttpContext Context, int Status, string Message = "") { Context.Response.StatusCode = Status; await Context.Response.WriteAsJsonAsync(new SimpleApiResponse(Status, Message), SGContext.Default.SimpleApiResponse, cancellationToken: CTS.Token); } internal static async Task?> TryGetBodyJsonAsync(this HttpContext Context, string[] Keys, CancellationToken Token) { using JsonDocument BodyDoc = await JsonDocument.ParseAsync(Context.Request.Body,cancellationToken: Token); JsonElement Element = BodyDoc.RootElement; Dictionary? Result = new(StringComparer.OrdinalIgnoreCase); foreach (string Key in Keys) { if (!Element.TryGetProperty(Key, out JsonElement Value)) { await Context.WriteJsonResponse(StatusCodes.Status400BadRequest, "Bad Request. One or more required properties were not received."); return null; } Result[Key] = Value.Clone(); } return Result; } } public static class Crc32 { static readonly uint[] Table; static Crc32() { Table = new uint[256]; const uint poly = 0xEDB88320; for (uint i = 0; i < Table.Length; i++) { uint crc = i; for (int j = 0; j < 8; j++) crc = (crc & 1) != 0 ? (crc >> 1) ^ poly : crc >> 1; Table[i] = crc; } } public static uint Compute(byte[] bytes) { uint crc = 0xFFFFFFFF; foreach (byte b in bytes) crc = (crc >> 8) ^ Table[(crc ^ b) & 0xFF]; return ~crc; } }