diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..7d6736d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+TRIBUFU_API_KEY=""
+TRIBUFU_API_URL="https://api.tribufu.com"
diff --git a/.gitignore b/.gitignore
index cd402ad..114d915 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,14 @@
+.agents/
+.codex/
.vs/
bin/
+Binaries
+Intermediate/
obj/
+.DS_Store
+.env
*.filters
*.user
-.DS_Store
+*.lscache
+desktop.ini
diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore
new file mode 100644
index 0000000..1dc3f29
--- /dev/null
+++ b/.openapi-generator-ignore
@@ -0,0 +1,9 @@
+docs/
+
+.gitignore
+*.sln
+**.csproj
+api/openapi.yaml
+appveyor.yml
+git_push.sh
+README.md
diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES
new file mode 100644
index 0000000..7cb3a8d
--- /dev/null
+++ b/.openapi-generator/FILES
@@ -0,0 +1,67 @@
+src/Tribufu/Api/TribufuGeneratedApi.cs
+src/Tribufu/Client/ApiClient.cs
+src/Tribufu/Client/ApiException.cs
+src/Tribufu/Client/ApiResponse.cs
+src/Tribufu/Client/ClientUtils.cs
+src/Tribufu/Client/Configuration.cs
+src/Tribufu/Client/ExceptionFactory.cs
+src/Tribufu/Client/GlobalConfiguration.cs
+src/Tribufu/Client/HttpMethod.cs
+src/Tribufu/Client/IApiAccessor.cs
+src/Tribufu/Client/IAsynchronousClient.cs
+src/Tribufu/Client/IReadableConfiguration.cs
+src/Tribufu/Client/ISynchronousClient.cs
+src/Tribufu/Client/Multimap.cs
+src/Tribufu/Client/OpenAPIDateConverter.cs
+src/Tribufu/Client/RequestOptions.cs
+src/Tribufu/Client/RetryConfiguration.cs
+src/Tribufu/Model/AbstractOpenAPISchema.cs
+src/Tribufu/Model/Application.cs
+src/Tribufu/Model/ApplicationType.cs
+src/Tribufu/Model/AuthorizeRequest.cs
+src/Tribufu/Model/ClientInfo.cs
+src/Tribufu/Model/ClientType.cs
+src/Tribufu/Model/CodeChallengeMethod.cs
+src/Tribufu/Model/CodeResponse.cs
+src/Tribufu/Model/CreateUser.cs
+src/Tribufu/Model/CryptoViewModel.cs
+src/Tribufu/Model/ExternalAccount.cs
+src/Tribufu/Model/Game.cs
+src/Tribufu/Model/GameServer.cs
+src/Tribufu/Model/GameServerCluster.cs
+src/Tribufu/Model/GameServerQuery.cs
+src/Tribufu/Model/GameServerStatus.cs
+src/Tribufu/Model/GrantType.cs
+src/Tribufu/Model/Group.cs
+src/Tribufu/Model/GroupApplication.cs
+src/Tribufu/Model/GroupMember.cs
+src/Tribufu/Model/GroupRank.cs
+src/Tribufu/Model/HashViewModel.cs
+src/Tribufu/Model/IdentityProviderType.cs
+src/Tribufu/Model/IntrospectRequest.cs
+src/Tribufu/Model/IntrospectResponse.cs
+src/Tribufu/Model/IpAddress.cs
+src/Tribufu/Model/LeaderboardItem.cs
+src/Tribufu/Model/LeaderboardOrder.cs
+src/Tribufu/Model/ModelClient.cs
+src/Tribufu/Model/Package.cs
+src/Tribufu/Model/PackageRelease.cs
+src/Tribufu/Model/Product.cs
+src/Tribufu/Model/ProductPrice.cs
+src/Tribufu/Model/ProductType.cs
+src/Tribufu/Model/Profile.cs
+src/Tribufu/Model/ProfileGame.cs
+src/Tribufu/Model/ProfileGroup.cs
+src/Tribufu/Model/ResponseType.cs
+src/Tribufu/Model/RevokeRequest.cs
+src/Tribufu/Model/Search.cs
+src/Tribufu/Model/SearchType.cs
+src/Tribufu/Model/ServerMetrics.cs
+src/Tribufu/Model/StorageFile.cs
+src/Tribufu/Model/TokenHintType.cs
+src/Tribufu/Model/TokenRequest.cs
+src/Tribufu/Model/TokenResponse.cs
+src/Tribufu/Model/TokenType.cs
+src/Tribufu/Model/UpdateProfile.cs
+src/Tribufu/Model/UserInfo.cs
+src/Tribufu/Model/UserType.cs
diff --git a/.openapi-generator/VERSION b/.openapi-generator/VERSION
new file mode 100644
index 0000000..5f84a81
--- /dev/null
+++ b/.openapi-generator/VERSION
@@ -0,0 +1 @@
+7.12.0
diff --git a/Directory.Build.props b/Directory.Build.props
index 62f2166..5e893d6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,13 +1,21 @@
- 1.0.0.0
Tribufu
Tribufu
Copyright (c) Tribufu. All Rights Reserved.
- 1.0.0.0
MIT
git
- https://github.com/Tribufu/TribufuNet
- 1.0.0
+ https://github.com/Tribufu/TribufuDotNet
+ 1.3.0
+ $(Version).0
+ $(NoWarn);0618;1591;1998;2002;8767
+
+
+ $(SolutionDir)Intermediate\DotNet\$(MSBuildProjectName)\
+ $(BaseIntermediateOutputPath)$(Configuration)\
+ $(BaseIntermediateOutputPath)
+ $(IntermediateOutputPath)
+ $(SolutionDir)Binaries\$(Configuration)\DotNet\
+ $(SolutionDir)Intermediate\
diff --git a/Directory.Packages.props b/Directory.Packages.props
index cd87747..0640a39 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,8 +3,22 @@
true
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/README.md b/README.md
index 2d26ce1..a5bf230 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,14 @@
.NET SDK to access Tribufu APIs and services.
+[![nuget][nuget-badge]][nuget-url]
[![MIT License][mit-badge]][mit-url]
[![Discord Chat][discord-badge]][discord-url]
+[nuget-badge]: https://img.shields.io/nuget/v/Tribufu.svg
+[nuget-url]: https://www.nuget.org/packages/Tribufu
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
-[mit-url]: https://github.com/tribufu/sdk-dotnet/blob/main/LICENSE.txt
+[mit-url]: https://github.com/Tribufu/TribufuDotNet/blob/main/LICENSE.txt
[discord-badge]: https://img.shields.io/discord/276504514616623104.svg?logo=discord&style=flat-square
[discord-url]: https://www.tribufu.com/discord
@@ -17,4 +20,4 @@
This project is licensed under the [MIT License].
-[MIT License]: https://github.com/tribufu/sdk-dotnet/blob/main/LICENSE.txt
+[MIT License]: https://github.com/Tribufu/TribufuDotNet/blob/main/LICENSE.txt
diff --git a/Scripts/Generate.ps1 b/Scripts/Generate.ps1
new file mode 100644
index 0000000..abb11fc
--- /dev/null
+++ b/Scripts/Generate.ps1
@@ -0,0 +1,23 @@
+#!/usr/bin/env pwsh
+
+$ErrorActionPreference = "Stop"
+
+$env:CSHARP_POST_PROCESS_FILE = ""
+
+java -jar ./vendor/openapi-generator/openapi-generator-cli.jar generate `
+ -i https://api.tribufu.com/openapi.json `
+ -g csharp `
+ -o . `
+ --global-property apis,models,supportingFiles,apiDocs=false,modelDocs=false,apiTests=false,modelTests=false `
+ --additional-properties=packageName=Tribufu,library=restsharp,targetFramework=net47,zeroBasedEnums=true `
+ --openapi-normalizer SET_TAGS_FOR_ALL_OPERATIONS=TribufuGenerated `
+ --skip-validate-spec
+
+$srcDir = "./src"
+$sourceDir = "./Source"
+
+if (Test-Path $srcDir) {
+ New-Item -ItemType Directory -Force -Path $sourceDir | Out-Null
+ Copy-Item -Path (Join-Path $srcDir "*") -Destination $sourceDir -Recurse -Force
+ Remove-Item $srcDir -Recurse -Force
+}
diff --git a/Scripts/Package.ps1 b/Scripts/Package.ps1
new file mode 100644
index 0000000..658179e
--- /dev/null
+++ b/Scripts/Package.ps1
@@ -0,0 +1,7 @@
+#!/usr/bin/env pwsh
+
+Get-ChildItem ./Intermediate -Filter *.nupkg -Recurse -Force | Remove-Item -Force
+
+dotnet clean
+dotnet build -c Release
+dotnet pack -c Release -o ./Intermediate
diff --git a/Source/Tribufu.AspNetCore/Extensions/HttpContextExtensions.cs b/Source/Tribufu.AspNetCore/Extensions/HttpContextExtensions.cs
new file mode 100644
index 0000000..b65a12b
--- /dev/null
+++ b/Source/Tribufu.AspNetCore/Extensions/HttpContextExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: UNLICENSED
+
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Linq;
+
+namespace Tribufu.AspNetCore.Extensions
+{
+ public static class HttpContextExtensions
+ {
+ public static string GetIpAddress(this HttpContext context)
+ {
+ var headers = context.Request.Headers;
+ if (headers.TryGetValue("CF-Connecting-IP", out var cfConnectingIp))
+ {
+ return cfConnectingIp.FirstOrDefault() ?? "127.0.0.1";
+ }
+
+ if (headers.TryGetValue("X-Forwarded-For", out var xForwardedFor))
+ {
+ var forwardedIps = xForwardedFor.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries);
+
+ if (forwardedIps.Length > 0)
+ {
+ return forwardedIps[0].Trim();
+ }
+ }
+
+ return context.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
+ }
+ }
+}
diff --git a/Source/Tribufu.AspNetCore/README.md b/Source/Tribufu.AspNetCore/README.md
new file mode 100644
index 0000000..54d4fec
--- /dev/null
+++ b/Source/Tribufu.AspNetCore/README.md
@@ -0,0 +1 @@
+# Tribufu AspNetCore
diff --git a/Source/Tribufu.AspNetCore/Tribufu.AspNetCore.csproj b/Source/Tribufu.AspNetCore/Tribufu.AspNetCore.csproj
new file mode 100644
index 0000000..a739e7f
--- /dev/null
+++ b/Source/Tribufu.AspNetCore/Tribufu.AspNetCore.csproj
@@ -0,0 +1,28 @@
+
+
+ Tribufu.AspNetCore
+ Tribufu AspNetCore
+ README.md
+
+
+ Properties
+ true
+ false
+ enable
+ Library
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tribufu.ComponentModel/EnumMemberConverter.cs b/Source/Tribufu.ComponentModel/EnumMemberConverter.cs
similarity index 100%
rename from src/Tribufu.ComponentModel/EnumMemberConverter.cs
rename to Source/Tribufu.ComponentModel/EnumMemberConverter.cs
diff --git a/Source/Tribufu.ComponentModel/README.md b/Source/Tribufu.ComponentModel/README.md
new file mode 100644
index 0000000..f41a146
--- /dev/null
+++ b/Source/Tribufu.ComponentModel/README.md
@@ -0,0 +1 @@
+# Tribufu ComponentModel
diff --git a/src/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj b/Source/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj
similarity index 84%
rename from src/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj
rename to Source/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj
index c2368b5..05ddc36 100644
--- a/src/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj
+++ b/Source/Tribufu.ComponentModel/Tribufu.ComponentModel.csproj
@@ -1,14 +1,15 @@
Tribufu.ComponentModel
- Tribufu Component Model Helpers
+ Tribufu ComponentModel
README.md
- Library
Properties
- netstandard2.0;net45;net5.0
true
+ false
+ Library
+ netstandard2.0;net45;net5.0
diff --git a/Source/Tribufu.Configuration/ConfigurationLoader.cs b/Source/Tribufu.Configuration/ConfigurationLoader.cs
new file mode 100644
index 0000000..d23c03e
--- /dev/null
+++ b/Source/Tribufu.Configuration/ConfigurationLoader.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Microsoft.Extensions.Configuration;
+using System.IO;
+using Tomlyn.Extensions.Configuration;
+using Tribufu.Logging;
+using Tribufu.Platform;
+
+namespace Tribufu.Configuration
+{
+ public static class ConfigurationLoader
+ {
+ public static IConfiguration Load(string[] fileNames)
+ {
+ var configDirectory = Paths.GetApplicationConfigDirectory();
+ var configurationBuilder = new ConfigurationBuilder();
+ configurationBuilder.AddEnvironmentVariables();
+
+ foreach (var fileName in fileNames)
+ {
+ var fullPath = Path.Combine(configDirectory, fileName);
+ if (!File.Exists(fullPath))
+ {
+ Logger.Debug($"Config file '{fullPath}' not found, skipping.");
+ continue;
+ }
+
+ var extension = Path.GetExtension(fullPath).ToLowerInvariant();
+ switch (extension)
+ {
+ case ".ini":
+ configurationBuilder.AddIniFile(fullPath, true, false);
+ break;
+ case ".json":
+ configurationBuilder.AddJsonFile(fullPath, true, false);
+ break;
+ case ".toml":
+ configurationBuilder.AddTomlFile(fullPath, true, false);
+ break;
+ default:
+ Logger.Warn($"Unsupported config file extension: {extension}");
+ break;
+ }
+ }
+
+ return configurationBuilder.Build();
+ }
+ }
+}
diff --git a/Source/Tribufu.Configuration/Extensions/IConfigurationExtensions.cs b/Source/Tribufu.Configuration/Extensions/IConfigurationExtensions.cs
new file mode 100644
index 0000000..6c11899
--- /dev/null
+++ b/Source/Tribufu.Configuration/Extensions/IConfigurationExtensions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Microsoft.Extensions.Configuration;
+
+namespace Tribufu.Configuration.Extensions
+{
+ public static class IConfigurationExtensions
+ {
+ public static string? Get(this IConfiguration configuration, string prefix, string key)
+ {
+ var section = configuration.GetSection(prefix);
+ return !section.Exists() ? configuration[$"{prefix}_{key}"] : section[key];
+ }
+ }
+}
diff --git a/Source/Tribufu.Configuration/README.md b/Source/Tribufu.Configuration/README.md
new file mode 100644
index 0000000..8726fdd
--- /dev/null
+++ b/Source/Tribufu.Configuration/README.md
@@ -0,0 +1 @@
+# Tribufu Configuration
diff --git a/Source/Tribufu.Configuration/Tribufu.Configuration.csproj b/Source/Tribufu.Configuration/Tribufu.Configuration.csproj
new file mode 100644
index 0000000..3dc2aa8
--- /dev/null
+++ b/Source/Tribufu.Configuration/Tribufu.Configuration.csproj
@@ -0,0 +1,32 @@
+
+
+ Tribufu.Configuration
+ Tribufu Configuration
+ README.md
+
+
+ Properties
+ true
+ false
+ enable
+ Library
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Tribufu.EntityFrameworkCore/IRepository.cs b/Source/Tribufu.EntityFrameworkCore/IRepository.cs
new file mode 100644
index 0000000..0e00b08
--- /dev/null
+++ b/Source/Tribufu.EntityFrameworkCore/IRepository.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Tribufu.EntityFrameworkCore
+{
+ public interface IRepository where T : class
+ {
+ void Seed();
+
+ Task SeedAsync();
+
+ IList List();
+
+ Task> ListAsync();
+
+ IList List(uint page, uint limit);
+
+ Task> ListAsync(uint page, uint limit);
+
+ T? Find(K key);
+
+ Task FindAsync(K key);
+
+ T? Create(T entity);
+
+ Task CreateAsync(T entity);
+
+ T? Update(T entity);
+
+ Task UpdateAsync(T entity);
+
+ void Delete(K key);
+
+ Task DeleteAsync(K key);
+
+ void Delete(T entity);
+
+ Task DeleteAsync(T entity);
+ }
+}
diff --git a/Source/Tribufu.EntityFrameworkCore/README.md b/Source/Tribufu.EntityFrameworkCore/README.md
new file mode 100644
index 0000000..19c48b4
--- /dev/null
+++ b/Source/Tribufu.EntityFrameworkCore/README.md
@@ -0,0 +1 @@
+# Tribufu EntityFrameworkCore
diff --git a/Source/Tribufu.EntityFrameworkCore/Repository.cs b/Source/Tribufu.EntityFrameworkCore/Repository.cs
new file mode 100644
index 0000000..6851858
--- /dev/null
+++ b/Source/Tribufu.EntityFrameworkCore/Repository.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Tribufu.EntityFrameworkCore
+{
+ public class Repository : IRepository where C : DbContext where T : class
+ {
+ protected readonly C _dbContext;
+
+ protected readonly DbSet _dbSet;
+
+ public Repository(C dbContext)
+ {
+ _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+ _dbSet = dbContext.Set();
+ }
+
+ public virtual void Seed()
+ {
+ }
+
+ public virtual async Task SeedAsync()
+ {
+ }
+
+ public virtual IList List()
+ {
+ return [.. _dbSet];
+ }
+
+ public virtual async Task> ListAsync()
+ {
+ return await _dbSet.ToListAsync();
+ }
+
+ public virtual IList List(uint page, uint limit)
+ {
+ return _dbSet.Skip((int)((page < 1 ? 0 : page - 1) * limit)).Take((int)limit).ToList();
+ }
+
+ public virtual async Task> ListAsync(uint page, uint limit)
+ {
+ return await _dbSet.Skip((int)((page < 1 ? 0 : page - 1) * limit)).Take((int)limit).ToListAsync();
+ }
+
+ public virtual T? Find(K key)
+ {
+ return _dbSet.Find(key);
+ }
+
+ public virtual async Task FindAsync(K key)
+ {
+ return await _dbSet.FindAsync(key);
+ }
+
+ public virtual T? Create(T entity)
+ {
+ _dbSet.Add(entity);
+ var result = _dbContext.SaveChanges();
+ return result > 0 ? entity : null;
+ }
+
+ public virtual async Task CreateAsync(T entity)
+ {
+ await _dbSet.AddAsync(entity);
+ var result = await _dbContext.SaveChangesAsync();
+ return result > 0 ? entity : null;
+ }
+
+ public virtual T? Update(T entity)
+ {
+ _dbSet.Update(entity);
+ var result = _dbContext.SaveChanges();
+ return result > 0 ? entity : null;
+ }
+
+ public virtual async Task UpdateAsync(T entity)
+ {
+ _dbSet.Update(entity);
+ var result = await _dbContext.SaveChangesAsync();
+ return result > 0 ? entity : null;
+ }
+
+ public virtual void Delete(K key)
+ {
+ var entity = _dbSet.Find(key);
+ if (entity != null)
+ {
+ Delete(entity);
+ }
+ }
+
+ public virtual async Task DeleteAsync(K key)
+ {
+ var entity = await _dbSet.FindAsync(key);
+ if (entity != null)
+ {
+ await DeleteAsync(entity);
+ }
+ }
+
+ public virtual void Delete(T entity)
+ {
+ _dbSet.Remove(entity);
+ _dbContext.SaveChanges();
+ }
+
+ public virtual async Task DeleteAsync(T entity)
+ {
+ _dbSet.Remove(entity);
+ await _dbContext.SaveChangesAsync();
+ }
+ }
+}
diff --git a/Source/Tribufu.EntityFrameworkCore/Tribufu.EntityFrameworkCore.csproj b/Source/Tribufu.EntityFrameworkCore/Tribufu.EntityFrameworkCore.csproj
new file mode 100644
index 0000000..f591fd3
--- /dev/null
+++ b/Source/Tribufu.EntityFrameworkCore/Tribufu.EntityFrameworkCore.csproj
@@ -0,0 +1,26 @@
+
+
+ Tribufu.EntityFrameworkCore
+ Tribufu EntityFrameworkCore
+ README.md
+
+
+ Properties
+ true
+ false
+ enable
+ Library
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Tribufu.Example/Program.cs b/Source/Tribufu.Example/Program.cs
new file mode 100644
index 0000000..1dce3d9
--- /dev/null
+++ b/Source/Tribufu.Example/Program.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using dotenv.net;
+using System;
+using System.Threading.Tasks;
+using Tribufu.Client;
+using Tribufu.Logging;
+
+namespace Tribufu.Test
+{
+ public static class Program
+ {
+ public static async Task Main(string[] args)
+ {
+ Logger.Initialize(LogLevel.Trace);
+
+ DotEnv.Load(new DotEnvOptions(ignoreExceptions: true, envFilePaths: [".env", "../../.env"]));
+
+ var apiKey = Environment.GetEnvironmentVariable("TRIBUFU_API_KEY");
+ var tribufu = new TribufuApi(apiKey ?? "");
+
+ Logger.Debug(TribufuApi.GetUserAgent());
+
+ try
+ {
+ var result = await tribufu.GetUserInfoAsync();
+ Logger.Debug(result.ToString());
+ }
+ catch (ApiException e)
+ {
+ Logger.Debug(e.Message);
+ }
+ }
+ }
+}
diff --git a/Source/Tribufu.Example/Tribufu.Example.csproj b/Source/Tribufu.Example/Tribufu.Example.csproj
new file mode 100644
index 0000000..3bde082
--- /dev/null
+++ b/Source/Tribufu.Example/Tribufu.Example.csproj
@@ -0,0 +1,16 @@
+
+
+ false
+ false
+ enable
+ Exe
+ net8.0
+
+
+
+
+
+
+
+
+
diff --git a/Source/Tribufu.Logging/LogLevel.cs b/Source/Tribufu.Logging/LogLevel.cs
new file mode 100644
index 0000000..f8cea00
--- /dev/null
+++ b/Source/Tribufu.Logging/LogLevel.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+namespace Tribufu.Logging
+{
+ public enum LogLevel : byte
+ {
+ Off = 0,
+
+ Error = 1,
+
+ Warn = 2,
+
+ Info = 3,
+
+ Debug = 4,
+
+ Trace = 5,
+ }
+}
diff --git a/Source/Tribufu.Logging/Logger.cs b/Source/Tribufu.Logging/Logger.cs
new file mode 100644
index 0000000..2da1ba9
--- /dev/null
+++ b/Source/Tribufu.Logging/Logger.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using System;
+
+namespace Tribufu.Logging
+{
+ public static class Logger
+ {
+ private static LogLevel _level = LogLevel.Off;
+
+ public static void Initialize(LogLevel level = LogLevel.Off)
+ {
+ _level = level;
+ }
+
+ public static void Info(object context, string message)
+ {
+ Info($"({context.GetType().Name}) {message}");
+ }
+
+ public static void Warn(object context, string message)
+ {
+ Warn($"({context.GetType().Name}) {message}");
+ }
+
+ public static void Error(object context, string message)
+ {
+ Error($"({context.GetType().Name}) {message}");
+ }
+
+ public static void Debug(object context, string message)
+ {
+ Debug($"({context.GetType().Name}) {message}");
+ }
+
+ public static void Trace(object context, string message)
+ {
+ Trace($"({context.GetType().Name}) {message}");
+ }
+
+ public static void Info(string message)
+ {
+ Log(LogLevel.Info, message, ConsoleColor.Green);
+ }
+
+ public static void Warn(string message)
+ {
+ Log(LogLevel.Warn, message, ConsoleColor.Yellow);
+ }
+
+ public static void Error(string message)
+ {
+ Log(LogLevel.Error, message, ConsoleColor.Red);
+ }
+
+ public static void Debug(string message)
+ {
+ Log(LogLevel.Debug, message, ConsoleColor.White);
+ }
+
+ public static void Trace(string message)
+ {
+ Log(LogLevel.Trace, message, ConsoleColor.DarkGray);
+ }
+
+ private static void Log(LogLevel level, string message, ConsoleColor color)
+ {
+ if (level <= _level)
+ {
+ var defaultColor = Console.ForegroundColor;
+ Console.ForegroundColor = color;
+ var timestamp = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss");
+ Console.WriteLine($"[{timestamp}] [{level.ToString().ToUpper()}]: {message}");
+ Console.ForegroundColor = defaultColor;
+ }
+ }
+ }
+}
diff --git a/Source/Tribufu.Logging/README.md b/Source/Tribufu.Logging/README.md
new file mode 100644
index 0000000..9845f6c
--- /dev/null
+++ b/Source/Tribufu.Logging/README.md
@@ -0,0 +1 @@
+# Tribufu Logging
diff --git a/src/Tribufu.Utils/Tribufu.Utils.csproj b/Source/Tribufu.Logging/Tribufu.Logging.csproj
similarity index 68%
rename from src/Tribufu.Utils/Tribufu.Utils.csproj
rename to Source/Tribufu.Logging/Tribufu.Logging.csproj
index 026c17d..cade901 100644
--- a/src/Tribufu.Utils/Tribufu.Utils.csproj
+++ b/Source/Tribufu.Logging/Tribufu.Logging.csproj
@@ -1,14 +1,15 @@
- Tribufu.Utils
- Tribufu Utils
+ Tribufu.Logging
+ Tribufu Logging
README.md
- Library
Properties
- net5.0
true
+ false
+ Library
+ netstandard2.0;net45;net5.0
diff --git a/Source/Tribufu.Platform/FlakeGenerator.cs b/Source/Tribufu.Platform/FlakeGenerator.cs
new file mode 100644
index 0000000..ca89efd
--- /dev/null
+++ b/Source/Tribufu.Platform/FlakeGenerator.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: UNLICENSED
+
+using IdGen;
+using System;
+
+namespace Tribufu.Platform
+{
+ public static class FlakeGenerator
+ {
+ public static readonly DateTime EPOCH = new(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private static readonly IdStructure _structure = new(41, 10, 12);
+
+ private static readonly IdGeneratorOptions _options = new(_structure, new DefaultTimeSource(EPOCH));
+
+ private static readonly IdGenerator _generator = new(0, _options);
+
+ public static ulong New()
+ {
+ //Console.WriteLine("Max. generators : {0}", _structure.MaxGenerators);
+ //Console.WriteLine("Id's/ms per generator : {0}", _structure.MaxSequenceIds);
+ //Console.WriteLine("Id's/ms total : {0}", _structure.MaxGenerators * _structure.MaxSequenceIds);
+ //Console.WriteLine("Wraparound interval : {0}", _structure.WraparoundInterval(_generator.Options.TimeSource));
+ //Console.WriteLine("Wraparound date : {0}", _structure.WraparoundDate(_generator.Options.TimeSource.Epoch, _generator.Options.TimeSource).ToString("O"));
+
+ return (ulong)_generator.CreateId();
+ }
+
+ public static ulong FromTimestamp(DateTime timestamp, ushort sequence = 0)
+ {
+ var diff = (long)(timestamp - EPOCH).TotalMilliseconds;
+ if (diff < 0 || diff > (1L << 41) - 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(timestamp), "Timestamp out of range for flake.");
+ }
+
+ ulong timestampPart = (ulong)diff << 22; // 41 bits << (10+12)
+ ulong generatorPart = 0ul << 12; // Always 0
+ ulong sequencePart = (ulong)(sequence & 0xFFF); // 12 bits
+
+ return timestampPart | generatorPart | sequencePart;
+ }
+ }
+}
diff --git a/Source/Tribufu.Platform/Paths.cs b/Source/Tribufu.Platform/Paths.cs
new file mode 100644
index 0000000..0d67e7b
--- /dev/null
+++ b/Source/Tribufu.Platform/Paths.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Tribufu.Logging;
+
+namespace Tribufu.Platform
+{
+ ///
+ /// Provides standardized access to important directories, such as config, saved data, logs, and platform-specific binaries.
+ /// This is especially useful for abstracting file path logic across environments (development, production, etc).
+ ///
+ public static class Paths
+ {
+ ///
+ /// Gets the root base directory of the application.
+ ///
+ ///
+ /// - In development, this resolves to the root of the repository (five levels above bin/Debug or bin/Release).
+ /// - In production, it resolves to two levels above the binary location.
+ /// - It uses case-insensitive checks and runtime heuristics to improve accuracy.
+ ///
+ /// The absolute path to the base directory.
+ public static string GetApplicationDirectory()
+ {
+ try
+ {
+ string baseDirectory;
+ string defaultBaseDirectory = AppContext.BaseDirectory;
+
+ bool isDevelopment = defaultBaseDirectory.ToLowerInvariant().Contains("debug");
+
+ if (isDevelopment)
+ {
+ // Go 5 levels up to simulate project root
+ baseDirectory = Path.Combine(defaultBaseDirectory, "..", "..", "..", "..", "..");
+ }
+ else
+ {
+ baseDirectory = Path.Combine(defaultBaseDirectory, "..", "..");
+ }
+
+ return Path.GetFullPath(baseDirectory);
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn($"(Paths) Failed to resolve base directory: {ex.Message}");
+ return AppContext.BaseDirectory;
+ }
+ }
+
+ ///
+ /// Gets the path to the platform-specific binary directory.
+ ///
+ ///
+ /// The absolute path to bin/<runtime-identifier> if available,
+ /// otherwise falls back to bin/dotnet.
+ ///
+ public static string GetApplicationBinDirectory()
+ {
+ var binDirectory = Path.Combine(GetApplicationDirectory(), "bin");
+
+ if (!string.IsNullOrEmpty(RuntimeInformation.RuntimeIdentifier))
+ {
+ binDirectory = Path.Combine(binDirectory, RuntimeInformation.RuntimeIdentifier);
+ }
+ else
+ {
+ binDirectory = Path.Combine(binDirectory, "dotnet");
+ }
+
+ return binDirectory;
+ }
+
+ ///
+ /// Gets the path to the configuration directory.
+ ///
+ /// The absolute path to the config directory.
+ public static string GetApplicationConfigDirectory()
+ {
+ return Path.Combine(GetApplicationDirectory(), "config");
+ }
+
+ ///
+ /// Gets the path to the assets directory.
+ ///
+ /// The absolute path to the assets directory.
+ public static string GetApplicationAssetsDirectory()
+ {
+ return Path.Combine(GetApplicationDirectory(), "assets");
+ }
+
+ ///
+ /// Gets the path to the saved data directory.
+ ///
+ /// The absolute path to the saved directory.
+ public static string GetApplicationSavedDirectory()
+ {
+ return Path.Combine(GetApplicationDirectory(), "saved");
+ }
+
+ ///
+ /// Gets the path to the cache directory inside saved.
+ ///
+ /// The absolute path to the saved/cache directory.
+ public static string GetApplicationCacheDirectory()
+ {
+ return Path.Combine(GetApplicationSavedDirectory(), "cache");
+ }
+
+ ///
+ /// Gets the path to the logs directory inside saved.
+ ///
+ /// The absolute path to the saved/logs directory.
+ public static string GetApplicationLogsDirectory()
+ {
+ return Path.Combine(GetApplicationSavedDirectory(), "logs");
+ }
+ }
+}
diff --git a/Source/Tribufu.Platform/README.md b/Source/Tribufu.Platform/README.md
new file mode 100644
index 0000000..6925ac2
--- /dev/null
+++ b/Source/Tribufu.Platform/README.md
@@ -0,0 +1 @@
+# Tribufu Runtime
diff --git a/src/Tribufu.Serialization/Tribufu.Serialization.csproj b/Source/Tribufu.Platform/Tribufu.Platform.csproj
similarity index 53%
rename from src/Tribufu.Serialization/Tribufu.Serialization.csproj
rename to Source/Tribufu.Platform/Tribufu.Platform.csproj
index 8efd520..b1a39fa 100644
--- a/src/Tribufu.Serialization/Tribufu.Serialization.csproj
+++ b/Source/Tribufu.Platform/Tribufu.Platform.csproj
@@ -1,16 +1,24 @@
- Tribufu.Serialization
- Tribufu Serialization Helpers
+ Tribufu.Platform
+ Tribufu Platform
README.md
- Library
Properties
- net5.0
true
+ false
+ Library
+ net5.0
+
+
+
+
+
+
+
diff --git a/Source/Tribufu.Serialization/BaseClassFirstContractResolver.cs b/Source/Tribufu.Serialization/BaseClassFirstContractResolver.cs
new file mode 100644
index 0000000..0aa320c
--- /dev/null
+++ b/Source/Tribufu.Serialization/BaseClassFirstContractResolver.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: UNLICENSED
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Tribufu.Serialization
+{
+ public class BaseClassFirstContractResolver : DefaultContractResolver
+ {
+ protected override IList CreateProperties(Type type, MemberSerialization memberSerialization)
+ {
+ var props = base.CreateProperties(type, memberSerialization);
+ return props.OrderBy(p => { return p.DeclaringType == type ? 1 : 0; }).ToList();
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/CustomSerializerSettings.cs b/Source/Tribufu.Serialization/CustomSerializerSettings.cs
new file mode 100644
index 0000000..b487345
--- /dev/null
+++ b/Source/Tribufu.Serialization/CustomSerializerSettings.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Serialization;
+
+namespace Tribufu.Serialization
+{
+ public static class CustomSerializerSettings
+ {
+ public static JsonSerializerSettings GetNewtonsoftJson()
+ {
+ var settings = new JsonSerializerSettings();
+ SetNewtonsoftJson(settings);
+ return settings;
+ }
+
+ public static void SetNewtonsoftJson(JsonSerializerSettings settings)
+ {
+ settings.ContractResolver = new BaseClassFirstContractResolver
+ {
+ NamingStrategy = new SnakeCaseNamingStrategy()
+ };
+
+ settings.Converters.Add(new DecimalNullableStringConverter());
+ settings.Converters.Add(new DecimalStringConverter());
+ settings.Converters.Add(new ULongNullableStringConverter());
+ settings.Converters.Add(new ULongStringConverter());
+ settings.Converters.Add(new StringEnumConverter(new SnakeCaseNamingStrategy(), false));
+
+ settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/DecimalNullableStringConverter.cs b/Source/Tribufu.Serialization/DecimalNullableStringConverter.cs
new file mode 100644
index 0000000..fee4870
--- /dev/null
+++ b/Source/Tribufu.Serialization/DecimalNullableStringConverter.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+
+namespace Tribufu.Serialization
+{
+ public class DecimalNullableStringConverter : JsonConverter
+ {
+ public override decimal? ReadJson(JsonReader reader, Type objectType, decimal? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer)
+ {
+ string value = reader.Value?.ToString();
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ return decimal.Parse(value);
+ }
+
+ throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing decimal?.");
+ }
+
+ public override void WriteJson(JsonWriter writer, decimal? value, JsonSerializer serializer)
+ {
+ if (value.HasValue)
+ {
+ writer.WriteValue(value.Value.ToString(CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ writer.WriteNull();
+ }
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/DecimalStringConverter.cs b/Source/Tribufu.Serialization/DecimalStringConverter.cs
new file mode 100644
index 0000000..2794fa3
--- /dev/null
+++ b/Source/Tribufu.Serialization/DecimalStringConverter.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+
+namespace Tribufu.Serialization
+{
+ public class DecimalStringConverter : JsonConverter
+ {
+ public override decimal ReadJson(JsonReader reader, Type objectType, decimal existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.String && decimal.TryParse(reader.Value?.ToString(), out var result))
+ {
+ return result;
+ }
+
+ if (reader.TokenType == JsonToken.Integer)
+ {
+ return Convert.ToUInt64(reader.Value);
+ }
+
+ throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing decimal.");
+ }
+
+ public override void WriteJson(JsonWriter writer, decimal value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/NetworkAwareSnakeCaseNamingStrategy.cs b/Source/Tribufu.Serialization/NetworkAwareSnakeCaseNamingStrategy.cs
new file mode 100644
index 0000000..4710a14
--- /dev/null
+++ b/Source/Tribufu.Serialization/NetworkAwareSnakeCaseNamingStrategy.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json.Serialization;
+using System.Text.RegularExpressions;
+
+namespace Tribufu.Serialization
+{
+ public class NetworkAwareSnakeCaseNamingStrategy : SnakeCaseNamingStrategy
+ {
+ private static readonly string[] KnownAcronyms = new string[] { "IPv4", "IPv6" };
+
+ protected override string ResolvePropertyName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ return name;
+ }
+
+ foreach (var acr in KnownAcronyms)
+ {
+ name = Regex.Replace(name, acr, acr.ToLower());
+ }
+
+ var snake = base.ResolvePropertyName(name);
+ foreach (var acr in KnownAcronyms)
+ {
+ var lower = acr.ToLower();
+ snake = snake.Replace(lower.Replace("_", ""), lower);
+ }
+
+ return snake;
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/README.md b/Source/Tribufu.Serialization/README.md
new file mode 100644
index 0000000..03ac61c
--- /dev/null
+++ b/Source/Tribufu.Serialization/README.md
@@ -0,0 +1 @@
+# Tribufu Serialization
diff --git a/src/Tribufu.Serialization.Newtonsoft/Tribufu.Serialization.Newtonsoft.csproj b/Source/Tribufu.Serialization/Tribufu.Serialization.csproj
similarity index 79%
rename from src/Tribufu.Serialization.Newtonsoft/Tribufu.Serialization.Newtonsoft.csproj
rename to Source/Tribufu.Serialization/Tribufu.Serialization.csproj
index 0d33420..6a7981a 100644
--- a/src/Tribufu.Serialization.Newtonsoft/Tribufu.Serialization.Newtonsoft.csproj
+++ b/Source/Tribufu.Serialization/Tribufu.Serialization.csproj
@@ -1,14 +1,15 @@
- Tribufu.Serialization.Newtonsoft
- Tribufu Serialization Helpers for Newtonsoft.Json
+ Tribufu.Serialization
+ Tribufu Serialization
README.md
- Library
Properties
- netstandard2.0;net45;net5.0
true
+ false
+ Library
+ netstandard2.0;net45;net5.0
diff --git a/Source/Tribufu.Serialization/ULongNullableStringConverter.cs b/Source/Tribufu.Serialization/ULongNullableStringConverter.cs
new file mode 100644
index 0000000..0beb8e4
--- /dev/null
+++ b/Source/Tribufu.Serialization/ULongNullableStringConverter.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json;
+using System;
+
+namespace Tribufu.Serialization
+{
+ public class ULongNullableStringConverter : JsonConverter
+ {
+ public override ulong? ReadJson(JsonReader reader, Type objectType, ulong? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer)
+ {
+ string value = reader.Value?.ToString();
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ return ulong.Parse(value);
+ }
+
+ throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing ulong?.");
+ }
+
+ public override void WriteJson(JsonWriter writer, ulong? value, JsonSerializer serializer)
+ {
+ if (value.HasValue)
+ {
+ writer.WriteValue(value.Value.ToString());
+ }
+ else
+ {
+ writer.WriteNull();
+ }
+ }
+ }
+}
diff --git a/Source/Tribufu.Serialization/ULongStringConverter.cs b/Source/Tribufu.Serialization/ULongStringConverter.cs
new file mode 100644
index 0000000..5cb6f33
--- /dev/null
+++ b/Source/Tribufu.Serialization/ULongStringConverter.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Tribufu. All Rights Reserved.
+// SPDX-License-Identifier: MIT
+
+using Newtonsoft.Json;
+using System;
+
+namespace Tribufu.Serialization
+{
+ public class ULongStringConverter : JsonConverter
+ {
+ public override ulong ReadJson(JsonReader reader, Type objectType, ulong existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.String && ulong.TryParse(reader.Value?.ToString(), out var result))
+ {
+ return result;
+ }
+
+ if (reader.TokenType == JsonToken.Integer)
+ {
+ return Convert.ToUInt64(reader.Value);
+ }
+
+ throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing ulong.");
+ }
+
+ public override void WriteJson(JsonWriter writer, ulong value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString());
+ }
+ }
+}
diff --git a/Source/Tribufu/Api/TribufuGeneratedApi.cs b/Source/Tribufu/Api/TribufuGeneratedApi.cs
new file mode 100644
index 0000000..096ce48
--- /dev/null
+++ b/Source/Tribufu/Api/TribufuGeneratedApi.cs
@@ -0,0 +1,20140 @@
+/*
+ * Tribufu API
+ *
+ * API to access Tribufu services.
+ *
+ * The version of the OpenAPI document: 1.4.3
+ * Contact: contact@tribufu.com
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
+
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Net.Mime;
+using Tribufu.Client;
+using Tribufu.Model;
+
+namespace Tribufu.Api
+{
+
+ ///
+ /// Represents a collection of functions to interact with the API endpoints
+ ///
+ public interface ITribufuGeneratedApiSync : IApiAccessor
+ {
+ #region Synchronous Operations
+ ///
+ /// Authorize the client to access the user information.
+ ///
+ ///
+ /// <b>🔒 Required permissions:</b> <code>tribufu.identity.oauth2.authorize</code>
+ ///
+ /// Thrown when fails to make API call
+ /// (optional)
+ /// Index associated with the operation.
+ /// CodeResponse
+ CodeResponse Authorize(AuthorizeRequest authorizeRequest = default(AuthorizeRequest), int operationIndex = 0);
+
+ ///
+ /// Authorize the client to access the user information.
+ ///
+ ///
+ /// <b>🔒 Required permissions:</b> <code>tribufu.identity.oauth2.authorize</code>
+ ///
+ /// Thrown when fails to make API call
+ /// (optional)
+ /// Index associated with the operation.
+ /// ApiResponse of CodeResponse
+ ApiResponse AuthorizeWithHttpInfo(AuthorizeRequest authorizeRequest = default(AuthorizeRequest), int operationIndex = 0);
+ ///
+ /// Change the email of a user.
+ ///
+ ///
+ /// This endpoint is not available with an api key, only with a bearer token.<br/><br/><b>🔒 Required permissions:</b> <code>tribufu.identity.user.email.update</code>
+ ///
+ /// Thrown when fails to make API call
+ ///
+ /// (optional)
+ /// Index associated with the operation.
+ ///
+ void ChangeEmail(string id, Object body = default(Object), int operationIndex = 0);
+
+ ///
+ /// Change the email of a user.
+ ///
+ ///
+ /// This endpoint is not available with an api key, only with a bearer token.<br/><br/><b>🔒 Required permissions:</b> <code>tribufu.identity.user.email.update</code>
+ ///
+ /// Thrown when fails to make API call
+ ///
+ /// (optional)
+ /// Index associated with the operation.
+ /// ApiResponse of Object(void)
+ ApiResponse