From 9ef9029799790a0f70c15ca9895027eb2e94971f Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 17 Nov 2025 09:47:04 +0800 Subject: [PATCH 001/140] Enable nullable annotations for reference types by default --- .gitignore | 1 + .../src/ServiceStack.Client/MetadataTypes.cs | 2 +- .../NativeTypes/CSharp/CSharpGenerator.cs | 22 +-------- .../NativeTypes/Dart/DartGenerator.cs | 47 +++++++++++++------ .../NativeTypes/Go/GoGenerator.cs | 6 +++ .../NativeTypes/Php/PhpGenerator.cs | 8 +++- .../NativeTypes/Python/PythonGenerator.cs | 6 +++ .../NativeTypes/Rust/RustGenerator.cs | 8 +++- .../TypeScript/TypeScriptGenerator.cs | 6 +++ .../NativeTypes/Zig/ZigGenerator.cs | 26 +++------- .../tests/AdhocNew/Configure.AI.Chat.cs | 11 +++-- .../AdhocNew/ServiceInterface/TestServices.cs | 4 ++ .../tests/AdhocNew/ServiceModel/Bookings.cs | 23 +++++++++ 13 files changed, 109 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index fb54b0d60d5..128b137a4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ src/ServiceStack.userprefs src/ServiceStack.sln.DotSettings ServiceStack/tests/NorthwindAuto/Configure.secrets.cs ServiceStack/tests/NorthwindAuto/App_Data/chinook.sqlite +mise.toml diff --git a/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs b/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs index ebe0323ae02..4d5679c2e26 100644 --- a/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs +++ b/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs @@ -35,7 +35,7 @@ public MetadataTypesConfig( bool addPropertyAccessors = true, bool excludeGenericBaseTypes = false, bool settersReturnThis = true, - bool addNullableAnnotations = false, + bool addNullableAnnotations = true, bool makePropertiesOptional = false, bool makeDataContractsExtensible = false, bool initializeCollections = false, diff --git a/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs index 4f5821a660c..bd7d9fe948b 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs @@ -50,24 +50,6 @@ public CSharpGenerator(MetadataTypesConfig config) public static Func PropertyTypeFilter { get; set; } - /// - /// Helper to make Nullable Reference Type Annotations - /// - [Obsolete("Use ConfigurePlugin(feature => feature.MetadataTypesConfig.AddNullableAnnotations = true);")] - public static bool UseNullableAnnotations - { - set - { - if (value) - { - PropertyTypeFilter = (gen, type, prop) => - prop.IsRequired == true || prop.PropertyInfo?.PropertyType.IsValueType == true - ? gen.GetPropertyType(prop) - : gen.GetPropertyType(prop).EnsureSuffix('?'); - } - } - } - public static Func, List> FilterTypes = DefaultFilterTypes; public static List DefaultFilterTypes(List types) => types; @@ -522,8 +504,8 @@ public virtual string GetPropertyType(MetadataPropertyType prop) } public static Dictionary AttributeConstructorArgs { get; set; } = new() { - ["ValidateRequest"] = new[] { nameof(ValidateRequestAttribute.Validator) }, - ["Validate"] = new[] { nameof(ValidateRequestAttribute.Validator) }, + ["ValidateRequest"] = [nameof(ValidateRequestAttribute.Validator)], + ["Validate"] = [nameof(ValidateRequestAttribute.Validator)], }; public bool AppendAttributes(StringBuilderWrapper sb, List attributes) diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs index eba58fcfd94..3d215e62ae1 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs @@ -68,7 +68,8 @@ public DartGenerator(MetadataTypesConfig config) {"Type", "String"}, }; private static string declaredEmptyString = "\"\""; - private static readonly Dictionary defaultValues = new() { + + public static readonly Dictionary DefaultValues = new() { {"String", declaredEmptyString}, {"string", declaredEmptyString}, {"Boolean", "false"}, @@ -99,7 +100,8 @@ public DartGenerator(MetadataTypesConfig config) {"DateTimeOffset", "DateTime(0)"}, }; - static HashSet BasicJsonTypes = new() { + static HashSet BasicJsonTypes = + [ nameof(String), nameof(Boolean), nameof(Guid), @@ -116,8 +118,8 @@ public DartGenerator(MetadataTypesConfig config) nameof(Decimal), "int", "bool", - "Dictionary", - }; + "Dictionary" + ]; public static Dictionary DartToJsonConverters = new() { { "double", "toDouble" }, @@ -567,7 +569,7 @@ private string AppendType(ref StringBuilderWrapper sb, MetadataType type, string returnType = "dynamic"; // This is to avoid invalid syntax such as "return new string()" - responseTypeExpression = defaultValues.TryGetValue(returnType, out var newReturnInstance) + responseTypeExpression = DefaultValues.TryGetValue(returnType, out var newReturnInstance) ? $"createResponse() => {newReturnInstance};" : $"createResponse() => {DartLiteral(returnType + "()")};"; responseTypeName = $"getResponseTypeName() => \"{returnType}\";"; @@ -880,7 +882,7 @@ public void RegisterPropertyType(MetadataPropertyType prop, string dartType) return; var csharpType = CSharpPropertyType(prop); - var factoryFn = defaultValues.TryGetValue(csharpType, out string defaultValue) + var factoryFn = DefaultValues.TryGetValue(csharpType, out string defaultValue) ? $"() => {defaultValue}" : null; @@ -934,7 +936,7 @@ private void RegisterType(MetadataType metaType, string dartType, string factory var genericArg = RawType(genericArgNode); - var genericArgFactoryFn = defaultValues.TryGetValue(genericArg, out string defaultValue) + var genericArgFactoryFn = DefaultValues.TryGetValue(genericArg, out string defaultValue) ? $"() => {defaultValue}" : null; @@ -1022,15 +1024,30 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu sb.Emit(prop, Lang.Dart); PrePropertyFilter?.Invoke(sb, prop, type); + + var isRequired = prop.Type != "Nullable`1" + && (prop.IsRequired == true) + && Config.AddNullableAnnotations + && !prop.IsInterface(); + string initializer = ""; + if (isRequired) + { + if (prop.IsEnumerable() && feature.ShouldInitializeCollection(type)) + { + initializer = prop.IsDictionary() + ? " = {}" + : " = []"; + } + else if (DefaultValues.TryGetValue(propType, out var defaultValue)) + { + initializer = " = " + defaultValue; + } + } + propType = isRequired + ? propType + : propType + "?"; - var initializer = (prop.IsRequired == true || Config.InitializeCollections) - && prop.IsEnumerable() && feature.ShouldInitializeCollection(type) && !prop.IsInterface() - ? prop.IsDictionary() - ? " = {}" - : " = []" - : ""; - - sb.AppendLine($"{propType}? {GetSafePropertyName(prop)}{initializer};"); + sb.AppendLine($"{propType} {GetSafePropertyName(prop)}{initializer};"); PostPropertyFilter?.Invoke(sb, prop, type); } } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs index d329136b55c..3d99e8a77a5 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs @@ -454,7 +454,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs index d894db287f6..41bdda824f8 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs @@ -785,9 +785,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); - if (!isNullable) - isNullable = prop.Type == "Nullable`1" || prop.IsValueType != true; + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs index e35eda3fedf..b0a3efba25e 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs @@ -734,7 +734,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs index d3d89a6f181..c1ddb04a0cd 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs @@ -486,7 +486,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } @@ -527,7 +533,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } // Handle optional fields with Option - if (isOptional) + if (isOptional && !propType.StartsWith("Option<")) { propType = $"Option<{propType}>"; } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs index 468e7d957e9..bfbb7a2ef93 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs @@ -590,7 +590,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs index 6d80ae0d426..a6db14a886a 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs @@ -201,24 +201,6 @@ public ZigGenerator(MetadataTypesConfig config) public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; - /// - /// Helper to make Nullable properties - /// - public static bool UseNullableProperties - { - set - { - if (value) - { - IsPropertyOptional = (gen, type, prop) => false; - PropertyTypeFilter = (gen, type, prop) => - prop.IsRequired == true - ? gen.GetPropertyType(prop, out _) - : gen.GetPropertyType(prop, out _) + "|null"; - } - } - } - public void Init(MetadataTypes metadata) { var includeList = metadata.RemoveIgnoredTypes(Config); @@ -518,7 +500,13 @@ public virtual string GetPropertyType(MetadataPropertyType prop, out bool isNull var propType = Type(prop.GetTypeName(Config, AllTypes), prop.GenericArgs); isNullable = propType.EndsWith("?"); if (isNullable) + { propType = propType.Substring(0, propType.Length - 1); + } + else + { + isNullable = prop.IsRequired != true; + } return propType; } @@ -546,7 +534,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu PrePropertyFilter?.Invoke(sb, prop, type); // In Zig, optional fields use ?Type syntax (no default values allowed in struct definitions) - var zigType = optional ? $"?{propType}" : propType; + var zigType = optional && !propType.StartsWith("?") ? $"?{propType}" : propType; sb.AppendLine("{0}: {1},".Fmt(GetPropertyName(prop), zigType)); PostPropertyFilter?.Invoke(sb, prop, type); diff --git a/ServiceStack/tests/AdhocNew/Configure.AI.Chat.cs b/ServiceStack/tests/AdhocNew/Configure.AI.Chat.cs index f0f58e538bd..25bba92ae26 100644 --- a/ServiceStack/tests/AdhocNew/Configure.AI.Chat.cs +++ b/ServiceStack/tests/AdhocNew/Configure.AI.Chat.cs @@ -16,9 +16,14 @@ public void Configure(IWebHostBuilder builder) => builder // ValidateRequest = async (req) => req.GetApiKey()?.HasScope(RoleNames.Admin) == true // ? null // : HttpResult.Redirect("/admin-ui"), - Variables = { - ["GOOGLE_API_KEY"] = Environment.GetEnvironmentVariable("GOOGLE_FREE_API_KEY")! - } + EnableProviders = [ + "servicestack", + "groq", + "openrouter_free", + ], + // Variables = { + // ["GOOGLE_API_KEY"] = Environment.GetEnvironmentVariable("GOOGLE_FREE_API_KEY")! + // } }); // services.AddSingleton(); services.AddSingleton(); diff --git a/ServiceStack/tests/AdhocNew/ServiceInterface/TestServices.cs b/ServiceStack/tests/AdhocNew/ServiceInterface/TestServices.cs index cfe95cdfd0d..938173ca043 100644 --- a/ServiceStack/tests/AdhocNew/ServiceInterface/TestServices.cs +++ b/ServiceStack/tests/AdhocNew/ServiceInterface/TestServices.cs @@ -1,3 +1,5 @@ +using MyApp.ServiceModel; + namespace MyApp.ServiceInterface; public class SearchRootSummary : QueryDb @@ -12,4 +14,6 @@ public QueryResponse Get(SearchRootSummary request) using var db = autoQuery.GetDb(Request); return new(); } + + public object Any(TestNullable request) => request.ConvertTo(); } \ No newline at end of file diff --git a/ServiceStack/tests/AdhocNew/ServiceModel/Bookings.cs b/ServiceStack/tests/AdhocNew/ServiceModel/Bookings.cs index 3a1aed70662..043d61c3e83 100644 --- a/ServiceStack/tests/AdhocNew/ServiceModel/Bookings.cs +++ b/ServiceStack/tests/AdhocNew/ServiceModel/Bookings.cs @@ -56,6 +56,29 @@ public class QueryBookings : QueryDb public int? Id { get; set; } } +public class TestNullable : IGet, IReturn +{ + public int? Id { get; set; } + public int RequiredId { get; set; } + public int[]? Ids { get; set; } + public int[] RequiredIds { get; set; } = []; + public List? Names { get; set; } + public List RequiredNames { get; set; } = []; + public List? RoomNumbers { get; set; } + public List RequiredRoomNumbers { get; set; } = []; +} +public class TestNullableResponse +{ + public int? Id { get; set; } + public int RequiredId { get; set; } + public int[]? Ids { get; set; } + public int[] RequiredIds { get; set; } = []; + public List? Names { get; set; } + public List RequiredNames { get; set; } = []; + public List? RoomNumbers { get; set; } + public List RequiredRoomNumbers { get; set; } = []; +} + // Uncomment below to enable DeletedBookings API to view deleted bookings: // [Route("/bookings/deleted")] // [AutoFilter(QueryTerm.Ensure, nameof(AuditBase.DeletedDate), Template = SqlTemplate.IsNotNull)] From a6d76c4f583f7e62374b4db8ad2a9817be7bbbdb Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 17 Nov 2025 09:47:07 +0800 Subject: [PATCH 002/140] Delete llms.json --- .../tests/AdhocNew/wwwroot/chat/llms.json | 1157 ----------------- 1 file changed, 1157 deletions(-) delete mode 100644 ServiceStack/tests/AdhocNew/wwwroot/chat/llms.json diff --git a/ServiceStack/tests/AdhocNew/wwwroot/chat/llms.json b/ServiceStack/tests/AdhocNew/wwwroot/chat/llms.json deleted file mode 100644 index 0c439761a49..00000000000 --- a/ServiceStack/tests/AdhocNew/wwwroot/chat/llms.json +++ /dev/null @@ -1,1157 +0,0 @@ -{ - "auth": { - "enabled": false, - "github": { - "client_id": "$GITHUB_CLIENT_ID", - "client_secret": "$GITHUB_CLIENT_SECRET", - "redirect_uri": "http://localhost:8000/auth/github/callback", - "restrict_to": "$GITHUB_USERS" - } - }, - "defaults": { - "headers": { - "Content-Type": "application/json", - "User-Agent": "llms.py/1.0" - }, - "text": { - "model": "kimi-k2", - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "" - } - ] - } - ] - }, - "image": { - "model": "qwen2.5vl", - "messages": [ - { - "role": "user", - "content": [ - { - "type": "image_url", - "image_url": { - "url": "" - } - }, - { - "type": "text", - "text": "Describe the key features of the input image" - } - ] - } - ] - }, - "audio": { - "model": "gpt-4o-audio-preview", - "messages": [ - { - "role": "user", - "content": [ - { - "type": "input_audio", - "input_audio": { - "data": "", - "format": "mp3" - } - }, - { - "type": "text", - "text": "Please transcribe and summarize this audio file" - } - ] - } - ] - }, - "file": { - "model": "qwen2.5vl", - "messages": [ - { - "role": "user", - "content": [ - { - "type": "file", - "file": { - "filename": "", - "file_data": "" - } - }, - { - "type": "text", - "text": "Please summarize this document" - } - ] - } - ] - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "max_completion_tokens": 16, - "stream": false - } - }, - "limits": { - "client_max_size": 20971520 - }, - "convert": { - "image": { - "max_size": "1536x1024", - "max_length": 1572864 - } - }, - "providers": { - "groq": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.groq.com/openai", - "api_key": "$GROQ_API_KEY", - "models": { - "allam-2-7b": "allam-2-7b", - "compound": "groq/compound", - "compound-mini": "groq/compound-mini", - "llama3.1:8b": "llama-3.1-8b-instant", - "llama3.3:70b": "llama-3.3-70b-versatile", - "llama4:109b": "meta-llama/llama-4-scout-17b-16e-instruct", - "llama4:400b": "meta-llama/llama-4-maverick-17b-128e-instruct", - "llama-guard-4:12b": "meta-llama/llama-guard-4-12b", - "llama-prompt-guard2:22m": "meta-llama/llama-prompt-guard-2-22m", - "llama-prompt-guard2:86m": "meta-llama/llama-prompt-guard-2-86m", - "kimi-k2": "moonshotai/kimi-k2-instruct-0905", - "gpt-oss:120b": "openai/gpt-oss-120b", - "gpt-oss:20b": "openai/gpt-oss-20b", - "qwen3:32b": "qwen/qwen3-32b" - }, - "default_pricing": { - "input": "0", - "output": "0" - } - }, - "google_free": { - "enabled": true, - "type": "GoogleProvider", - "api_key": "$GOOGLE_FREE_API_KEY", - "models": { - "gemini-flash-latest": "gemini-flash-latest", - "gemini-flash-lite-latest": "gemini-flash-lite-latest", - "gemini-2.5-pro": "gemini-2.5-pro", - "gemini-2.5-flash": "gemini-2.5-flash", - "gemini-2.5-flash-lite": "gemini-2.5-flash-lite" - }, - "default_pricing": { - "input": "0", - "output": "0" - }, - "safety_settings": [ - { - "category": "HARM_CATEGORY_DANGEROUS_CONTENT", - "threshold": "BLOCK_ONLY_HIGH" - } - ], - "thinking_config": { - "thinkingBudget": 1024, - "includeThoughts": true - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "max_completion_tokens": 512, - "reasoning": { - "max_tokens": 128, - "reasoning_effort": "low" - }, - "stream": false - } - }, - "codestral": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://codestral.mistral.ai", - "api_key": "$CODESTRAL_API_KEY", - "models": { - "codestral:22b": "codestral-latest" - }, - "default_pricing": { - "input": "0", - "output": "0" - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "stream": false - } - }, - "openrouter_free": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://openrouter.ai/api", - "api_key": "$OPENROUTER_API_KEY", - "models": { - "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", - "llama4:109b": "meta-llama/llama-4-scout:free", - "llama4:400b": "meta-llama/llama-4-maverick:free", - "deepseek-v3.1:671b": "deepseek/deepseek-chat-v3.1:free", - "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", - "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", - "gemma3n:e2b": "google/gemma-3n-e2b-it:free", - "glm-4.5-air": "z-ai/glm-4.5-air:free", - "mai-ds-r1": "microsoft/mai-ds-r1:free", - "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free", - "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", - "deepseek-r1-distill-llama:70b": "deepseek/deepseek-r1-distill-llama-70b:free", - "gpt-oss:20b": "openai/gpt-oss-20b:free", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", - "mistral-nemo:12b": "mistralai/mistral-nemo:free", - "venice-uncensored:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", - "llama3.3:8b": "meta-llama/llama-3.3-8b-instruct:free", - "gemma3:27b": "google/gemma-3-27b-it:free", - "qwen3-coder": "qwen/qwen3-coder:free", - "qwen3:235b": "qwen/qwen3-235b-a22b:free", - "qwen3:30b": "qwen/qwen3-30b-a3b:free", - "qwen3:4b": "qwen/qwen3-4b:free", - "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", - "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", - "minimax-m2": "minimax/minimax-m2:free" - }, - "default_pricing": { - "input": "0", - "output": "0" - } - }, - "ollama": { - "enabled": false, - "type": "OllamaProvider", - "base_url": "http://localhost:11434", - "models": {}, - "all_models": true, - "default_pricing": { - "input": "0", - "output": "0" - } - }, - "google": { - "enabled": true, - "type": "GoogleProvider", - "api_key": "$GOOGLE_API_KEY", - "models": { - "gemini-flash-latest": "gemini-flash-latest", - "gemini-flash-lite-latest": "gemini-flash-lite-latest", - "gemini-2.5-pro": "gemini-2.5-pro", - "gemini-2.5-flash": "gemini-2.5-flash", - "gemini-2.5-flash-lite": "gemini-2.5-flash-lite" - }, - "pricing": { - "gemini-flash-latest": { - "input": "0.0000003", - "output": "0.0000025" - }, - "gemini-flash-lite-latest": { - "input": "0.0000001", - "output": "0.0000004" - }, - "gemini-2.5-pro": { - "input": "0.00000125", - "output": "0.00001" - }, - "gemini-2.5-flash": { - "input": "0.0000003", - "output": "0.0000025" - }, - "gemini-2.5-flash-lite": { - "input": "0.0000001", - "output": "0.0000004" - } - }, - "safety_settings": [ - { - "category": "HARM_CATEGORY_DANGEROUS_CONTENT", - "threshold": "BLOCK_ONLY_HIGH" - } - ], - "thinking_config": { - "thinkingBudget": 1024, - "includeThoughts": true - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "max_completion_tokens": 512, - "reasoning": { - "max_tokens": 128, - "reasoning_effort": "low" - }, - "stream": false - } - }, - "anthropic": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.anthropic.com", - "api_key": "$ANTHROPIC_API_KEY", - "models": { - "claude-sonnet-4-5": "claude-sonnet-4-5", - "claude-sonnet-4-0": "claude-sonnet-4-0", - "claude-3-7-sonnet": "claude-3-7-sonnet-latest", - "claude-haiku-4-5": "claude-haiku-4-5", - "claude-3-5-haiku": "claude-3-5-haiku-latest", - "claude-3-haiku": "claude-3-haiku-20240307" - }, - "pricing": { - "claude-opus-4-1": { - "input": "0.000015", - "output": "0.000075" - }, - "claude-opus-4": { - "input": "0.000015", - "output": "0.000075" - }, - "claude-sonnet-4-5": { - "input": "0.000003", - "output": "0.000015" - }, - "claude-sonnet-4-0": { - "input": "0.000003", - "output": "0.000015" - }, - "claude-3-7-sonnet-latest": { - "input": "0.000003", - "output": "0.000015" - }, - "claude-haiku-4-5": { - "input": "0.000001", - "output": "0.000005" - }, - "claude-3-5-haiku-latest": { - "input": "0.0000008", - "output": "0.000004" - }, - "claude-3-haiku-20240307": { - "input": "0.00000025", - "output": "0.00000125" - } - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "max_completion_tokens": 512, - "reasoning": { - "max_tokens": 128, - "reasoning_effort": "low" - }, - "stream": false - } - }, - "openai": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.openai.com", - "api_key": "$OPENAI_API_KEY", - "models": { - "gpt-5-nano": "gpt-5-nano", - "gpt-5-mini": "gpt-5-mini", - "gpt-5": "gpt-5", - "gpt-5-chat-latest": "gpt-5-chat-latest", - "gpt-4.1-nano": "gpt-4.1-nano", - "gpt-4.1-mini": "gpt-4.1-mini", - "gpt-4.1": "gpt-4.1", - "o4-mini": "o4-mini", - "o3": "o3", - "gpt-4o-mini-search-preview": "gpt-4o-mini-search-preview", - "gpt-4o-search-preview": "gpt-4o-search-preview", - "o3-mini": "o3-mini", - "o1-mini": "o1-mini", - "chatgpt-4o-latest": "chatgpt-4o-latest", - "gpt-4o-mini": "gpt-4o-mini", - "gpt-4o": "gpt-4o", - "gpt-3.5-turbo": "gpt-3.5-turbo" - }, - "pricing": { - "gpt-5-nano": { - "input": "0.00000005", - "output": "0.0000004" - }, - "gpt-5-mini": { - "input": "0.00000025", - "output": "0.000002" - }, - "gpt-5": { - "input": "0.00000125", - "output": "0.00001" - }, - "gpt-5-chat-latest": { - "input": "0.00000125", - "output": "0.00001" - }, - "gpt-4.1-nano": { - "input": "0.0000001", - "output": "0.0000004" - }, - "gpt-4.1-mini": { - "input": "0.0000004", - "output": "0.0000016" - }, - "gpt-4.1": { - "input": "0.000002", - "output": "0.000008" - }, - "o4-mini": { - "input": "0.0000011", - "output": "0.0000044" - }, - "o3": { - "input": "0.000002", - "output": "0.000008" - }, - "gpt-4o-mini-search-preview": { - "input": "0.00000015", - "output": "0.0000006" - }, - "gpt-4o-search-preview": { - "input": "0.0000025", - "output": "0.00001" - }, - "o3-mini": { - "input": "0.0000011", - "output": "0.0000044" - }, - "o1-mini": { - "input": "0.0000011", - "output": "0.0000044" - }, - "chatgpt-4o-latest": { - "input": "0.000005", - "output": "0.000015" - }, - "gpt-4o-mini": { - "input": "0.00000015", - "output": "0.0000006" - }, - "gpt-4o": { - "input": "0.0000025", - "output": "0.00001" - }, - "gpt-3.5-turbo": { - "input": "0.000003", - "output": "0.000006" - } - } - }, - "grok": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.x.ai", - "api_key": "$GROK_API_KEY", - "models": { - "grok-4": "grok-4-0709", - "grok-code-fast-1": "grok-code-fast-1", - "grok-4-fast-reasoning": "grok-4-fast-reasoning", - "grok-4-fast-non-reasoning": "grok-4-fast-non-reasoning", - "grok-4-0709": "grok-4-0709", - "grok-3-mini": "grok-3-mini", - "grok-3": "grok-3" - }, - "pricing": { - "grok-4": { - "input": "0.000003", - "output": "0.000015" - }, - "grok-code-fast-1": { - "input": "0.0000002", - "output": "0.0000015" - }, - "grok-4-fast-reasoning": { - "input": "0.0000002", - "output": "0.0000005" - }, - "grok-4-fast-non-reasoning": { - "input": "0.0000002", - "output": "0.0000005" - }, - "grok-4-0709": { - "input": "0.000003", - "output": "0.000015" - }, - "grok-3-mini": { - "input": "0.0000003", - "output": "0.0000005" - }, - "grok-3": { - "input": "0.000003", - "output": "0.000015" - } - } - }, - "qwen": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode", - "api_key": "$DASHSCOPE_API_KEY", - "models": { - "qwen3-max": "qwen3-max", - "qwen-max": "qwen-max", - "qwen-plus": "qwen-plus", - "qwen-flash": "qwen-flash", - "qwq-plus": "qwq-plus", - "qwen-turbo": "qwen-turbo", - "qwen-omni-turbo": "qwen-omni-turbo", - "qwen3-omni-flash": "qwen3-omni-flash", - "qwen3-coder-plus": "qwen3-coder-plus", - "qwen3-coder-flash": "qwen3-coder-flash", - "qwen3-next:80b": "qwen3-next-80b-a3b-instruct", - "qwen3:235b": "qwen3-235b-a22b", - "qwen3:32b": "qwen3-32b", - "qwen3:30b": "qwen3-30b-a3b", - "qwen3:14b": "qwen3-14b", - "qwen3:8b": "qwen3-8b", - "qwen3:4b": "qwen3-4b", - "qwen3:1.7b": "qwen3-1.7b", - "qwen3:0.6b": "qwen3-0.6b", - "qwen3-coder": "qwen3-coder-480b-a35b-instruct", - "qwen3-coder:30b": "qwen3-coder-30b-a3b-instruct", - "qwen3-vl-plus": "qwen3-vl-plus", - "qwen3-vl-thinking:235b": "qwen3-vl-235b-a22b-thinking", - "qwen3-vl:235b": "qwen3-vl-235b-a22b-instruct", - "qwen3-vl:30b": "qwen3-vl-30b-a3b-instruct", - "qwen2.5-omni:7b": "qwen2.5-omni-7b" - }, - "pricing": { - "qwen3-max": { - "input": "0.0000012", - "output": "0.000006" - }, - "qwen-max": { - "input": "0.0000012", - "output": "0.000006" - }, - "qwen-plus": { - "input": "0.0000004", - "output": "0.0000012" - }, - "qwen-flash": { - "input": "0.00000005", - "output": "0.0000004" - }, - "qwq-plus": { - "input": "0.0000008", - "output": "0.0000024" - }, - "qwen-turbo": { - "input": "0.00000005", - "output": "0.0000005" - }, - "qwen-omni-turbo": { - "input": "0.00000007", - "output": "0.00000027" - }, - "qwen3-omni-flash": { - "input": "0.00000043", - "output": "0.00000166" - }, - "qwen3-coder-plus": { - "input": "0.000001", - "output": "0.000005" - }, - "qwen3-coder-flash": { - "input": "0.0000003", - "output": "0.0000015" - }, - "qwen3-next-80b-a3b-instruct": { - "input": "0.0000005", - "output": "0.000002" - }, - "qwen3-235b-a22b": { - "input": "0.0000007", - "output": "0.0000028" - }, - "qwen3-32b": { - "input": "0.0000007", - "output": "0.0000028" - }, - "qwen3-30b-a3b": { - "input": "0.0000002", - "output": "0.0000008" - }, - "qwen3-14b": { - "input": "0.00000035", - "output": "0.0000014" - }, - "qwen3-8b": { - "input": "0.00000018", - "output": "0.0000007" - }, - "qwen3-4b": { - "input": "0.00000011", - "output": "0.00000042" - }, - "qwen3-1.7b": { - "input": "0.00000011", - "output": "0.00000042" - }, - "qwen3-0.6b": { - "input": "0.00000011", - "output": "0.00000042" - }, - "qwen3-coder-480b-a35b-instruct": { - "input": "0.0000015", - "output": "0.0000075" - }, - "qwen3-coder-30b-a3b-instruct": { - "input": "0.00000045", - "output": "0.00000225" - }, - "qwen3-vl-plus": { - "input": "0.0000002", - "output": "0.0000016" - }, - "qwen3-vl-235b-a22b-thinking": { - "input": "0.0000007", - "output": "0.0000084" - }, - "qwen3-vl-235b-a22b-instruct": { - "input": "0.0000007", - "output": "0.0000084" - }, - "qwen3-vl-30b-a3b-instruct": { - "input": "0.0000002", - "output": "0.0000024" - }, - "qwen2.5-omni-7b": { - "input": "0.0000001", - "output": "0.0000004" - } - }, - "enable_thinking": false - }, - "z.ai": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.z.ai/api/paas/v4", - "api_key": "$ZAI_API_KEY", - "models": { - "glm-4.6": "glm-4.6", - "glm-4.5": "glm-4.5", - "glm-4.5-air": "glm-4.5-air", - "glm-4.5-x": "glm-4.5-x", - "glm-4.5-airx": "glm-4.5-airx", - "glm-4.5-flash": "glm-4.5-flash", - "glm-4:32b": "glm-4-32b-0414-128k" - }, - "temperature": 0.7, - "pricing": { - "glm-4.6": { - "input": "0.0000006", - "output": "0.0000022" - }, - "glm-4.5": { - "input": "0.0000006", - "output": "0.0000022" - }, - "glm-4.5-air": { - "input": "0.0000002", - "output": "0.0000011" - }, - "glm-4.5-x": { - "input": "0.0000022", - "output": "0.0000089" - }, - "glm-4.5-airx": { - "input": "0.0000011", - "output": "0.0000045" - }, - "glm-4.5-flash": { - "input": "0", - "output": "0" - }, - "glm-4-32b-0414-128k": { - "input": "0.0000001", - "output": "0.0000001" - } - } - }, - "mistral": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://api.mistral.ai", - "api_key": "$MISTRAL_API_KEY", - "models": { - "mistral-medium": "mistral-medium-latest", - "magistral-medium": "magistral-medium-latest", - "devstral-medium": "devstral-medium-2507", - "codestral:22b": "codestral-latest", - "mistral-small3.2:24b": "mistral-small-latest", - "magistral-small": "magistral-small-latest", - "devstral-small": "devstral-small-2507", - "voxtral-small": "voxtral-small-latest", - "voxtral-mini": "voxtral-mini-latest", - "mistral-large:123b": "mistral-large-latest", - "pixtral-large:124b": "pixtral-large-latest", - "pixtral:12b": "pixtral-12b", - "mistral-nemo:12b": "open-mistral-nemo", - "mistral:7b": "open-mistral-7b", - "mixtral:8x7b": "open-mixtral-8x7b", - "mixtral:8x22b": "open-mixtral-8x22b", - "ministral:8b": "ministral-8b-latest", - "ministral:3b": "ministral-3b-latest" - }, - "pricing": { - "mistral-medium-latest": { - "input": "0.0000004", - "output": "0.000002" - }, - "magistral-medium-latest": { - "input": "0.000002", - "output": "0.000005" - }, - "devstral-medium-2507": { - "input": "0.0000004", - "output": "0.000002" - }, - "codestral-latest": { - "input": "0.0000003", - "output": "0.0000003" - }, - "mistral-small-latest": { - "input": "0.0000001", - "output": "0.0000003" - }, - "magistral-small-latest": { - "input": "0.0000005", - "output": "0.0000015" - }, - "devstral-small-2507": { - "input": "0.0000001", - "output": "0.0000003" - }, - "voxtral-small-latest": { - "input": "0.0000001", - "output": "0.0000003" - }, - "voxtral-mini-latest": { - "input": "0.00000004", - "output": "0.00000004" - }, - "mistral-large-latest": { - "input": "0.000002", - "output": "0.000006" - }, - "pixtral-large-latest": { - "input": "0.000002", - "output": "0.000006" - }, - "pixtral-12b": { - "input": "0.00000015", - "output": "0.00000015" - }, - "open-mistral-nemo": { - "input": "0.00000015", - "output": "0.00000015" - }, - "open-mistral-7b": { - "input": "0.00000025", - "output": "0.00000025" - }, - "open-mixtral-8x7b": { - "input": "0.0000007", - "output": "0.0000007" - }, - "open-mixtral-8x22b": { - "input": "0.000002", - "output": "0.000006" - }, - "ministral-8b-latest": { - "input": "0.0000001", - "output": "0.0000001" - }, - "ministral-3b-latest": { - "input": "0.00000004", - "output": "0.00000004" - } - }, - "check": { - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "1+1=" - } - ] - } - ], - "stream": false - } - }, - "openrouter": { - "enabled": true, - "type": "OpenAiProvider", - "base_url": "https://openrouter.ai/api", - "api_key": "$OPENROUTER_API_KEY", - "models": { - "llama3.1:70b": "meta-llama/llama-3.1-405b-instruct", - "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct", - "phi-4:14b": "microsoft/phi-4", - "codestral:22b": "mistralai/codestral-2508", - "mistral-nemo:12b": "mistralai/mistral-nemo", - "mixtral:8x22b": "mistralai/mixtral-8x22b-instruct", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct", - "nova-premier": "amazon/nova-premier-v1", - "nova-micro": "amazon/nova-micro-v1", - "nova-lite": "amazon/nova-lite-v1", - "nova-pro": "amazon/nova-pro-v1", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl", - "claude-sonnet-4-5": "anthropic/claude-sonnet-4.5", - "claude-sonnet-4-0": "anthropic/claude-sonnet-4", - "gemini-flash-latest": "google/gemini-2.5-flash", - "gemini-flash-lite-latest": "google/gemini-2.5-flash-lite", - "gemini-2.5-pro": "google/gemini-2.5-pro", - "gemma3:4b": "google/gemma-3-4b-it", - "gemma3:12b": "google/gemma-3-12b-it", - "gemma3:27b": "google/gemma-3-27b-it", - "gpt-5": "openai/gpt-5", - "gpt-5-chat": "openai/gpt-5-chat", - "gpt-5-mini": "openai/gpt-5-mini", - "gpt-5-nano": "openai/gpt-5-nano", - "gpt-5-codex": "openai/gpt-5-codex", - "gpt-oss:120b": "openai/gpt-oss-120b", - "gpt-oss:20b": "openai/gpt-oss-20b", - "gpt-4o": "openai/gpt-4o", - "gpt-4o-mini": "openai/gpt-4o-mini", - "gpt-4.1": "openai/gpt-4.1", - "gpt-4.1-mini": "openai/gpt-4.1-mini", - "gpt-4.1-nano": "openai/gpt-4.1-nano", - "o4-mini": "openai/o4-mini", - "o4-mini-high": "openai/o4-mini-high", - "grok-4": "x-ai/grok-4", - "grok-4-fast": "x-ai/grok-4-fast", - "grok-code-fast-1": "x-ai/grok-code-fast-1", - "glm-4.6": "z-ai/glm-4.6", - "glm-4.5v": "z-ai/glm-4.5v", - "glm-4.5": "z-ai/glm-4.5", - "glm-4.5-air": "z-ai/glm-4.5-air", - "minimax-m2": "minimax/minimax-m2", - "kimi-k2": "moonshotai/kimi-k2", - "deepseek-v3.1:671b": "deepseek/deepseek-chat", - "deepseek-v3.2-exp": "deepseek/deepseek-v3.2-exp", - "deepseek-chat-v3.1:671b": "deepseek/deepseek-chat-v3.1", - "deepseek-r1:671b": "deepseek/deepseek-r1", - "deepseek-v3.1-terminus": "deepseek/deepseek-v3.1-terminus", - "qwen3:8b": "qwen/qwen3-8b", - "qwen3:30b": "qwen/qwen3-30b-a3b", - "qwen3:32b": "qwen/qwen3-32b", - "qwen3:235b": "qwen/qwen3-235b-a22b", - "qwen3-coder": "qwen/qwen3-coder", - "qwen3-coder-flash": "qwen/qwen3-coder-flash", - "qwen3-coder-plus": "qwen/qwen3-coder-plus", - "qwen3-coder:30b": "qwen/qwen3-coder-30b-a3b-instruct", - "qwen3-max": "qwen/qwen3-max", - "qwen3-vl:235b": "qwen/qwen3-vl-235b-a22b-instruct", - "qwen3-vl-thinking:235b": "qwen/qwen3-vl-235b-a22b-thinking", - "ling-1t": "inclusionai/ling-1t", - "llama4:109b": "meta-llama/llama-4-scout", - "llama4:400b": "meta-llama/llama-4-maverick" - }, - "pricing": { - "meta-llama/llama-3.1-405b-instruct": { - "input": "0.0000008", - "output": "0.0000008" - }, - "meta-llama/llama-3.3-70b-instruct": { - "input": "0.00000013", - "output": "0.00000038" - }, - "microsoft/phi-4": { - "input": "0.00000006", - "output": "0.00000014" - }, - "mistralai/codestral-2508": { - "input": "0.0000003", - "output": "0.0000009" - }, - "mistralai/mistral-nemo": { - "input": "0.00000002", - "output": "0.00000004" - }, - "mistralai/mixtral-8x22b-instruct": { - "input": "0.000002", - "output": "0.000006" - }, - "mistralai/mistral-small-3.2-24b-instruct": { - "input": "0.00000006", - "output": "0.00000018" - }, - "amazon/nova-premier-v1": { - "input": "0.0000025", - "output": "0.0000125" - }, - "amazon/nova-micro-v1": { - "input": "0.000000035", - "output": "0.00000014" - }, - "amazon/nova-lite-v1": { - "input": "0.00000006", - "output": "0.00000024" - }, - "amazon/nova-pro-v1": { - "input": "0.0000008", - "output": "0.0000032" - }, - "nvidia/nemotron-nano-12b-v2-vl": { - "input": "0.0000002", - "output": "0.0000006" - }, - "anthropic/claude-sonnet-4.5": { - "input": "0.000003", - "output": "0.000015" - }, - "anthropic/claude-sonnet-4": { - "input": "0.000003", - "output": "0.000015" - }, - "google/gemini-2.5-flash": { - "input": "0.0000003", - "output": "0.0000025" - }, - "google/gemini-2.5-flash-lite": { - "input": "0.0000001", - "output": "0.0000004" - }, - "google/gemini-2.5-pro": { - "input": "0.00000125", - "output": "0.00001" - }, - "google/gemma-3-4b-it": { - "input": "0.00000001703012", - "output": "0.0000000681536" - }, - "google/gemma-3-12b-it": { - "input": "0.00000003", - "output": "0.0000001" - }, - "google/gemma-3-27b-it": { - "input": "0.00000009", - "output": "0.00000016" - }, - "openai/gpt-5": { - "input": "0.00000125", - "output": "0.00001" - }, - "openai/gpt-5-chat": { - "input": "0.00000125", - "output": "0.00001" - }, - "openai/gpt-5-mini": { - "input": "0.00000025", - "output": "0.000002" - }, - "openai/gpt-5-nano": { - "input": "0.00000005", - "output": "0.0000004" - }, - "openai/gpt-5-codex": { - "input": "0.00000125", - "output": "0.00001" - }, - "openai/gpt-oss-120b": { - "input": "0.00000005", - "output": "0.00000024" - }, - "openai/gpt-oss-20b": { - "input": "0.00000003", - "output": "0.00000014" - }, - "openai/gpt-4o": { - "input": "0.000006", - "output": "0.000018" - }, - "openai/gpt-4o-mini": { - "input": "0.00000015", - "output": "0.0000006" - }, - "openai/gpt-4.1": { - "input": "0.000002", - "output": "0.000008" - }, - "openai/gpt-4.1-mini": { - "input": "0.0000004", - "output": "0.0000016" - }, - "openai/gpt-4.1-nano": { - "input": "0.0000001", - "output": "0.0000004" - }, - "openai/o4-mini": { - "input": "0.0000011", - "output": "0.0000044" - }, - "openai/o4-mini-high": { - "input": "0.0000011", - "output": "0.0000044" - }, - "x-ai/grok-4": { - "input": "0.000003", - "output": "0.000015" - }, - "x-ai/grok-4-fast": { - "input": "0.0000002", - "output": "0.0000005" - }, - "x-ai/grok-code-fast-1": { - "input": "0.0000002", - "output": "0.0000015" - }, - "z-ai/glm-4.6": { - "input": "0.00000045", - "output": "0.0000019" - }, - "z-ai/glm-4.5v": { - "input": "0.0000006", - "output": "0.0000018" - }, - "z-ai/glm-4.5": { - "input": "0.00000035", - "output": "0.00000155" - }, - "z-ai/glm-4.5-air": { - "input": "0.00000013", - "output": "0.00000085" - }, - "minimax/minimax-m2": { - "input": "0.00000015", - "output": "0.00000045" - }, - "moonshotai/kimi-k2": { - "input": "0.00000014", - "output": "0.00000249" - }, - "deepseek/deepseek-chat": { - "input": "0.0000003", - "output": "0.00000085" - }, - "deepseek/deepseek-v3.2-exp": { - "input": "0.00000027", - "output": "0.0000004" - }, - "deepseek/deepseek-chat-v3.1": { - "input": "0.0000002", - "output": "0.0000008" - }, - "deepseek/deepseek-r1": { - "input": "0.0000003", - "output": "0.0000012" - }, - "deepseek/deepseek-v3.1-terminus": { - "input": "0.00000027", - "output": "0.000001" - }, - "qwen/qwen3-8b": { - "input": "0.000000035", - "output": "0.000000138" - }, - "qwen/qwen3-30b-a3b": { - "input": "0.00000006", - "output": "0.00000022" - }, - "qwen/qwen3-32b": { - "input": "0.00000005", - "output": "0.0000002" - }, - "qwen/qwen3-235b-a22b": { - "input": "0.00000018", - "output": "0.00000054" - }, - "qwen/qwen3-coder": { - "input": "0.00000038", - "output": "0.00000153" - }, - "qwen/qwen3-coder-flash": { - "input": "0.0000003", - "output": "0.0000015" - }, - "qwen/qwen3-coder-plus": { - "input": "0.000001", - "output": "0.000005" - }, - "qwen/qwen3-coder-30b-a3b-instruct": { - "input": "0.00000006", - "output": "0.00000025" - }, - "qwen/qwen3-max": { - "input": "0.0000012", - "output": "0.000006" - }, - "qwen/qwen3-vl-235b-a22b-instruct": { - "input": "0.00000022", - "output": "0.00000088" - }, - "qwen/qwen3-vl-235b-a22b-thinking": { - "input": "0.0000003", - "output": "0.0000012" - }, - "inclusionai/ling-1t": { - "input": "0.00000057", - "output": "0.00000228" - }, - "meta-llama/llama-4-scout": { - "input": "0.00000008", - "output": "0.0000003" - }, - "meta-llama/llama-4-maverick": { - "input": "0.00000015", - "output": "0.0000006" - } - } - } - } -} \ No newline at end of file From 86bcf4b2720f3ed720413f5680f78691e70ae296 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 17 Nov 2025 09:47:30 +0800 Subject: [PATCH 003/140] Update llms.json --- .../NorthwindAuto/wwwroot/chat/llms.json | 112 +++++++++++++----- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json index d6884706d41..90c94d1b459 100644 --- a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json +++ b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json @@ -219,32 +219,39 @@ "base_url": "https://openrouter.ai/api", "api_key": "$OPENROUTER_API_KEY", "models": { - "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", - "llama4:109b": "meta-llama/llama-4-scout:free", - "llama4:400b": "meta-llama/llama-4-maverick:free", + "kat-coder-pro": "kwaipilot/kat-coder-pro:free", + "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", + "tongyi-deepresearch:30b": "alibaba/tongyi-deepresearch-30b-a3b:free", + "longcat-flash-chat": "meituan/longcat-flash-chat:free", + "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", "deepseek-v3.1:671b": "deepseek/deepseek-chat-v3.1:free", - "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", - "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", - "gemma3n:e2b": "google/gemma-3n-e2b-it:free", + "gpt-oss:20b": "openai/gpt-oss-20b:free", "glm-4.5-air": "z-ai/glm-4.5-air:free", + "qwen3-coder": "qwen/qwen3-coder:free", + "kimi-k2": "moonshotai/kimi-k2:free", + "venice-mistral:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", + "gemma3n:2b": "google/gemma-3n-e2b-it:free", + "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", + "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", + "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", + "gemma3n:4b": "google/gemma-3n-e4b-it:free", + "qwen3:30b": "qwen/qwen3-30b-a3b:free", + "qwen3:14b": "qwen/qwen3-14b:free", + "qwen3:235b": "qwen/qwen3-235b-a22b:free", "mai-ds-r1": "microsoft/mai-ds-r1:free", + "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", + "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", + "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", + "gemma3:4b": "google/gemma-3-4b-it:free", + "gemma3:12b": "google/gemma-3-12b-it:free", + "gemma3:27b": "google/gemma-3-27b-it:free", + "deepseek-r1": "deepseek/deepseek-r1-0528:free", + "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free", - "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", - "deepseek-r1-distill-llama:70b": "deepseek/deepseek-r1-distill-llama-70b:free", - "gpt-oss:20b": "openai/gpt-oss-20b:free", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", + "llama3.2:3b": "meta-llama/llama-3.2-3b-instruct:free", "mistral-nemo:12b": "mistralai/mistral-nemo:free", - "venice-uncensored:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", - "llama3.3:8b": "meta-llama/llama-3.3-8b-instruct:free", - "gemma3:27b": "google/gemma-3-27b-it:free", - "qwen3-coder": "qwen/qwen3-coder:free", - "qwen3:235b": "qwen/qwen3-235b-a22b:free", - "qwen3:30b": "qwen/qwen3-30b-a3b:free", "qwen3:4b": "qwen/qwen3-4b:free", - "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", - "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", - "minimax-m2": "minimax/minimax-m2:free" + "mistral:7b": "mistralai/mistral-7b-instruct:free" }, "default_pricing": { "input": "0", @@ -398,6 +405,10 @@ "base_url": "https://api.openai.com", "api_key": "$OPENAI_API_KEY", "models": { + "gpt-5.1": "gpt-5.1", + "gpt-5.1-chat-latest": "gpt-5.1-chat-latest", + "gpt-5.1-codex": "gpt-5.1-codex", + "gpt-5.1-codex-mini": "gpt-5.1-codex-mini", "gpt-5-nano": "gpt-5-nano", "gpt-5-mini": "gpt-5-mini", "gpt-5": "gpt-5", @@ -417,6 +428,22 @@ "gpt-3.5-turbo": "gpt-3.5-turbo" }, "pricing": { + "gpt-5.1": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-chat-latest": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-codex": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-codex-mini": { + "input": "0.00000025", + "output": "0.000002" + }, "gpt-5-nano": { "input": "0.00000005", "output": "0.0000004" @@ -859,6 +886,10 @@ "gemma3:4b": "google/gemma-3-4b-it", "gemma3:12b": "google/gemma-3-12b-it", "gemma3:27b": "google/gemma-3-27b-it", + "gpt-5.1": "openai/gpt-5.1", + "gpt-5.1-chat": "openai/gpt-5.1-chat", + "gpt-5.1-codex": "openai/gpt-5.1-codex", + "gpt-5.1-codex-mini": "openai/gpt-5.1-codex-mini", "gpt-5": "openai/gpt-5", "gpt-5-chat": "openai/gpt-5-chat", "gpt-5-mini": "openai/gpt-5-mini", @@ -883,6 +914,7 @@ "minimax-m2": "minimax/minimax-m2", "kimi-k2": "moonshotai/kimi-k2", "kimi-k2-thinking": "moonshotai/kimi-k2-thinking", + "kimi-linear:48b": "moonshotai/kimi-linear-48b-a3b-instruct", "deepseek-v3.1:671b": "deepseek/deepseek-chat", "deepseek-v3.2-exp": "deepseek/deepseek-v3.2-exp", "deepseek-chat-v3.1:671b": "deepseek/deepseek-chat-v3.1", @@ -905,8 +937,8 @@ }, "pricing": { "meta-llama/llama-3.1-405b-instruct": { - "input": "0.0000008", - "output": "0.0000008" + "input": "0.0000035", + "output": "0.0000035" }, "meta-llama/llama-3.3-70b-instruct": { "input": "0.00000013", @@ -984,6 +1016,22 @@ "input": "0.00000009", "output": "0.00000016" }, + "openai/gpt-5.1": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-chat": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-codex": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-codex-mini": { + "input": "0.00000025", + "output": "0.000002" + }, "openai/gpt-5": { "input": "0.00000125", "output": "0.00001" @@ -1062,23 +1110,31 @@ }, "z-ai/glm-4.5": { "input": "0.00000035", - "output": "0.00000155" + "output": "0.0000015" }, "z-ai/glm-4.5-air": { "input": "0.00000013", "output": "0.00000085" }, "minimax/minimax-m2": { - "input": "0.00000015", - "output": "0.00000045" + "input": "0.000000255", + "output": "0.00000102" }, "moonshotai/kimi-k2": { - "input": "0.00000014", - "output": "0.00000249" + "input": "0.0000005", + "output": "0.0000024" + }, + "moonshotai/kimi-k2-thinking": { + "input": "0.00000055", + "output": "0.00000225" + }, + "moonshotai/kimi-linear-48b-a3b-instruct": { + "input": "0.0000003", + "output": "0.0000006" }, "deepseek/deepseek-chat": { "input": "0.0000003", - "output": "0.00000085" + "output": "0.0000012" }, "deepseek/deepseek-v3.2-exp": { "input": "0.00000027", From 7c4bb50b1c825b2212fcb686af038c7750fbc46a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 17 Nov 2025 11:18:56 +0800 Subject: [PATCH 004/140] Upgrade ServiceStack.Text.Tests.csproj to .NET 10 + fix build + tests --- .../ServiceStack.Text/Common/ParseUtils.cs | 4 +- .../Common/StaticParseMethod.cs | 6 +- .../src/ServiceStack.Text/CsvReader.cs | 3 +- .../Jsv/JsvTypeSerializer.cs | 8 +- .../ServiceStack.Text.Tests/CsvStreamTests.cs | 32 ++++---- .../DataModel/CustomCollection.cs | 4 +- .../DynamicModels/DataModel/DynamicType.cs | 2 +- .../Issues/CompositeKeyIssue.cs | 4 +- .../ServiceStack.Text.Tests.csproj | 11 +-- .../SpanMemoryTests.cs | 9 ++- .../ServiceStack.Text.Tests/SpanTests.cs | 24 +++--- .../StringExtensionsTests.cs | 80 +++++++++---------- .../ServiceStack.Common/VirtualPathUtils.cs | 10 +-- .../InputInfoTests.cs | 4 +- 14 files changed, 103 insertions(+), 98 deletions(-) diff --git a/ServiceStack.Text/src/ServiceStack.Text/Common/ParseUtils.cs b/ServiceStack.Text/src/ServiceStack.Text/Common/ParseUtils.cs index 4accac789ec..ff148e1bcb5 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Common/ParseUtils.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Common/ParseUtils.cs @@ -34,7 +34,7 @@ public static object ParseEnum(Type type, string value) public static ParseStringDelegate GetSpecialParseMethod(Type type) { if (type == typeof(Uri)) - return x => new Uri(x.FromCsvField()); + return x => new Uri(x.FromCsvField().ToString()); //Warning: typeof(object).IsInstanceOfType(typeof(Type)) == True?? if (type.IsInstanceOfType(typeof(Type))) @@ -51,7 +51,7 @@ public static ParseStringDelegate GetSpecialParseMethod(Type type) public static Type ParseType(string assemblyQualifiedName) { - return AssemblyUtils.FindType(assemblyQualifiedName.FromCsvField()); + return AssemblyUtils.FindType(assemblyQualifiedName.FromCsvField().ToString()); } public static object TryParseEnum(Type enumType, string str) diff --git a/ServiceStack.Text/src/ServiceStack.Text/Common/StaticParseMethod.cs b/ServiceStack.Text/src/ServiceStack.Text/Common/StaticParseMethod.cs index 55e9f94dcf7..462e005b216 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Common/StaticParseMethod.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Common/StaticParseMethod.cs @@ -22,7 +22,7 @@ internal static class ParseMethodUtilities public static ParseStringDelegate GetParseFn(string parseMethod) { // Get the static Parse(string) method on the type supplied - var parseMethodInfo = typeof(T).GetStaticMethod(parseMethod, new[] { typeof(string) }); + var parseMethodInfo = typeof(T).GetStaticMethod(parseMethod, [typeof(string)]); if (parseMethodInfo == null) return null; @@ -46,7 +46,7 @@ public static ParseStringDelegate GetParseFn(string parseMethod) } if (parseDelegate != null) - return value => parseDelegate(value.FromCsvField()); + return value => parseDelegate(value.FromCsvField().ToString()); return null; } @@ -80,7 +80,7 @@ public static ParseStringSpanDelegate GetParseStringSpanFn(string parseMethod } if (parseDelegate != null) - return value => parseDelegate(value.ToString().FromCsvField().AsSpan()); + return value => parseDelegate(value.ToString().FromCsvField()); return null; } diff --git a/ServiceStack.Text/src/ServiceStack.Text/CsvReader.cs b/ServiceStack.Text/src/ServiceStack.Text/CsvReader.cs index c3c84738b43..e6795349685 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/CsvReader.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/CsvReader.cs @@ -71,7 +71,8 @@ public static List ParseFields(string line, Func parseFn) while (++i <= len) { var value = EatValue(line, ref i); - to.Add(parseFn != null ? parseFn(value.FromCsvField()) : value.FromCsvField()); + var fieldValue = value == null ? null : value.FromCsvField().Value(); + to.Add(parseFn != null ? parseFn(fieldValue) : fieldValue); } return to; diff --git a/ServiceStack.Text/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs b/ServiceStack.Text/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs index f2cb70af531..e923523bdea 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs @@ -306,7 +306,7 @@ public object UnescapeStringAsObject(ReadOnlySpan value) public string UnescapeSafeString(string value) => JsState.IsCsv ? value - : value.FromCsvField(); + : value.FromCsvField().ToString(); public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) => JsState.IsCsv ? value // already unescaped in CsvReader.ParseFields() @@ -314,11 +314,11 @@ public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) => JsStat public string ParseRawString(string value) => value; - public string ParseString(string value) => value.FromCsvField(); + public string ParseString(string value) => value.FromCsvField().ToString(); - public string ParseString(ReadOnlySpan value) => value.ToString().FromCsvField(); + public string ParseString(ReadOnlySpan value) => value.ToString().FromCsvField().ToString(); - public string UnescapeString(string value) => value.FromCsvField(); + public string UnescapeString(string value) => value.FromCsvField().ToString(); public ReadOnlySpan UnescapeString(ReadOnlySpan value) => value.FromCsvField(); diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/CsvStreamTests.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/CsvStreamTests.cs index c380393f9bc..009cc11b90b 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/CsvStreamTests.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/CsvStreamTests.cs @@ -87,33 +87,33 @@ public void Can_convert_to_csv_field_pipe_delimiter() [Test] public void Can_convert_from_csv_field() { - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + Assert.That("1".FromCsvField().ToString(), Is.EqualTo("1")); + Assert.That("\"3\"\"\"".FromCsvField().ToString(), Is.EqualTo("3\"")); + Assert.That("\"5\"\"five,six\"\"\"".FromCsvField().ToString(), Is.EqualTo("5\"five,six\"")); + Assert.That("\"7,7.1\"".FromCsvField().ToString(), Is.EqualTo("7,7.1")); + Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField().ToString(), Is.EqualTo("\"7,7.1\"")); } [Test] public void Can_convert_from_csv_field_pipe_separator() { CsvConfig.ItemSeperatorString = "|"; - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("7,7.1".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + Assert.That("1".FromCsvField().ToString(), Is.EqualTo("1")); + Assert.That("\"3\"\"\"".FromCsvField().ToString(), Is.EqualTo("3\"")); + Assert.That("\"5\"\"five,six\"\"\"".FromCsvField().ToString(), Is.EqualTo("5\"five,six\"")); + Assert.That("\"7,7.1\"".FromCsvField().ToString(), Is.EqualTo("7,7.1")); + Assert.That("7,7.1".FromCsvField().ToString(), Is.EqualTo("7,7.1")); + Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField().ToString(), Is.EqualTo("\"7,7.1\"")); } [Test] public void Can_convert_from_csv_field_pipe_delimiter() { CsvConfig.ItemDelimiterString = "|"; - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("3\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("|5\"five,six\"|".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("|7,7.1|".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("|\"7,7.1\"|".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + Assert.That("1".FromCsvField().ToString(), Is.EqualTo("1")); + Assert.That("3\"".FromCsvField().ToString(), Is.EqualTo("3\"")); + Assert.That("|5\"five,six\"|".FromCsvField().ToString(), Is.EqualTo("5\"five,six\"")); + Assert.That("|7,7.1|".FromCsvField().ToString(), Is.EqualTo("7,7.1")); + Assert.That("|\"7,7.1\"|".FromCsvField().ToString(), Is.EqualTo("\"7,7.1\"")); } } \ No newline at end of file diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/CustomCollection.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/CustomCollection.cs index 600e7a7a4f4..e5bbf49bc73 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/CustomCollection.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/CustomCollection.cs @@ -38,7 +38,7 @@ public Uri AddressUri return idx < 0 ? null : ( this[idx].Value is string - ? new Uri(((string)this[idx].Value).FromCsvField()) + ? new Uri(((string)this[idx].Value).FromCsvField().ToString()) : this[idx].Value as Uri ); } @@ -59,7 +59,7 @@ public Type SomeType return idx < 0 ? null : ( this[idx].Value is string - ? AssemblyUtils.FindType(((string)this[idx].Value).FromCsvField()) + ? AssemblyUtils.FindType(((string)this[idx].Value).FromCsvField().ToString()) : this[idx].Value as Type ); } diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/DynamicType.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/DynamicType.cs index c361e7be099..5211cc5a5a1 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/DynamicType.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/DynamicModels/DataModel/DynamicType.cs @@ -13,7 +13,7 @@ public object GetTypedValue() var strValue = this.Value as string; if (strValue != null) { - var unescapedValue = strValue.FromCsvField(); + var unescapedValue = strValue.FromCsvField().ToString(); return TypeSerializer.DeserializeFromString(unescapedValue, this.Type); } return Value; diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs index daae1a98bd0..f9f45cc40e0 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs @@ -21,8 +21,8 @@ public CompositeKey(string name, bool value) public CompositeKey(string jsonKey) { - Name = jsonKey.LeftPart(':'); - Value = jsonKey.RightPart(':').ConvertTo(); + Name = jsonKey.LeftPart(':').ToString(); + Value = jsonKey.RightPart(':').ToString().ConvertTo(); } public bool Equals(CompositeKey other) => diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj b/ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj index 8c676ee7e8e..556cab177b0 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj @@ -1,6 +1,6 @@  - net472;net8.0 + net472;net10.0 portable ServiceStack.Text.Tests Library @@ -17,7 +17,7 @@ 1591 - Full + Full @@ -52,14 +52,9 @@ - + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER - - - - - diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs index 58ef2fbed74..e0dc9e1e211 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs @@ -120,10 +120,13 @@ public void Can_ToUtf8_and_FromUtf8_using_Memory() Assert.That(bytes.ToArray(), Is.EquivalentTo(test.expectedBytes)); ReadOnlyMemory chars = bytes.FromUtf8(); + var withoutBomSpan = test.expectedString.WithoutBom(); + var withoutBom = new string(withoutBomSpan.ToArray()); Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) - .Or.EqualTo(test.expectedString.WithoutBom().Length)); - Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) - .Or.EqualTo(test.expectedString.WithoutBom())); + .Or.EqualTo(withoutBom.Length)); + var charsString = new string(chars.Span.ToArray()); + Assert.That(charsString, Is.EqualTo(test.expectedString) + .Or.EqualTo(withoutBom)); } } diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanTests.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanTests.cs index a5393552ea6..08c90d93fd2 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanTests.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/SpanTests.cs @@ -111,10 +111,13 @@ public void Can_ToUtf8_and_FromUtf8_using_Span() Assert.That(bytes.ToArray(), Is.EquivalentTo(test.expectedBytes)); ReadOnlyMemory chars = bytes.FromUtf8(); + var withoutBomSpan = test.expectedString.WithoutBom(); + var withoutBom = new string(withoutBomSpan.ToArray()); Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) - .Or.EqualTo(test.expectedString.WithoutBom().Length)); - Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) - .Or.EqualTo(test.expectedString.WithoutBom())); + .Or.EqualTo(withoutBom.Length)); + var charsString = new string(chars.Span.ToArray()); + Assert.That(charsString, Is.EqualTo(test.expectedString) + .Or.EqualTo(withoutBom)); } } @@ -133,12 +136,15 @@ public void Can_ToUtf8_and_FromUtf8_in_place_using_Span() Memory charBuff = CharPool.GetBuffer(MemoryProvider.Instance.GetUtf8CharCount(bytes.Span)); var charsWritten = MemoryProvider.Instance.FromUtf8(bytes.Span, charBuff.Span); - chars = charBuff.Slice(0, charsWritten).Span; - - Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) - .Or.EqualTo(test.expectedString.WithoutBom().Length)); - Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) - .Or.EqualTo(test.expectedString.WithoutBom())); + var charsResult = charBuff.Slice(0, charsWritten); + + var withoutBomSpan = test.expectedString.WithoutBom(); + var withoutBom = new string(withoutBomSpan.ToArray()); + Assert.That(charsResult.Length, Is.EqualTo(test.expectedString.Length) + .Or.EqualTo(withoutBom.Length)); + var charsResultString = new string(charsResult.Span.ToArray()); + Assert.That(charsResultString, Is.EqualTo(test.expectedString) + .Or.EqualTo(withoutBom)); } } diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs b/ServiceStack.Text/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs index b6fce331170..e2df6a0ae4e 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs +++ b/ServiceStack.Text/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs @@ -21,14 +21,14 @@ public void Can_SplitOnFirst_char_needle() public void Can_LeftPart_and_LeftPart_char_needle() { var str = "user:pass@w:rd"; - Assert.That(str.LeftPart(':'), Is.EqualTo("user")); + Assert.That(str.LeftPart(':').ToString(), Is.EqualTo("user")); Assert.That(str.SplitOnFirst(':')[0], Is.EqualTo("user")); - Assert.That(str.RightPart(':'), Is.EqualTo("pass@w:rd")); + Assert.That(str.RightPart(':').ToString(), Is.EqualTo("pass@w:rd")); Assert.That(str.SplitOnFirst(':').Last(), Is.EqualTo("pass@w:rd")); - Assert.That(str.LeftPart('|'), Is.EqualTo("user:pass@w:rd")); + Assert.That(str.LeftPart('|').ToString(), Is.EqualTo("user:pass@w:rd")); Assert.That(str.SplitOnFirst('|')[0], Is.EqualTo("user:pass@w:rd")); - Assert.That(str.RightPart('|'), Is.EqualTo("user:pass@w:rd")); + Assert.That(str.RightPart('|').ToString(), Is.EqualTo("user:pass@w:rd")); Assert.That(str.SplitOnFirst('|').Last(), Is.EqualTo("user:pass@w:rd")); } @@ -44,14 +44,14 @@ public void Can_SplitOnFirst_string_needle() public void Can_LeftPart_and_RightPart_string_needle() { var str = "user::pass@w:rd"; - Assert.That(str.LeftPart("::"), Is.EqualTo("user")); + Assert.That(str.LeftPart("::").ToString(), Is.EqualTo("user")); Assert.That(str.SplitOnFirst("::")[0], Is.EqualTo("user")); - Assert.That(str.RightPart("::"), Is.EqualTo("pass@w:rd")); + Assert.That(str.RightPart("::").ToString(), Is.EqualTo("pass@w:rd")); Assert.That(str.SplitOnFirst("::").Last(), Is.EqualTo("pass@w:rd")); - Assert.That(str.LeftPart("||"), Is.EqualTo("user::pass@w:rd")); + Assert.That(str.LeftPart("||").ToString(), Is.EqualTo("user::pass@w:rd")); Assert.That(str.SplitOnFirst("||")[0], Is.EqualTo("user::pass@w:rd")); - Assert.That(str.RightPart("||"), Is.EqualTo("user::pass@w:rd")); + Assert.That(str.RightPart("||").ToString(), Is.EqualTo("user::pass@w:rd")); Assert.That(str.SplitOnFirst("||").Last(), Is.EqualTo("user::pass@w:rd")); } @@ -67,14 +67,14 @@ public void Can_SplitOnLast_char_needle() public void Can_LastLeftPart_and_LastRightPart_char_needle() { var str = "user:name:pass@word"; - Assert.That(str.LastLeftPart(':'), Is.EqualTo("user:name")); + Assert.That(str.LastLeftPart(':').ToString(), Is.EqualTo("user:name")); Assert.That(str.SplitOnLast(':')[0], Is.EqualTo("user:name")); - Assert.That(str.LastRightPart(':'), Is.EqualTo("pass@word")); + Assert.That(str.LastRightPart(':').ToString(), Is.EqualTo("pass@word")); Assert.That(str.SplitOnLast(':').Last(), Is.EqualTo("pass@word")); - Assert.That(str.LastLeftPart('|'), Is.EqualTo("user:name:pass@word")); + Assert.That(str.LastLeftPart('|').ToString(), Is.EqualTo("user:name:pass@word")); Assert.That(str.SplitOnLast('|')[0], Is.EqualTo("user:name:pass@word")); - Assert.That(str.LastRightPart('|'), Is.EqualTo("user:name:pass@word")); + Assert.That(str.LastRightPart('|').ToString(), Is.EqualTo("user:name:pass@word")); Assert.That(str.SplitOnLast('|').Last(), Is.EqualTo("user:name:pass@word")); } @@ -90,14 +90,14 @@ public void Can_SplitOnLast_string_needle() public void Can_LastLeftPart_and_LastRightPart_string_needle() { var str = "user::name::pass@word"; - Assert.That(str.LastLeftPart("::"), Is.EqualTo("user::name")); + Assert.That(str.LastLeftPart("::").ToString(), Is.EqualTo("user::name")); Assert.That(str.SplitOnLast("::")[0], Is.EqualTo("user::name")); - Assert.That(str.LastRightPart("::"), Is.EqualTo("pass@word")); + Assert.That(str.LastRightPart("::").ToString(), Is.EqualTo("pass@word")); Assert.That(str.SplitOnLast("::").Last(), Is.EqualTo("pass@word")); - Assert.That(str.LastLeftPart("||"), Is.EqualTo("user::name::pass@word")); + Assert.That(str.LastLeftPart("||").ToString(), Is.EqualTo("user::name::pass@word")); Assert.That(str.SplitOnLast("||")[0], Is.EqualTo("user::name::pass@word")); - Assert.That(str.LastRightPart("||"), Is.EqualTo("user::name::pass@word")); + Assert.That(str.LastRightPart("||").ToString(), Is.EqualTo("user::name::pass@word")); Assert.That(str.SplitOnLast("||").Last(), Is.EqualTo("user::name::pass@word")); } @@ -109,14 +109,14 @@ public void Does_get_ParentDirectory() { var dirSep = DirSep; var filePath = "path{0}to{0}file".FormatWith(dirSep); - Assert.That(filePath.ParentDirectory(), Is.EqualTo("path{0}to".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory(), Is.EqualTo("path".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory(), Is.Null); + Assert.That(filePath.ParentDirectory().ToString(), Is.EqualTo("path{0}to".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("path".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ToString().ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("")); var filePathWithExt = "path{0}to{0}file/".FormatWith(dirSep); - Assert.That(filePathWithExt.ParentDirectory(), Is.EqualTo("path{0}to".FormatWith(dirSep))); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory(), Is.EqualTo("path".FormatWith(dirSep))); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory(), Is.Null); + Assert.That(filePathWithExt.ParentDirectory().ToString(), Is.EqualTo("path{0}to".FormatWith(dirSep))); + Assert.That(filePathWithExt.ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("path".FormatWith(dirSep))); + Assert.That(filePathWithExt.ParentDirectory().ToString().ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("")); } [Test] @@ -124,26 +124,26 @@ public void Does_get_ParentDirectory_of_AltDirectorySeperator() { var dirSep = AltDirSep; var filePath = "path{0}to{0}file".FormatWith(dirSep); - Assert.That(filePath.ParentDirectory(), Is.EqualTo("path{0}to".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory(), Is.EqualTo("path".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory(), Is.Null); + Assert.That(filePath.ParentDirectory().ToString(), Is.EqualTo("path{0}to".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("path".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ToString().ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("")); var filePathWithExt = "path{0}to{0}file{0}".FormatWith(dirSep); - Assert.That(filePathWithExt.ParentDirectory(), Is.EqualTo("path{0}to".FormatWith(dirSep))); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory(), Is.EqualTo("path".FormatWith(dirSep))); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory(), Is.Null); + Assert.That(filePathWithExt.ParentDirectory().ToString(), Is.EqualTo("path{0}to".FormatWith(dirSep))); + Assert.That(filePathWithExt.ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("path".FormatWith(dirSep))); + Assert.That(filePathWithExt.ParentDirectory().ToString().ParentDirectory().ToString().ParentDirectory().ToString(), Is.EqualTo("")); } [Test] public void Does_not_alter_filepath_without_extension() { var path = "path/dir.with.dot/to/file"; - Assert.That(path.WithoutExtension(), Is.EqualTo(path)); + Assert.That(path.WithoutExtension().ToString(), Is.EqualTo(path)); - Assert.That("path/to/file.ext".WithoutExtension(), Is.EqualTo("path/to/file")); + Assert.That("path/to/file.ext".WithoutExtension().ToString(), Is.EqualTo("path/to/file")); } - [TestCase(null, null)] + [TestCase(null, "")] [TestCase("/", "")] [TestCase("/a", "")] [TestCase("/a.b", ".b")] @@ -152,8 +152,8 @@ public void Does_not_alter_filepath_without_extension() [TestCase("/:=#%$@{a.b}.c", ".c")] public void Does_get_Path_extension(string actual, string expected) { - Assert.That(actual.GetExtension(), Is.EqualTo(Path.GetExtension(actual))); - Assert.That(actual.GetExtension(), Is.EqualTo(expected)); + Assert.That(actual.GetExtension().ToString(), Is.EqualTo(Path.GetExtension(actual) ?? "")); + Assert.That(actual.GetExtension().ToString(), Is.EqualTo(expected)); } // 0 1 @@ -315,19 +315,19 @@ public void Can_SafeSubstring_with_no_length() { var input = "TestString"; - Assert.That(input.SafeSubstring(0), Is.EqualTo("TestString")); - Assert.That(input.SafeSubstring(2), Is.EqualTo("stString")); - Assert.That(input.SafeSubstring(20), Is.EqualTo("")); + Assert.That(input.SafeSubstring(0).ToString(), Is.EqualTo("TestString")); + Assert.That(input.SafeSubstring(2).ToString(), Is.EqualTo("stString")); + Assert.That(input.SafeSubstring(20).ToString(), Is.EqualTo("")); } [Test] public void Can_SafeSubstring_with_length() { var input = "TestString"; - Assert.That(input.SafeSubstring(0, 4), Is.EqualTo("Test")); - Assert.That(input.SafeSubstring(2, 4), Is.EqualTo("stSt")); - Assert.That(input.SafeSubstring(20, 4), Is.EqualTo("")); - Assert.That(input.SafeSubstring(0, 20), Is.EqualTo("TestString")); + Assert.That(input.SafeSubstring(0, 4).ToString(), Is.EqualTo("Test")); + Assert.That(input.SafeSubstring(2, 4).ToString(), Is.EqualTo("stSt")); + Assert.That(input.SafeSubstring(20, 4).ToString(), Is.EqualTo("")); + Assert.That(input.SafeSubstring(0, 20).ToString(), Is.EqualTo("TestString")); } [Test] diff --git a/ServiceStack/src/ServiceStack.Common/VirtualPathUtils.cs b/ServiceStack/src/ServiceStack.Common/VirtualPathUtils.cs index 6146a4ad8fd..b03762ebe7c 100644 --- a/ServiceStack/src/ServiceStack.Common/VirtualPathUtils.cs +++ b/ServiceStack/src/ServiceStack.Common/VirtualPathUtils.cs @@ -25,8 +25,8 @@ public static Stack TokenizeVirtualPath(this string str, string virtualP if (string.IsNullOrEmpty(str)) return new Stack(); - var tokens = str.Split(new[] { virtualPathSeparator }, StringSplitOptions.RemoveEmptyEntries); - return new Stack(tokens.Reverse()); + var tokens = str.Split([virtualPathSeparator], StringSplitOptions.RemoveEmptyEntries); + return new Stack(((IEnumerable)tokens).Reverse()); } public static Stack TokenizeResourcePath(this string str, char pathSeparator = '.') @@ -35,14 +35,14 @@ public static Stack TokenizeResourcePath(this string str, char pathSepar return new Stack(); var n = str.Count(c => c == pathSeparator); - var tokens = str.Split(new[] { pathSeparator }, n); + var tokens = str.Split([pathSeparator], n); - return new Stack(tokens.Reverse()); + return new Stack(((IEnumerable)tokens).Reverse()); } public static IEnumerable> GroupByFirstToken(this IEnumerable resourceNames, char pathSeparator = '.') { - return resourceNames.Select(n => n.Split(new[] { pathSeparator }, 2)) + return resourceNames.Select(n => n.Split([pathSeparator], 2)) .GroupBy(t => t[0]); } diff --git a/ServiceStack/tests/ServiceStack.Common.Tests/InputInfoTests.cs b/ServiceStack/tests/ServiceStack.Common.Tests/InputInfoTests.cs index 5018c51c458..2b1640e767e 100644 --- a/ServiceStack/tests/ServiceStack.Common.Tests/InputInfoTests.cs +++ b/ServiceStack/tests/ServiceStack.Common.Tests/InputInfoTests.cs @@ -152,7 +152,7 @@ public void Does_find_correct_min_media_size() Assert.That(mediaRules.MinVisibleSize(nameof(UserAuth.DisplayName)), Is.EqualTo(MediaSizes.ExtraSmall)); Assert.That(mediaRules.MinVisibleSize(nameof(UserAuth.CreatedDate)), Is.EqualTo(MediaSizes.Small)); Assert.That(mediaRules.MinVisibleSize(nameof(UserAuth.Nickname)), Is.EqualTo(MediaSizes.Medium)); - - Assert.That(mediaRules.Reverse().MinVisibleSize(nameof(UserAuth.Id)), Is.EqualTo(MediaSizes.ExtraSmall)); + + Assert.That(mediaRules.AsEnumerable().Reverse().MinVisibleSize(nameof(UserAuth.Id)), Is.EqualTo(MediaSizes.ExtraSmall)); } } \ No newline at end of file From 1b44be750a37ad3575a9727ed07131a96506208d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 17 Nov 2025 11:53:18 +0800 Subject: [PATCH 005/140] Upgrade build-text.yml to .NET 10 --- .github/workflows/build-text.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-text.yml b/.github/workflows/build-text.yml index 85ef7fba735..7e82ae89991 100644 --- a/.github/workflows/build-text.yml +++ b/.github/workflows/build-text.yml @@ -10,11 +10,11 @@ jobs: build-text: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.x - name: Build @@ -26,7 +26,7 @@ jobs: echo "SERVICESTACK_LICENSE=${{ secrets.SERVICESTACK_LICENSE }}" >> $GITHUB_ENV - name: Text tests - run: dotnet test --framework net8.0 ./ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj --logger 'trx;LogFileName=test-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj --logger 'trx;LogFileName=test-results.trx' - name: Text Tests Report uses: dorny/test-reporter@v1 From c62dc06e7d7cb8fccae0d504e9c04c89f7a1b260 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 12:04:33 +0800 Subject: [PATCH 006/140] Fix ConnectionFilter --- .../ServiceStack.OrmLite/OrmLiteConnection.cs | 4 +- .../ConnectionFilterTest.cs | 199 ++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ConnectionFilterTest.cs diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteConnection.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteConnection.cs index e2ce2152f61..7bb7a1d34ab 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteConnection.cs +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteConnection.cs @@ -153,7 +153,7 @@ public void Open() dbConn.Open(); //so the internal connection is wrapped for example by miniprofiler if (Factory.ConnectionFilter != null) - dbConn = Factory.ConnectionFilter(dbConn); + dbConnection = Factory.ConnectionFilter(dbConn); DialectProvider.InitConnection(this); } @@ -187,7 +187,7 @@ public async Task OpenAsync(CancellationToken token = default) await DialectProvider.OpenAsync(dbConn, token).ConfigAwait(); //so the internal connection is wrapped for example by miniprofiler if (Factory.ConnectionFilter != null) - dbConn = Factory.ConnectionFilter(dbConn); + dbConnection = Factory.ConnectionFilter(dbConn); DialectProvider.InitConnection(this); } diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ConnectionFilterTest.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ConnectionFilterTest.cs new file mode 100644 index 00000000000..5320433e374 --- /dev/null +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ConnectionFilterTest.cs @@ -0,0 +1,199 @@ +using System; +using System.Data; +using NUnit.Framework; +using ServiceStack.Data; + +namespace ServiceStack.OrmLite.Tests; + +public class ConnectionFilterTest +{ + [Test] + public void ReproduceConnectionFilterIssue() + { + var dbConnectionFactory = new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider) + { + ConnectionFilter = dbConnection => new DbConnectionWrapper(dbConnection) + }; + + using var connection = dbConnectionFactory.OpenDbConnection(); + Assert.That(connection.CreateCommand().GetType(), Is.EqualTo(typeof(DbCommandWrapper))); + var result = connection.Scalar("select 1"); + Assert.That(result, Is.EqualTo(1)); + } +} + +public sealed class DbConnectionWrapper(IDbConnection innerConnection) : IDbConnection +{ + private IDbConnection InnerConnection { get; } = innerConnection; + + // we rely on the fact that a DbCommandWrapper is returned by the connection + public IDbCommand CreateCommand() + { + return new DbCommandWrapper(InnerConnection.CreateCommand()); + } + + #region uncustomized logic + public void Dispose() + { + InnerConnection.Dispose(); + } + + public IDbTransaction BeginTransaction() + { + return InnerConnection.BeginTransaction(); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return InnerConnection.BeginTransaction(il); + } + + public void Close() + { + InnerConnection.Close(); + } + + public void ChangeDatabase(string databaseName) + { + InnerConnection.ChangeDatabase(databaseName); + } + + public void Open() + { + InnerConnection.Open(); + } + + public string ConnectionString + { + get => InnerConnection.ConnectionString; + set => InnerConnection.ConnectionString = value; + } + + public int ConnectionTimeout => InnerConnection.ConnectionTimeout; + + public string Database => InnerConnection.Database; + + public ConnectionState State => InnerConnection.State; + #endregion +} + +public class DbCommandWrapper(IDbCommand innerCommand) : IDbCommand, IHasDbCommand +{ + public IDbCommand DbCommand => innerCommand; + + // we implement shared transaction scope in our code, which relies on + // this custom transaction setter that prevents nulling out the transaction + public IDbTransaction Transaction + { + get => innerCommand.Transaction; + set + { + if (value != null) + innerCommand.Transaction = value; + } + } + + // we rely on these customized Execute... that logs and profiles each command + public IDataReader ExecuteReader() + { + return ExecuteWithLoggingProfiling(() => innerCommand.ExecuteReader()); + } + + public int ExecuteNonQuery() + { + return ExecuteWithLoggingProfiling(() => innerCommand.ExecuteNonQuery()); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + return ExecuteWithLoggingProfiling(() => innerCommand.ExecuteReader(behavior)); + } + + public object ExecuteScalar() + { + return ExecuteWithLoggingProfiling(() => innerCommand.ExecuteScalar()); + } + + #region uncustomized logic + public void Dispose() + { + innerCommand.Dispose(); + } + + public void Prepare() + { + innerCommand.Prepare(); + } + + public void Cancel() + { + innerCommand.Cancel(); + } + + public IDbDataParameter CreateParameter() + { + return innerCommand.CreateParameter(); + } + + public IDbConnection Connection + { + get => innerCommand.Connection; + set => innerCommand.Connection = value; + } + + public string CommandText + { + get => innerCommand.CommandText; + set => innerCommand.CommandText = value; + } + + public int CommandTimeout + { + get => innerCommand.CommandTimeout; + set => innerCommand.CommandTimeout = value; + } + + public CommandType CommandType + { + get => innerCommand.CommandType; + set => innerCommand.CommandType = value; + } + + public IDataParameterCollection Parameters => innerCommand.Parameters; + + public UpdateRowSource UpdatedRowSource + { + get => innerCommand.UpdatedRowSource; + set => innerCommand.UpdatedRowSource = value; + } + #endregion + + private T ExecuteWithLoggingProfiling(Func func) + { + using var profiler = new DbCommandProfiler(innerCommand); + LogCommand(); + + return func(); + } + + private void LogCommand() + { + TestContext.WriteLine("Logging command"); + // code removed for brevity + } +} + +public sealed class DbCommandProfiler : IDisposable +{ + public DbCommandProfiler(IDbCommand command) + { + TestContext.WriteLine("Started profiling"); + // code removed for brevity + } + + public void Dispose() + { + TestContext.WriteLine("Completed profiling"); + // code removed for brevity + } +} \ No newline at end of file From 06045615278de390861012c23a2c592d594de8d0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 12:05:25 +0800 Subject: [PATCH 007/140] Switch to .NET 10 OrmLite tests --- .github/workflows/integration-ormlite.yml | 16 ++++++++-------- .../ServiceStack.OrmLite.T4.csproj | 5 ++--- .../ServiceStack.OrmLite.FirebirdTests.csproj | 4 ++-- .../ServiceStack.OrmLite.MySql.Tests.csproj | 4 ++-- ...viceStack.OrmLite.MySqlConnector.Tests.csproj | 3 ++- .../ServiceStack.OrmLite.Oracle.Tests.csproj | 4 ++-- .../ServiceStack.OrmLite.PostgreSQL.Tests.csproj | 4 ++-- .../ServiceStack.OrmLite.SqlServer.Tests.csproj | 5 +++-- .../ServiceStack.OrmLite.SqliteTests.csproj | 3 ++- .../ServiceStack.OrmLite.Tests.Benchmarks.csproj | 2 +- .../ServiceStack.OrmLite.Tests.Setup.csproj | 4 ++-- .../ServiceStack.OrmLite.Tests.csproj | 9 ++++----- 12 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.github/workflows/integration-ormlite.yml b/.github/workflows/integration-ormlite.yml index 7af620f764f..aae76443de8 100644 --- a/.github/workflows/integration-ormlite.yml +++ b/.github/workflows/integration-ormlite.yml @@ -48,7 +48,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -63,7 +63,7 @@ jobs: working-directory: ServiceStack.OrmLite/tests env: ORMLITE_DIALECT: Sqlite - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -80,7 +80,7 @@ jobs: env: ORMLITE_DIALECT: PostgreSql11 PGSQL_CONNECTION: Server=localhost;Port=48303;User Id=postgres;Password=test;Database=test;Pooling=true;MinPoolSize=0;MaxPoolSize=200 - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj - name: Test Postgres OrmLite working-directory: ServiceStack.OrmLite/tests @@ -88,7 +88,7 @@ jobs: env: ORMLITE_DIALECT: PostgreSql11 PGSQL_CONNECTION: Server=localhost;Port=48303;User Id=postgres;Password=test;Database=test;Pooling=true;MinPoolSize=0;MaxPoolSize=200 - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -105,7 +105,7 @@ jobs: env: ORMLITE_DIALECT: SqlServer2017 MSSQL_CONNECTION: Server=localhost,48501;Database=master;User Id=sa;Password=Test!tesT;MultipleActiveResultSets=True; - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj - name: Test SQL Server OrmLite @@ -114,7 +114,7 @@ jobs: env: ORMLITE_DIALECT: SqlServer2017 MSSQL_CONNECTION: Server=localhost,48501;Database=master;User Id=sa;Password=Test!tesT;MultipleActiveResultSets=True; - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -131,7 +131,7 @@ jobs: env: ORMLITE_DIALECT: MySql MYSQL_CONNECTION: Server=localhost;Port=48205;Database=test;UID=root;Password=Test!tesT;AllowLoadLocalInfile=true;Convert Zero Datetime=True - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj - name: Test MySql OrmLite working-directory: ServiceStack.OrmLite/tests @@ -139,7 +139,7 @@ jobs: env: ORMLITE_DIALECT: MySql MYSQL_CONNECTION: Server=localhost;Port=48205;Database=test;UID=root;Password=Test!tesT - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.T4/ServiceStack.OrmLite.T4.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.T4/ServiceStack.OrmLite.T4.csproj index 1761bbd7547..f97116960e5 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.T4/ServiceStack.OrmLite.T4.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.T4/ServiceStack.OrmLite.T4.csproj @@ -1,9 +1,8 @@ - - - net472;net6.0;net8.0 + net10.0;net472 + net10.0 ServiceStack.OrmLite.T4 OrmLite.T4 - T4 schema-generation templates for OrmLite diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.FirebirdTests/ServiceStack.OrmLite.FirebirdTests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.FirebirdTests/ServiceStack.OrmLite.FirebirdTests.csproj index 863a387dc9a..01c3d0112f3 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.FirebirdTests/ServiceStack.OrmLite.FirebirdTests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.FirebirdTests/ServiceStack.OrmLite.FirebirdTests.csproj @@ -1,7 +1,7 @@  - net472 - net8.0 + net10.0;net472 + net10.0 ServiceStack.OrmLite.FirebirdTests ServiceStack.OrmLite.FirebirdTests default diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySql.Tests/ServiceStack.OrmLite.MySql.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySql.Tests/ServiceStack.OrmLite.MySql.Tests.csproj index ebc10f944dd..77c5edd9c11 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySql.Tests/ServiceStack.OrmLite.MySql.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySql.Tests/ServiceStack.OrmLite.MySql.Tests.csproj @@ -1,8 +1,8 @@  - net472 - + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.MySql.Tests Library diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySqlConnector.Tests/ServiceStack.OrmLite.MySqlConnector.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySqlConnector.Tests/ServiceStack.OrmLite.MySqlConnector.Tests.csproj index 64f80169bcb..572745cbcaa 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySqlConnector.Tests/ServiceStack.OrmLite.MySqlConnector.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.MySqlConnector.Tests/ServiceStack.OrmLite.MySqlConnector.Tests.csproj @@ -1,7 +1,8 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.MySqlConnector.Tests Library diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Oracle.Tests/ServiceStack.OrmLite.Oracle.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Oracle.Tests/ServiceStack.OrmLite.Oracle.Tests.csproj index f2f81f02637..4ef8f9b4622 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Oracle.Tests/ServiceStack.OrmLite.Oracle.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Oracle.Tests/ServiceStack.OrmLite.Oracle.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net472 - net8.0 + net10.0;net472 + net10.0 ServiceStack.OrmLite.Oracle.Tests ServiceStack.OrmLite.Oracle.Tests default diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj index 8f5199cf518..48880ec020c 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net472 - net8.0 + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.PostgreSQL.Tests Library diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqlServer.Tests/ServiceStack.OrmLite.SqlServer.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqlServer.Tests/ServiceStack.OrmLite.SqlServer.Tests.csproj index d9783b5dc16..2a8a027f0e8 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqlServer.Tests/ServiceStack.OrmLite.SqlServer.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqlServer.Tests/ServiceStack.OrmLite.SqlServer.Tests.csproj @@ -1,7 +1,8 @@  - net8.0 - + net10.0;net472 + net10.0 + ServiceStack.OrmLite.SqlServerTests ServiceStack.OrmLite.SqlServerTests default diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj index ca65ac2d3f2..45f6255ac51 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj @@ -1,7 +1,8 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.SqliteTests ServiceStack.OrmLite.Sqlite32Tests diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Benchmarks/ServiceStack.OrmLite.Tests.Benchmarks.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Benchmarks/ServiceStack.OrmLite.Tests.Benchmarks.csproj index 7c216a28d0a..5f59d354cdb 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Benchmarks/ServiceStack.OrmLite.Tests.Benchmarks.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Benchmarks/ServiceStack.OrmLite.Tests.Benchmarks.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable Exe diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj index 44e86ecf1f0..75993f9ad23 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj @@ -1,7 +1,7 @@  - - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.Tests.Setup Library diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj index 65730db3de8..f16be9650c8 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj @@ -1,8 +1,7 @@  - net8.0;net472 - net8.0 - + net10.0;net472 + net10.0 portable ServiceStack.OrmLite.Tests Library @@ -36,8 +35,8 @@ - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER;NET10_0_OR_GREATER From 51182dfae1748ed7aeba7312ccd9e7b6084d84f1 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 15:04:01 +0800 Subject: [PATCH 008/140] Upgrade to .NET 10 --- .github/workflows/benchmarks-ormlite.yml | 6 +- .github/workflows/build-aws.yml | 8 +- .github/workflows/build-azure.yml | 8 +- .github/workflows/build-blazor.yml | 6 +- .github/workflows/build-core.yml | 26 --- .github/workflows/build-logging.yml | 8 +- .github/workflows/build-ormlite.yml | 10 +- .github/workflows/build-redis.yml | 10 +- .github/workflows/build-servicestack.yml | 14 +- .github/workflows/build-stripe.yml | 8 +- .github/workflows/build-text.yml | 2 +- .github/workflows/feedz-push.yml | 6 +- .github/workflows/github-push.yml | 6 +- ...ntegration-ormlite-community-providers.yml | 10 +- .github/workflows/integration-ormlite.yml | 4 +- .github/workflows/myget-push.yml | 6 +- .github/workflows/nuget-pack.yml | 37 ++-- .github/workflows/nuget-push.yml | 6 +- .github/workflows/pre-release-pack.yml | 7 +- ServiceStack.Aws/src/Directory.Build.props | 8 +- ServiceStack.Aws/src/ServiceStack.Aws.sln | 1 - .../ServiceStack.Aws.Core.csproj | 44 ---- .../ServiceStack.Aws/ServiceStack.Aws.csproj | 16 +- ServiceStack.Aws/tests/Directory.Build.props | 8 +- .../ServiceStack.Aws.DynamoDbTests.csproj | 6 +- .../ServiceStack.Aws.Tests.csproj | 5 +- ServiceStack.Azure/src/Directory.Build.props | 8 +- ServiceStack.Azure/src/ServiceStack.Azure.sln | 1 - .../ServiceStack.Azure.Core.csproj | 40 ---- .../ServiceStack.Azure.csproj | 18 +- .../tests/Directory.Build.props | 8 +- .../Messaging/MqServerAppHostTests.cs | 2 +- .../Messaging/MqServerIntroTests.cs | 2 +- .../ServiceStack.Azure.Tests.csproj | 12 +- ServiceStack.Blazor/src/Directory.Build.props | 8 +- .../ServiceStack.Blazor.csproj | 15 +- .../tests/Directory.Build.props | 8 +- .../Client/MyApp.Client.csproj | 2 +- .../Server/MyApp.Server.csproj | 2 +- .../ServiceModel/MyApp.ServiceModel.csproj | 2 +- .../Tests/MyApp.Tests.csproj | 2 +- .../Server/MyApp.Server.csproj | 2 +- .../ServiceModel/MyApp.ServiceModel.csproj | 2 +- .../Tests/MyApp.Tests.csproj | 2 +- .../Client/MyApp.Client.csproj | 2 +- .../Client/wwwroot/css/app.css | 4 +- .../Server/MyApp.Server.csproj | 2 +- .../ServiceModel/MyApp.ServiceModel.csproj | 2 +- .../Tests/MyApp.Tests.csproj | 2 +- .../Gallery.Server/Gallery.Server.csproj | 2 +- .../Gallery.Server/wwwroot/css/app.css | 4 +- .../Gallery.Wasm.Client.csproj | 2 +- .../Gallery.Wasm.Client/wwwroot/css/app.css | 4 +- .../Gallery.Wasm.Tests.csproj | 2 +- .../Gallery.Wasm/Gallery.Wasm.csproj | 2 +- .../UI.Gallery/Gallery/MyApp/Gallery.csproj | 2 +- ServiceStack.Core/build/build.bat | 1 - ServiceStack.Core/build/build.proj | 144 ------------- ServiceStack.Core/build/build.sh | 2 - ServiceStack.Core/build/build.tasks | 34 ---- ServiceStack.Core/src/Directory.Build.props | 59 ------ ServiceStack.Core/src/ServiceStack.Core.sln | 191 ------------------ .../src/Directory.Build.props | 8 +- .../ServiceStack.Logging.Log4Net.Core.csproj | 28 --- .../ServiceStack.Logging.Log4Net.csproj | 12 +- .../ServiceStack.Logging.NLog.Core.csproj | 30 --- .../ServiceStack.Logging.NLog.csproj | 4 +- .../ServiceStack.Logging.Serilog.csproj | 2 +- .../ServiceStack.Logging.Slack.csproj | 2 +- .../src/ServiceStack.Logging.sln | 2 - .../tests/Directory.Build.props | 8 +- .../src/Directory.Build.props | 8 +- .../ServiceStack.OrmLite.Firebird.Core.csproj | 36 ---- .../ServiceStack.OrmLite.Firebird.csproj | 2 +- .../ServiceStack.OrmLite.MySql.Core.csproj | 35 ---- .../ServiceStack.OrmLite.MySql.csproj | 2 +- ...ceStack.OrmLite.MySqlConnector.Core.csproj | 49 ----- ...ServiceStack.OrmLite.MySqlConnector.csproj | 2 +- .../ServiceStack.OrmLite.Oracle.Core.csproj | 36 ---- .../ServiceStack.OrmLite.Oracle.csproj | 4 +- ...erviceStack.OrmLite.PostgreSQL.Core.csproj | 39 ---- .../ServiceStack.OrmLite.PostgreSQL.csproj | 5 +- ...eStack.OrmLite.SqlServer.Converters.csproj | 2 +- ...ceStack.OrmLite.SqlServer.Data.Core.csproj | 105 ---------- ...ServiceStack.OrmLite.SqlServer.Data.csproj | 6 +- ...ServiceStack.OrmLite.SqlServer.Core.csproj | 40 ---- .../ServiceStack.OrmLite.SqlServer.csproj | 8 +- .../ServiceStack.OrmLite.Sqlite.Data.csproj | 7 +- .../ServiceStack.OrmLite.Sqlite.Core.csproj | 35 ---- .../ServiceStack.OrmLite.Sqlite.csproj | 9 +- .../src/ServiceStack.OrmLite.sln | 11 - .../ServiceStack.OrmLite.Core.csproj | 56 ----- .../ServiceStack.OrmLite.csproj | 9 +- .../tests/Directory.Build.props | 8 +- ...rviceStack.OrmLite.PostgreSQL.Tests.csproj | 3 + ServiceStack.Redis/src/Directory.Build.props | 8 +- ServiceStack.Redis/src/ServiceStack.Redis.sln | 1 - .../ServiceStack.Redis.Core.csproj | 41 ---- .../ServiceStack.Redis.csproj | 19 +- .../Support/OrderedDictionary.cs | 4 +- .../tests/Directory.Build.props | 8 +- .../ServiceStack.Redis.Benchmark.csproj | 3 +- .../ServiceStack.Redis.Tests.Sentinel.csproj | 8 +- .../ServiceStack.Redis.Tests.csproj | 8 +- ServiceStack.Stripe/src/Directory.Build.props | 8 +- .../src/ServiceStack.Stripe.sln | 1 - .../ServiceStack.Stripe.Core.csproj | 31 --- .../ServiceStack.Stripe.csproj | 19 +- .../tests/Directory.Build.props | 8 +- .../ServiceStack.Stripe.Tests.csproj | 5 +- ServiceStack.Text/src/Directory.Build.props | 8 +- ServiceStack.Text/src/ServiceStack.Text.sln | 1 - .../ServiceStack.Text/PclExport.NetCore.cs | 2 +- .../ServiceStack.Text.Core.csproj | 42 ---- .../ServiceStack.Text.csproj | 12 +- ServiceStack.Text/tests/Directory.Build.props | 8 +- .../ServiceStack.Text.Benchmarks.csproj | 4 +- ServiceStack/build/run-webhost-tests.sh | 4 +- ServiceStack/src/Directory.Build.props | 8 +- .../ServiceStack.AI.Chat.csproj | 5 +- .../ServiceStack.AI/ServiceStack.AI.csproj | 14 +- .../OpenApiService.cs | 5 +- .../ServiceStack.Api.OpenApi.Core.csproj | 37 ---- .../ServiceStack.Api.OpenApi.csproj | 20 +- .../Specification/OpenApiDeclaration.cs | 5 +- .../Specification/OpenApiOperation.cs | 7 +- .../Specification/OpenApiSchema.cs | 5 +- .../Support/IOrderedDictionary.cs | 6 +- .../Support/OrderedDictionary.cs | 4 +- .../IOrderedDictionary.cs | 8 +- .../OrderedDictionary.cs | 8 +- .../ServiceStack.AspNetCore.OpenApi.csproj | 19 +- ...ceStack.Authentication.MongoDb.Core.csproj | 29 --- ...ServiceStack.Authentication.MongoDb.csproj | 2 +- ...ServiceStack.Authentication.RavenDb.csproj | 2 +- .../ServiceStack.Caching.Memcached.csproj | 2 +- .../ServiceStack.Client.Core.csproj | 42 ---- .../ServiceStack.Client.csproj | 13 +- .../ServiceStack.Common.Core.csproj | 40 ---- .../ServiceStack.Common.csproj | 4 +- .../ServiceStack.Desktop.csproj | 2 +- .../ServiceStack.Extensions.csproj | 11 +- .../ServiceStack.GoogleCloud.csproj | 15 +- .../ServiceStack.GrpcClient.csproj | 2 +- .../ServiceStack.HttpClient.Core.csproj | 40 ---- .../ServiceStack.HttpClient.csproj | 9 +- .../ServiceStack.Interfaces.Core.csproj | 33 --- .../ServiceStack.Interfaces.csproj | 12 +- .../ServiceStack.Jobs.csproj | 2 +- .../ServiceStack.Kestrel.Core.csproj | 50 ----- .../ServiceStack.Kestrel.csproj | 40 ++-- .../ServiceStack.MsgPack.Core.csproj | 35 ---- .../ServiceStack.MsgPack.csproj | 2 +- .../ServiceStack.Mvc.Core.csproj | 44 ---- .../ServiceStack.Mvc/ServiceStack.Mvc.csproj | 17 +- .../ServiceStack.NetFramework.csproj | 2 +- .../ServiceStack.ProtoBuf.Core.csproj | 33 --- .../ServiceStack.ProtoBuf.csproj | 2 +- .../ServiceStack.RabbitMq.Core.csproj | 25 --- .../ServiceStack.RabbitMq.csproj | 2 +- .../ServiceStack.Razor.csproj | 2 +- .../ServiceStack.Server.Core.csproj | 37 ---- .../ServiceStack.Server.csproj | 9 +- .../src/ServiceStack/ServiceStack.Core.csproj | 59 ------ .../src/ServiceStack/ServiceStack.csproj | 33 +-- ServiceStack/tests/Adhoc/Adhoc.csproj | 2 +- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 13 +- .../tests/CheckCoreApi/CheckCoreApi.csproj | 2 +- ServiceStack/tests/CheckGrpc/CheckGrpc.csproj | 2 +- .../CheckRazorCore/CheckRazorCore.csproj | 2 +- .../CheckRazorPages/CheckRazorPages.csproj | 10 +- .../CheckTemplatesCore.csproj | 2 +- .../tests/CheckWebCore/CheckWebCore.csproj | 2 +- ServiceStack/tests/Directory.Build.props | 8 +- .../tests/NetCoreTests/NetCoreTests.csproj | 2 +- .../tests/NorthwindAuto/NorthwindAuto.csproj | 12 +- .../NorthwindBlazor/NorthwindBlazor.csproj | 12 +- .../ServiceStack.Common.Tests.csproj | 9 +- .../ServiceStack.Extensions.Tests.csproj | 8 +- .../ServiceStack.Server.Tests.csproj | 18 +- .../ServiceStack.Server.Tests.sln | 24 +++ .../Shared/CacheClientTestsAsyncBase.cs | 22 +- .../ServiceStack.ServiceModel.Tests.csproj | 3 +- ...erviceStack.WebHost.Endpoints.Tests.csproj | 9 +- build/build-all.sh | 2 - build/src/Directory.Build.props | 8 +- build/stage-output.sh | 1 - build/sync.bat | 3 - build/sync.sh | 3 - build/tests/Directory.Build.props | 8 +- 190 files changed, 582 insertions(+), 2147 deletions(-) delete mode 100644 .github/workflows/build-core.yml delete mode 100644 ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.Core.csproj delete mode 100644 ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.Core.csproj delete mode 100644 ServiceStack.Core/build/build.bat delete mode 100644 ServiceStack.Core/build/build.sh delete mode 100644 ServiceStack.Core/build/build.tasks delete mode 100644 ServiceStack.Core/src/Directory.Build.props delete mode 100644 ServiceStack.Core/src/ServiceStack.Core.sln delete mode 100644 ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.Core.csproj delete mode 100644 ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.Core.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.Core.csproj delete mode 100644 ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj delete mode 100644 ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.Core.csproj delete mode 100644 ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Client/ServiceStack.Client.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Common/ServiceStack.Common.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack.Server/ServiceStack.Server.Core.csproj delete mode 100644 ServiceStack/src/ServiceStack/ServiceStack.Core.csproj create mode 100644 ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.sln diff --git a/.github/workflows/benchmarks-ormlite.yml b/.github/workflows/benchmarks-ormlite.yml index 4b80758039e..e455d35587c 100644 --- a/.github/workflows/benchmarks-ormlite.yml +++ b/.github/workflows/benchmarks-ormlite.yml @@ -43,11 +43,11 @@ jobs: mysql -h 127.0.0.1 --password=p@55wOrd --user root -e "CREATE DATABASE IF NOT EXISTS test" sqlcmd -U sa -P p@55wOrd -Q "CREATE DATABASE test" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build diff --git a/.github/workflows/build-aws.yml b/.github/workflows/build-aws.yml index ddfe0a839f1..51e801b5193 100644 --- a/.github/workflows/build-aws.yml +++ b/.github/workflows/build-aws.yml @@ -10,11 +10,11 @@ jobs: build-aws: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -28,7 +28,7 @@ jobs: echo "AWS_SECRET_KEY=${{ secrets.AWS_SECRET_KEY }}" >> $GITHUB_ENV - name: Aws Tests - run: dotnet test --framework net8.0 ./ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-azure.yml b/.github/workflows/build-azure.yml index 8e610596717..3d9a7c396ce 100644 --- a/.github/workflows/build-azure.yml +++ b/.github/workflows/build-azure.yml @@ -13,11 +13,11 @@ jobs: build-azure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -30,7 +30,7 @@ jobs: # Azure tests need mocking. # - name: Azure Tests -# run: dotnet test --framework net8.0 ./ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj --logger 'trx;LogFileName=results.trx' +# run: dotnet test --framework net10.0 ./ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj --logger 'trx;LogFileName=results.trx' # # - name: Test Report # uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-blazor.yml b/.github/workflows/build-blazor.yml index 6e6f9ae9aed..ab7462b6a64 100644 --- a/.github/workflows/build-blazor.yml +++ b/.github/workflows/build-blazor.yml @@ -13,11 +13,11 @@ jobs: build-blazor: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build diff --git a/.github/workflows/build-core.yml b/.github/workflows/build-core.yml deleted file mode 100644 index 4f66ebb45d6..00000000000 --- a/.github/workflows/build-core.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build Core - -on: - push: - paths: - - 'ServiceStack.Core/**' - - '.github/workflows/build-core.yml' - -permissions: - contents: read - -jobs: - build-servicestack-core: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.100' - - - - name: Build - working-directory: ServiceStack.Core/build - run: dotnet build ./build.proj - \ No newline at end of file diff --git a/.github/workflows/build-logging.yml b/.github/workflows/build-logging.yml index b68cc10b83a..644cc6a54e9 100644 --- a/.github/workflows/build-logging.yml +++ b/.github/workflows/build-logging.yml @@ -13,11 +13,11 @@ jobs: build-logging: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -30,7 +30,7 @@ jobs: # net472 only package, need to upgrade if possible to run tests or use different runner. # - name: Logging Tests -# run: dotnet test --framework net8.0 ./ServiceStack.Logging/tests/ServiceStack.Logging.Tests/ServiceStack.Logging.Tests.csproj --logger 'trx;LogFileName=results.trx' +# run: dotnet test --framework net10.0 ./ServiceStack.Logging/tests/ServiceStack.Logging.Tests/ServiceStack.Logging.Tests.csproj --logger 'trx;LogFileName=results.trx' # - name: Test Report diff --git a/.github/workflows/build-ormlite.yml b/.github/workflows/build-ormlite.yml index 7664db1cd88..9752405d42d 100644 --- a/.github/workflows/build-ormlite.yml +++ b/.github/workflows/build-ormlite.yml @@ -10,11 +10,11 @@ jobs: build-ormlite: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -27,13 +27,13 @@ jobs: - name: Tests Setup working-directory: ServiceStack.OrmLite/tests - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj - name: Test Sqlite working-directory: ServiceStack.OrmLite/tests env: ORMLITE_DIALECT: Sqlite - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj --logger 'trx;LogFileName=test-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj --logger 'trx;LogFileName=test-results.trx' - name: Test Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-redis.yml b/.github/workflows/build-redis.yml index cb63883517c..6d29f02ced8 100644 --- a/.github/workflows/build-redis.yml +++ b/.github/workflows/build-redis.yml @@ -20,11 +20,11 @@ jobs: ports: - 6379:6379 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -37,7 +37,7 @@ jobs: - name: Test Without Integration working-directory: ServiceStack.Redis/tests - run: dotnet test --framework net8.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory\!=Integration --logger 'trx;LogFileName=non-integration-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory\!=Integration --logger 'trx;LogFileName=non-integration-results.trx' - name: Non-Integration Tests Report uses: dorny/test-reporter@v1 @@ -52,7 +52,7 @@ jobs: - name: Test With Integration id: test_integration working-directory: ServiceStack.Redis/tests - run: dotnet test --framework net8.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory=Integration --logger 'trx;LogFileName=integration-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory=Integration --logger 'trx;LogFileName=integration-results.trx' - name: Integration Tests Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-servicestack.yml b/.github/workflows/build-servicestack.yml index 8781dfaa756..36b960beea3 100644 --- a/.github/workflows/build-servicestack.yml +++ b/.github/workflows/build-servicestack.yml @@ -39,11 +39,11 @@ jobs: ports: - 48303:5432 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -60,7 +60,7 @@ jobs: echo "GOOGLE_FREE_API_KEY=${{ secrets.GOOGLE_FREE_API_KEY }}" >> $GITHUB_ENV - name: Test Extensions - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -72,7 +72,7 @@ jobs: reporter: dotnet-trx - name: Test Common - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -84,7 +84,7 @@ jobs: reporter: dotnet-trx - name: Test ServiceModels - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -99,7 +99,7 @@ jobs: env: PGSQL_CONNECTION: Server=localhost;Port=48303;User Id=postgres;Password=test;Database=test;Pooling=true;MinPoolSize=0;MaxPoolSize=200 MSSQL_CONNECTION: Server=localhost,48501;Database=master;User Id=sa;Password=Test!tesT;MultipleActiveResultSets=True; - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj --logger 'trx;LogFileName=results.trx' --logger 'console;verbosity=detailed' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj --logger 'trx;LogFileName=results.trx' --logger 'console;verbosity=detailed' - name: Test Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-stripe.yml b/.github/workflows/build-stripe.yml index 3aa9019ea0f..22c41bbe305 100644 --- a/.github/workflows/build-stripe.yml +++ b/.github/workflows/build-stripe.yml @@ -10,11 +10,11 @@ jobs: build-stripe: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -26,7 +26,7 @@ jobs: echo "SERVICESTACK_LICENSE=${{ secrets.SERVICESTACK_LICENSE }}" >> $GITHUB_ENV - name: Stripe Tests - run: dotnet test --framework net8.0 ./ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/build-text.yml b/.github/workflows/build-text.yml index 7e82ae89991..7979c561556 100644 --- a/.github/workflows/build-text.yml +++ b/.github/workflows/build-text.yml @@ -14,7 +14,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.x + dotnet-version: 10.0.x - name: Build diff --git a/.github/workflows/feedz-push.yml b/.github/workflows/feedz-push.yml index cee0295744b..bd7a9c23d9b 100644 --- a/.github/workflows/feedz-push.yml +++ b/.github/workflows/feedz-push.yml @@ -9,13 +9,13 @@ jobs: runs-on: ubuntu-latest steps: # Checkout the repo - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Setup .NET SDK - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.*' + dotnet-version: 10.0.x - name: Download artifact uses: dawidd6/action-download-artifact@v2 diff --git a/.github/workflows/github-push.yml b/.github/workflows/github-push.yml index 62c0a93750e..371fabc5f94 100644 --- a/.github/workflows/github-push.yml +++ b/.github/workflows/github-push.yml @@ -9,11 +9,11 @@ jobs: github-push: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Download artifact diff --git a/.github/workflows/integration-ormlite-community-providers.yml b/.github/workflows/integration-ormlite-community-providers.yml index 9eee1ccb413..8cae51229f4 100644 --- a/.github/workflows/integration-ormlite-community-providers.yml +++ b/.github/workflows/integration-ormlite-community-providers.yml @@ -23,11 +23,11 @@ jobs: ports: - 48101:3050 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build @@ -45,7 +45,7 @@ jobs: ORMLITE_DIALECT: Firebird FIREBIRD_CONNECTION: User=test;Password=Test!tesT;Database=/firebird/data/test.gdb;DataSource=localhost;Port=48101;Dialect=3;charset=ISO8859_1;MinPoolSize=0;MaxPoolSize=100; EnableLegacyClientAuth: true - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests.Setup/ServiceStack.OrmLite.Tests.Setup.csproj - name: Test Firebird OrmLite working-directory: ServiceStack.OrmLite/tests @@ -55,7 +55,7 @@ jobs: FIREBIRD_CONNECTION: User=test;Password=Test!tesT;Database=/firebird/data/test.gdb;DataSource=localhost;Port=48101;Dialect=3;charset=ISO8859_1;MinPoolSize=0;MaxPoolSize=100; EnableLegacyClientAuth: true run: | - dotnet test --framework net8.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' --filter "FullyQualifiedName=ServiceStack.OrmLite.Tests.OrderByTests" + dotnet test --framework net10.0 ./ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj --logger 'trx;LogFileName=results.trx' --filter "FullyQualifiedName=ServiceStack.OrmLite.Tests.OrderByTests" - name: Test Report uses: dorny/test-reporter@v1 diff --git a/.github/workflows/integration-ormlite.yml b/.github/workflows/integration-ormlite.yml index aae76443de8..e883ee666bf 100644 --- a/.github/workflows/integration-ormlite.yml +++ b/.github/workflows/integration-ormlite.yml @@ -44,9 +44,9 @@ jobs: ports: - 48205:3306 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x diff --git a/.github/workflows/myget-push.yml b/.github/workflows/myget-push.yml index e48972d553a..7d98138d0ec 100644 --- a/.github/workflows/myget-push.yml +++ b/.github/workflows/myget-push.yml @@ -6,11 +6,11 @@ jobs: myget-push: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Download artifact diff --git a/.github/workflows/nuget-pack.yml b/.github/workflows/nuget-pack.yml index 742130deb75..d907e9a84de 100644 --- a/.github/workflows/nuget-pack.yml +++ b/.github/workflows/nuget-pack.yml @@ -27,11 +27,11 @@ jobs: ports: - 48303:5432 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Env setup @@ -47,7 +47,7 @@ jobs: run: dotnet build ./build.proj - name: Text tests - run: dotnet test --framework net8.0 ./ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj --logger 'trx;LogFileName=test-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Text/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj --logger 'trx;LogFileName=test-results.trx' - name: Text Tests Report uses: dorny/test-reporter@v1 @@ -70,7 +70,7 @@ jobs: - name: Redis Test Without Integration working-directory: ServiceStack.Redis/tests - run: dotnet test --framework net8.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory\!=Integration --logger 'trx;LogFileName=non-integration-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory\!=Integration --logger 'trx;LogFileName=non-integration-results.trx' - name: Redis Non-Integration Tests Report uses: dorny/test-reporter@v1 @@ -85,7 +85,7 @@ jobs: - name: Redis Test With Integration id: test_integration working-directory: ServiceStack.Redis/tests - run: dotnet test --framework net8.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory=Integration --logger 'trx;LogFileName=integration-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj --filter TestCategory=Integration --logger 'trx;LogFileName=integration-results.trx' - name: Redis Integration Tests Report uses: dorny/test-reporter@v1 @@ -104,7 +104,7 @@ jobs: working-directory: ServiceStack.OrmLite/tests env: ORMLITE_DIALECT: Sqlite - run: dotnet test --framework net8.0 ./ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj --logger 'trx;LogFileName=test-results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.OrmLite.SqliteTests/ServiceStack.OrmLite.SqliteTests.csproj --logger 'trx;LogFileName=test-results.trx' - name: Test Sqlite Report uses: dorny/test-reporter@v1 @@ -120,7 +120,7 @@ jobs: run: dotnet build ./build.proj - name: Test ServiceStack.Common - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -132,7 +132,7 @@ jobs: reporter: dotnet-trx - name: Test ServiceStack.ServiceModels - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -147,7 +147,7 @@ jobs: env: PGSQL_CONNECTION: Server=localhost;Port=48303;User Id=postgres;Password=test;Database=test;Pooling=true;MinPoolSize=0;MaxPoolSize=200 MSSQL_CONNECTION: Server=localhost,48501;Database=master;User Id=sa;Password=Test!tesT;MultipleActiveResultSets=True;Encrypt=false;TrustServerCertificate=true; - run: dotnet test --framework net8.0 ./ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -163,7 +163,7 @@ jobs: run: dotnet build ./build.proj - name: Aws Tests - run: dotnet test --framework net8.0 ./ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -185,17 +185,13 @@ jobs: - name: Build Logging working-directory: ServiceStack.Logging/build run: dotnet build ./build.proj - - - name: Build Core - working-directory: ServiceStack.Core/build - run: dotnet build ./build.proj - name: Build Stripe working-directory: ServiceStack.Stripe/build run: dotnet build ./build.proj - name: Stripe Tests - run: dotnet test --framework net8.0 ./ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj --logger 'trx;LogFileName=results.trx' + run: dotnet test --framework net10.0 ./ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj --logger 'trx;LogFileName=results.trx' - name: Test Report uses: dorny/test-reporter@v1 @@ -210,11 +206,11 @@ jobs: needs: build-test-all runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.*' + dotnet-version: 10.0.x - name: Rebuild All working-directory: ./build @@ -229,7 +225,6 @@ jobs: chmod +x ../ServiceStack.Redis/build/build.sh chmod +x ../ServiceStack.Stripe/build/build.sh chmod +x ../ServiceStack.Text/build/build.sh - chmod +x ../ServiceStack.Core/build/build.sh ./build-all.sh - name: Stage output @@ -244,7 +239,7 @@ jobs: - name: Check number of packages if: env.number_of_packages < 73 run: | - echo "Less packages produced than expected, failing." + echo "Expected at least 73 packages, got ${{ env.number_of_packages }}, failing." exit 1 - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/nuget-push.yml b/.github/workflows/nuget-push.yml index e5f738afff9..70f6c0d6f54 100644 --- a/.github/workflows/nuget-push.yml +++ b/.github/workflows/nuget-push.yml @@ -6,11 +6,11 @@ jobs: nuget-push: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Download artifact diff --git a/.github/workflows/pre-release-pack.yml b/.github/workflows/pre-release-pack.yml index 92e0292ba61..aa96e01a227 100644 --- a/.github/workflows/pre-release-pack.yml +++ b/.github/workflows/pre-release-pack.yml @@ -9,11 +9,11 @@ jobs: pre-release-pack: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.100' + dotnet-version: 10.0.x - name: Build All @@ -29,7 +29,6 @@ jobs: chmod +x ../ServiceStack.Redis/build/build.sh chmod +x ../ServiceStack.Stripe/build/build.sh chmod +x ../ServiceStack.Text/build/build.sh - chmod +x ../ServiceStack.Core/build/build.sh ./build-all.sh - name: Stage output diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Aws/src/ServiceStack.Aws.sln b/ServiceStack.Aws/src/ServiceStack.Aws.sln index ce41bc2ad11..ff92e2ff302 100644 --- a/ServiceStack.Aws/src/ServiceStack.Aws.sln +++ b/ServiceStack.Aws/src/ServiceStack.Aws.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{91F39E88 ..\build\build.proj = ..\build\build.proj ..\build\start-local-dynamodb.bat = ..\build\start-local-dynamodb.bat Directory.Build.props = Directory.Build.props - ServiceStack.Aws\ServiceStack.Aws.Core.csproj = ServiceStack.Aws\ServiceStack.Aws.Core.csproj ..\tests\Directory.Build.props = ..\tests\Directory.Build.props EndProjectSection EndProject diff --git a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.Core.csproj b/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.Core.csproj deleted file mode 100644 index 14305fa005e..00000000000 --- a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.Core.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - ServiceStack.Aws.Core - ServiceStack.Aws - ServiceStack.Aws - netstandard2.0;net6.0;net8.0 - ServiceStack.Aws .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.Aws - - ServiceStack;AWS;Amazon;WebServices;DynamoDb;S3;SQS;Cache;CacheClient - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj b/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj index e39f15281e3..facff95ec52 100644 --- a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj +++ b/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj @@ -3,7 +3,7 @@ ServiceStack.Aws ServiceStack.Aws - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack integration for Amazon Web Services (AWS) ServiceStack integration for Amazon Web Services (AWS) including: @@ -21,10 +21,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -50,13 +53,6 @@ - - - - - - - diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Aws/tests/ServiceStack.Aws.DynamoDbTests/ServiceStack.Aws.DynamoDbTests.csproj b/ServiceStack.Aws/tests/ServiceStack.Aws.DynamoDbTests/ServiceStack.Aws.DynamoDbTests.csproj index e15ab56718f..fe5afc0fafe 100644 --- a/ServiceStack.Aws/tests/ServiceStack.Aws.DynamoDbTests/ServiceStack.Aws.DynamoDbTests.csproj +++ b/ServiceStack.Aws/tests/ServiceStack.Aws.DynamoDbTests/ServiceStack.Aws.DynamoDbTests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Aws.DynamoDbTests Library @@ -34,7 +35,4 @@ - - - \ No newline at end of file diff --git a/ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj b/ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj index 751c13eab10..ccc18398c70 100644 --- a/ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj +++ b/ServiceStack.Aws/tests/ServiceStack.Aws.Tests/ServiceStack.Aws.Tests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Aws.Tests Library @@ -32,6 +33,4 @@ - - \ No newline at end of file diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Azure/src/ServiceStack.Azure.sln b/ServiceStack.Azure/src/ServiceStack.Azure.sln index ea311d6a987..2d2ede5fc97 100644 --- a/ServiceStack.Azure/src/ServiceStack.Azure.sln +++ b/ServiceStack.Azure/src/ServiceStack.Azure.sln @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{DCEE435D ProjectSection(SolutionItems) = preProject ..\build\build.bat = ..\build\build.bat ..\build\build.proj = ..\build\build.proj - ServiceStack.Azure\ServiceStack.Azure.Core.csproj = ServiceStack.Azure\ServiceStack.Azure.Core.csproj Directory.Build.props = Directory.Build.props ..\tests\Directory.Build.props = ..\tests\Directory.Build.props EndProjectSection diff --git a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.Core.csproj b/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.Core.csproj deleted file mode 100644 index 717b690a444..00000000000 --- a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.Core.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - ServiceStack.Azure.Core - ServiceStack.Azure - ServiceStack.Azure - netstandard2.0;net6.0;net8.0 - ServiceStack.Azure .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.Azure - - Azure;Windows;ServiceBus;Blob;Table;Storage;WebServices;Cache;CacheClient - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj b/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj index 95b10ff5e91..d7200afd96b 100644 --- a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj +++ b/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj @@ -3,7 +3,7 @@ ServiceStack.Azure ServiceStack.Azure - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack integration for Azure Services enable @@ -17,10 +17,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -43,13 +46,12 @@ - - - - - + + + + diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerAppHostTests.cs b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerAppHostTests.cs index f174a07ff0f..fd3a7249ff4 100644 --- a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerAppHostTests.cs +++ b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerAppHostTests.cs @@ -41,7 +41,7 @@ private static string ConnectionString } public AzureServiceBusMqServerAppHostTests() { -#if !NETCORE_SUPPORT +#if !NETCORE NamespaceManager nm = NamespaceManager.CreateFromConnectionString(ConnectionString); Parallel.ForEach(nm.GetQueues(), qd => { diff --git a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerIntroTests.cs b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerIntroTests.cs index 4c2999eb9a4..02af132554e 100644 --- a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerIntroTests.cs +++ b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/Messaging/MqServerIntroTests.cs @@ -43,7 +43,7 @@ static string ConnectionString public AzureServiceBusMqServerIntroTests() { -#if !NETCORE_SUPPORT +#if !NETCORE NamespaceManager nm = NamespaceManager.CreateFromConnectionString(ConnectionString); Parallel.ForEach(nm.GetQueues(), qd => { diff --git a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj index 065a39cf83f..fbbe72bec79 100644 --- a/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj +++ b/ServiceStack.Azure/tests/ServiceStack.Azure.Tests/ServiceStack.Azure.Tests.csproj @@ -1,7 +1,8 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Azure.Tests Library @@ -16,6 +17,10 @@ false + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + @@ -36,10 +41,9 @@ - - + + - diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Blazor/src/ServiceStack.Blazor/ServiceStack.Blazor.csproj b/ServiceStack.Blazor/src/ServiceStack.Blazor/ServiceStack.Blazor.csproj index 6adf58d3d67..a17103b6507 100644 --- a/ServiceStack.Blazor/src/ServiceStack.Blazor/ServiceStack.Blazor.csproj +++ b/ServiceStack.Blazor/src/ServiceStack.Blazor/ServiceStack.Blazor.csproj @@ -1,17 +1,20 @@  - net6.0;net8.0 + net6.0;net8.0;net10.0 enable enable true - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -29,6 +32,12 @@ + + + + + + diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj index 2206ab107e9..306f766e34f 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 enable MyApp.Client diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj index ea31b4d50a9..a5862178a27 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable MyApp diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/ServiceModel/MyApp.ServiceModel.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/ServiceModel/MyApp.ServiceModel.csproj index dccad9c7ff4..b110c16e524 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/ServiceModel/MyApp.ServiceModel.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/ServiceModel/MyApp.ServiceModel.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 MyApp.ServiceModel diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj index f61f3953873..dc45c7a47ec 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj index bd92f1b3007..10343a98493 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable MyApp diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/ServiceModel/MyApp.ServiceModel.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/ServiceModel/MyApp.ServiceModel.csproj index f8a743c1b5c..de5e7340011 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/ServiceModel/MyApp.ServiceModel.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/ServiceModel/MyApp.ServiceModel.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable MyApp.ServiceModel diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj index 0461808755f..0f06a5bc09d 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj index 075480c2df2..ab3b800bd67 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 enable MyApp.Client enable diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/wwwroot/css/app.css b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/wwwroot/css/app.css index 8802226fb6a..34b70fac6bd 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/wwwroot/css/app.css +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/wwwroot/css/app.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.1.6 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-600:oklch(59.2% .249 .584);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:-ms-input-placeholder,textarea:-ms-input-placeholder),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.right-0{right:calc(var(--spacing)*0)}.bottom-0{bottom:calc(var(--spacing)*0)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-1{margin-top:calc(var(--spacing)*-1)}.-mt-3{margin-top:calc(var(--spacing)*-3)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-16{margin-top:calc(var(--spacing)*16)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-28{margin-right:calc(var(--spacing)*-28)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-48{width:calc(var(--spacing)*48)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.origin-top-right{transform-origin:100% 0}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.appearance-none{appearance:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-sky-500:hover{color:var(--color-sky-500)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (min-width:40rem){.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:ml-3{margin-left:calc(var(--spacing)*3)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-prose{max-width:65ch}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded{border-radius:.25rem}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:mt-8{margin-top:calc(var(--spacing)*8)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:px-10{padding-inline:calc(var(--spacing)*10)}.md\:py-4{padding-block:calc(var(--spacing)*4)}.md\:pr-8{padding-right:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:block{display:block}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:1px}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:border-pink-600:where(.dark,.dark *){border-color:var(--color-pink-600)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-600:where(.dark,.dark *){background-color:var(--color-indigo-600)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-purple-600:where(.dark,.dark *){background-color:var(--color-purple-600)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-300:where(.dark,.dark *){color:var(--color-green-300)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-sky-300:where(.dark,.dark *){color:var(--color-sky-300)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:border-blue-600:where(.dark,.dark *):hover{border-color:var(--color-blue-600)}.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-700:where(.dark,.dark *):hover{background-color:var(--color-indigo-700)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-purple-700:where(.dark,.dark *):hover{background-color:var(--color-purple-700)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-blue-400:where(.dark,.dark *):hover{color:var(--color-blue-400)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-sky-400:where(.dark,.dark *):hover{color:var(--color-sky-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-indigo-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-800)}.dark\:focus\:ring-purple-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-purple-800)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}.dark\:disabled\:bg-gray-800:where(.dark,.dark *):disabled{background-color:var(--color-gray-800)}@media (min-width:40rem){.dark\:sm\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file +/*! tailwindcss v4.1.15 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-600:oklch(59.2% .249 .584);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:placeholder-shown,textarea:placeholder-shown),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.right-0{right:calc(var(--spacing)*0)}.bottom-0{bottom:calc(var(--spacing)*0)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-1{margin-top:calc(var(--spacing)*-1)}.-mt-3{margin-top:calc(var(--spacing)*-3)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-16{margin-top:calc(var(--spacing)*16)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-28{margin-right:calc(var(--spacing)*-28)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-48{width:calc(var(--spacing)*48)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.origin-top-right{transform-origin:100% 0}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.appearance-none{appearance:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-sky-500:hover{color:var(--color-sky-500)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (min-width:40rem){.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:ml-3{margin-left:calc(var(--spacing)*3)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-prose{max-width:65ch}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded{border-radius:.25rem}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:mt-8{margin-top:calc(var(--spacing)*8)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:px-10{padding-inline:calc(var(--spacing)*10)}.md\:py-4{padding-block:calc(var(--spacing)*4)}.md\:pr-8{padding-right:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:block{display:block}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:1px}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:border-pink-600:where(.dark,.dark *){border-color:var(--color-pink-600)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-600:where(.dark,.dark *){background-color:var(--color-indigo-600)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-purple-600:where(.dark,.dark *){background-color:var(--color-purple-600)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-300:where(.dark,.dark *){color:var(--color-green-300)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-sky-300:where(.dark,.dark *){color:var(--color-sky-300)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:border-blue-600:where(.dark,.dark *):hover{border-color:var(--color-blue-600)}.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-700:where(.dark,.dark *):hover{background-color:var(--color-indigo-700)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-purple-700:where(.dark,.dark *):hover{background-color:var(--color-purple-700)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-blue-400:where(.dark,.dark *):hover{color:var(--color-blue-400)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-sky-400:where(.dark,.dark *):hover{color:var(--color-sky-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-indigo-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-800)}.dark\:focus\:ring-purple-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-purple-800)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}.dark\:disabled\:bg-gray-800:where(.dark,.dark *):disabled{background-color:var(--color-gray-800)}@media (min-width:40rem){.dark\:sm\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj index 4af9b7152e4..55399c8c925 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable MyApp diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/ServiceModel/MyApp.ServiceModel.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/ServiceModel/MyApp.ServiceModel.csproj index f8a743c1b5c..de5e7340011 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/ServiceModel/MyApp.ServiceModel.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/ServiceModel/MyApp.ServiceModel.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable MyApp.ServiceModel diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj index f61f3953873..dc45c7a47ec 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj index e5bb578de8a..fd777a4596e 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable MyApp.Client diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/wwwroot/css/app.css b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/wwwroot/css/app.css index efcd7d42a3b..5037c7903de 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/wwwroot/css/app.css +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/wwwroot/css/app.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.1.6 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-3xl:1.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:-ms-input-placeholder,textarea:-ms-input-placeholder),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-4{top:calc(var(--spacing)*4)}.top-8{top:calc(var(--spacing)*8)}.right-0{right:calc(var(--spacing)*0)}.right-1\/2{right:50%}.right-4{right:calc(var(--spacing)*4)}.right-full{right:100%}.bottom-0{bottom:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-full{left:100%}.-z-20{z-index:calc(20*-1)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.my-5{margin-block:calc(var(--spacing)*5)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.-mr-3{margin-right:calc(var(--spacing)*-3)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-40{margin-right:calc(var(--spacing)*-40)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.-mb-16{margin-bottom:calc(var(--spacing)*-16)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-6{margin-left:calc(var(--spacing)*-6)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-40{height:calc(var(--spacing)*40)}.h-48{height:calc(var(--spacing)*48)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-40{width:calc(var(--spacing)*40)}.w-48{width:calc(var(--spacing)*48)}.w-70{width:calc(var(--spacing)*70)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:100% 0}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x:calc(1/4*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-3\/4{--tw-translate-y:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1/3*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/60{background-color:#625fff99}@supports (color:color-mix(in lab, red, red)){.bg-indigo-500\/60{background-color:color-mix(in oklab,var(--color-indigo-500)60%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-10{padding-top:calc(var(--spacing)*10)}.pt-32{padding-top:calc(var(--spacing)*32)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pb-40{padding-bottom:calc(var(--spacing)*40)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-sky-500:hover{color:var(--color-sky-500)}.hover\:text-white:hover{color:var(--color-white)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (min-width:40rem){.sm\:relative{position:relative}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:mx-auto{margin-inline:auto}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-5{margin-top:calc(var(--spacing)*5)}.sm\:mt-12{margin-top:calc(var(--spacing)*12)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:-mb-48{margin-bottom:calc(var(--spacing)*-48)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:var(--container-2xl)}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-prose{max-width:65ch}.sm\:max-w-xl{max-width:var(--container-xl)}.sm\:-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:translate-x-1\/2{--tw-translate-x:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded-lg{border-radius:var(--radius-lg)}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-0{padding-inline:calc(var(--spacing)*0)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:px-12{padding-inline:calc(var(--spacing)*12)}.sm\:py-12{padding-block:calc(var(--spacing)*12)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pt-12{padding-top:calc(var(--spacing)*12)}.sm\:pt-16{padding-top:calc(var(--spacing)*16)}.sm\:pt-24{padding-top:calc(var(--spacing)*24)}.sm\:pt-32{padding-top:calc(var(--spacing)*32)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-center{text-align:center}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:pr-8{padding-right:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:absolute{position:absolute}.lg\:relative{position:relative}.lg\:inset-y-0{inset-block:calc(var(--spacing)*0)}.lg\:right-0{right:calc(var(--spacing)*0)}.lg\:left-0{left:calc(var(--spacing)*0)}.lg\:left-80{left:calc(var(--spacing)*80)}.lg\:m-0{margin:calc(var(--spacing)*0)}.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:mt-6{margin-top:calc(var(--spacing)*6)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:h-full{height:100%}.lg\:w-1\/2{width:50%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-none{max-width:none}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:-translate-x-3\/4{--tw-translate-x:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:gap-24{gap:calc(var(--spacing)*24)}.lg\:overflow-hidden{overflow:hidden}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:px-4{padding-inline:calc(var(--spacing)*4)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-16{padding-block:calc(var(--spacing)*16)}.lg\:py-24{padding-block:calc(var(--spacing)*24)}.lg\:py-48{padding-block:calc(var(--spacing)*48)}.lg\:pt-8{padding-top:calc(var(--spacing)*8)}.lg\:pt-24{padding-top:calc(var(--spacing)*24)}.lg\:pt-32{padding-top:calc(var(--spacing)*32)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}.lg\:pb-14{padding-bottom:calc(var(--spacing)*14)}.lg\:pl-12{padding-left:calc(var(--spacing)*12)}.lg\:text-left{text-align:left}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-600:where(.dark,.dark *){background-color:var(--color-indigo-600)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-purple-600:where(.dark,.dark *){background-color:var(--color-purple-600)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-300:where(.dark,.dark *){color:var(--color-green-300)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-sky-300:where(.dark,.dark *){color:var(--color-sky-300)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-700:where(.dark,.dark *):hover{background-color:var(--color-indigo-700)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-purple-700:where(.dark,.dark *):hover{background-color:var(--color-purple-700)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-blue-400:where(.dark,.dark *):hover{color:var(--color-blue-400)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-sky-400:where(.dark,.dark *):hover{color:var(--color-sky-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-indigo-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-800)}.dark\:focus\:ring-purple-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-purple-800)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}.dark\:disabled\:bg-gray-800:where(.dark,.dark *):disabled{background-color:var(--color-gray-800)}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file +/*! tailwindcss v4.1.15 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-3xl:1.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:placeholder-shown,textarea:placeholder-shown),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-4{top:calc(var(--spacing)*4)}.top-8{top:calc(var(--spacing)*8)}.right-0{right:calc(var(--spacing)*0)}.right-1\/2{right:50%}.right-4{right:calc(var(--spacing)*4)}.right-full{right:100%}.bottom-0{bottom:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-full{left:100%}.-z-20{z-index:calc(20*-1)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.my-5{margin-block:calc(var(--spacing)*5)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.-mr-3{margin-right:calc(var(--spacing)*-3)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-40{margin-right:calc(var(--spacing)*-40)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.-mb-16{margin-bottom:calc(var(--spacing)*-16)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-6{margin-left:calc(var(--spacing)*-6)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-40{height:calc(var(--spacing)*40)}.h-48{height:calc(var(--spacing)*48)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-40{width:calc(var(--spacing)*40)}.w-48{width:calc(var(--spacing)*48)}.w-70{width:calc(var(--spacing)*70)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:100% 0}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x:calc(1/4*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-3\/4{--tw-translate-y:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1/3*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/60{background-color:#625fff99}@supports (color:color-mix(in lab, red, red)){.bg-indigo-500\/60{background-color:color-mix(in oklab,var(--color-indigo-500)60%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-10{padding-top:calc(var(--spacing)*10)}.pt-32{padding-top:calc(var(--spacing)*32)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pb-40{padding-bottom:calc(var(--spacing)*40)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-sky-500:hover{color:var(--color-sky-500)}.hover\:text-white:hover{color:var(--color-white)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (min-width:40rem){.sm\:relative{position:relative}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:mx-auto{margin-inline:auto}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-5{margin-top:calc(var(--spacing)*5)}.sm\:mt-12{margin-top:calc(var(--spacing)*12)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:-mb-48{margin-bottom:calc(var(--spacing)*-48)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:var(--container-2xl)}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-prose{max-width:65ch}.sm\:max-w-xl{max-width:var(--container-xl)}.sm\:-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:translate-x-1\/2{--tw-translate-x:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded-lg{border-radius:var(--radius-lg)}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-0{padding-inline:calc(var(--spacing)*0)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:px-12{padding-inline:calc(var(--spacing)*12)}.sm\:py-12{padding-block:calc(var(--spacing)*12)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pt-12{padding-top:calc(var(--spacing)*12)}.sm\:pt-16{padding-top:calc(var(--spacing)*16)}.sm\:pt-24{padding-top:calc(var(--spacing)*24)}.sm\:pt-32{padding-top:calc(var(--spacing)*32)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-center{text-align:center}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:pr-8{padding-right:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:absolute{position:absolute}.lg\:relative{position:relative}.lg\:inset-y-0{inset-block:calc(var(--spacing)*0)}.lg\:right-0{right:calc(var(--spacing)*0)}.lg\:left-0{left:calc(var(--spacing)*0)}.lg\:left-80{left:calc(var(--spacing)*80)}.lg\:m-0{margin:calc(var(--spacing)*0)}.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:mt-6{margin-top:calc(var(--spacing)*6)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:h-full{height:100%}.lg\:w-1\/2{width:50%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-none{max-width:none}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:-translate-x-3\/4{--tw-translate-x:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:gap-24{gap:calc(var(--spacing)*24)}.lg\:overflow-hidden{overflow:hidden}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:px-4{padding-inline:calc(var(--spacing)*4)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-16{padding-block:calc(var(--spacing)*16)}.lg\:py-24{padding-block:calc(var(--spacing)*24)}.lg\:py-48{padding-block:calc(var(--spacing)*48)}.lg\:pt-8{padding-top:calc(var(--spacing)*8)}.lg\:pt-24{padding-top:calc(var(--spacing)*24)}.lg\:pt-32{padding-top:calc(var(--spacing)*32)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}.lg\:pb-14{padding-bottom:calc(var(--spacing)*14)}.lg\:pl-12{padding-left:calc(var(--spacing)*12)}.lg\:text-left{text-align:left}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-600:where(.dark,.dark *){background-color:var(--color-indigo-600)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-purple-600:where(.dark,.dark *){background-color:var(--color-purple-600)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-300:where(.dark,.dark *){color:var(--color-green-300)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-sky-300:where(.dark,.dark *){color:var(--color-sky-300)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-700:where(.dark,.dark *):hover{background-color:var(--color-indigo-700)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-purple-700:where(.dark,.dark *):hover{background-color:var(--color-purple-700)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-blue-400:where(.dark,.dark *):hover{color:var(--color-blue-400)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-sky-400:where(.dark,.dark *):hover{color:var(--color-sky-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-indigo-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-800)}.dark\:focus\:ring-purple-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-purple-800)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}.dark\:disabled\:bg-gray-800:where(.dark,.dark *):disabled{background-color:var(--color-gray-800)}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj index 1e350916956..3552d22ed1c 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 enable MyApp.Client enable diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/wwwroot/css/app.css b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/wwwroot/css/app.css index 5d431ed2fbf..f87b12905a5 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/wwwroot/css/app.css +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/wwwroot/css/app.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.1.6 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-600:oklch(59.2% .249 .584);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-3xl:1.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:-ms-input-placeholder,textarea:-ms-input-placeholder),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-4{top:calc(var(--spacing)*4)}.top-8{top:calc(var(--spacing)*8)}.right-0{right:calc(var(--spacing)*0)}.right-1\/2{right:50%}.right-4{right:calc(var(--spacing)*4)}.right-full{right:100%}.bottom-0{bottom:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-full{left:100%}.-z-20{z-index:calc(20*-1)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.my-5{margin-block:calc(var(--spacing)*5)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-1{margin-top:calc(var(--spacing)*-1)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.-mr-3{margin-right:calc(var(--spacing)*-3)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-24{margin-right:calc(var(--spacing)*-24)}.-mr-40{margin-right:calc(var(--spacing)*-40)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.-mb-16{margin-bottom:calc(var(--spacing)*-16)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-6{margin-left:calc(var(--spacing)*-6)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-40{height:calc(var(--spacing)*40)}.h-48{height:calc(var(--spacing)*48)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-40{width:calc(var(--spacing)*40)}.w-48{width:calc(var(--spacing)*48)}.w-70{width:calc(var(--spacing)*70)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:100% 0}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x:calc(1/4*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-3\/4{--tw-translate-y:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1/3*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/60{background-color:#625fff99}@supports (color:color-mix(in lab, red, red)){.bg-indigo-500\/60{background-color:color-mix(in oklab,var(--color-indigo-500)60%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-10{padding-top:calc(var(--spacing)*10)}.pt-32{padding-top:calc(var(--spacing)*32)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pb-40{padding-bottom:calc(var(--spacing)*40)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-white:hover{color:var(--color-white)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media (min-width:40rem){.sm\:relative{position:relative}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:mx-auto{margin-inline:auto}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-5{margin-top:calc(var(--spacing)*5)}.sm\:mt-12{margin-top:calc(var(--spacing)*12)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:-mb-48{margin-bottom:calc(var(--spacing)*-48)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:var(--container-2xl)}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-prose{max-width:65ch}.sm\:max-w-xl{max-width:var(--container-xl)}.sm\:-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:translate-x-1\/2{--tw-translate-x:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded{border-radius:.25rem}.sm\:rounded-lg{border-radius:var(--radius-lg)}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-0{padding-inline:calc(var(--spacing)*0)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:px-12{padding-inline:calc(var(--spacing)*12)}.sm\:py-12{padding-block:calc(var(--spacing)*12)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pt-12{padding-top:calc(var(--spacing)*12)}.sm\:pt-16{padding-top:calc(var(--spacing)*16)}.sm\:pt-24{padding-top:calc(var(--spacing)*24)}.sm\:pt-32{padding-top:calc(var(--spacing)*32)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-center{text-align:center}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:absolute{position:absolute}.lg\:relative{position:relative}.lg\:inset-y-0{inset-block:calc(var(--spacing)*0)}.lg\:right-0{right:calc(var(--spacing)*0)}.lg\:left-0{left:calc(var(--spacing)*0)}.lg\:left-80{left:calc(var(--spacing)*80)}.lg\:m-0{margin:calc(var(--spacing)*0)}.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:mt-6{margin-top:calc(var(--spacing)*6)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:h-full{height:100%}.lg\:w-1\/2{width:50%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-none{max-width:none}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:-translate-x-3\/4{--tw-translate-x:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:gap-24{gap:calc(var(--spacing)*24)}.lg\:overflow-hidden{overflow:hidden}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:px-4{padding-inline:calc(var(--spacing)*4)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-16{padding-block:calc(var(--spacing)*16)}.lg\:py-24{padding-block:calc(var(--spacing)*24)}.lg\:py-48{padding-block:calc(var(--spacing)*48)}.lg\:pt-8{padding-top:calc(var(--spacing)*8)}.lg\:pt-24{padding-top:calc(var(--spacing)*24)}.lg\:pt-32{padding-top:calc(var(--spacing)*32)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}.lg\:pb-14{padding-bottom:calc(var(--spacing)*14)}.lg\:pl-12{padding-left:calc(var(--spacing)*12)}.lg\:text-left{text-align:left}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:border-pink-600:where(.dark,.dark *){border-color:var(--color-pink-600)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:border-blue-600:where(.dark,.dark *):hover{border-color:var(--color-blue-600)}.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file +/*! tailwindcss v4.1.15 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-600:oklch(59.2% .249 .584);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-3xl:1.5rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}:root.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}*,:after,:before,::backdrop{border-color:hsl(var(--border))}::file-selector-button{border-color:hsl(var(--border))}[data-collapsed] [data-collapse=hidden]{display:none}[data-collapse-off=opacity-100]{opacity:1}[data-collapsed] [data-collapse=opacity-0]{opacity:0}[data-collapse-off=-translate-x-full]{transform:translate(0)}[data-collapsed] [data-collapse=-translate-x-full]{transform:translate(-100%)}@media (min-width:640px){.youtube{width:761px;height:428px}}[v-cloak]{display:none}b,strong{font-weight:600}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background-color:#ccc}[role=dialog].z-10{z-index:60}em{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;margin-left:.125em;margin-right:.125em;padding:.125em .5rem;font-style:normal;font-weight:400}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{appearance:none;background-color:#fff;border-width:1px;padding:.5rem .75rem;font-size:1rem}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=week]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=color]:focus,[multiple]:focus,textarea:focus,select:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;outline:2px solid #0000}input::-moz-placeholder{color:#6b7280;opacity:1}textarea::-moz-placeholder{color:#6b7280;opacity:1}:is(input:placeholder-shown,textarea:placeholder-shown),input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{-webkit-print-color-adjust:exact;color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;-webkit-print-color-adjust:unset;color-adjust:unset;padding-right:.75rem}[type=checkbox],[type=radio]{appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:#2563eb;background-color:#fff;background-origin:border-box;border-width:1px;flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}[type=color]{height:2.4rem;padding:2px 3px}[type=range]{height:2.4rem}@media (min-width:640px){[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=week],[type=search],[type=tel],[type=time],[type=color],[multiple],textarea,select{font-size:.875rem;line-height:1.25rem}}.dark input:-webkit-autofill{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:hover{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:focus{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input:-webkit-autofill:active{-webkit-text-fill-color:#fff;transition:background-color 5000s ease-in-out}.dark input[data-autocompleted]{background-color:#0000!important}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);--tw-aspect-w:16;position:relative}.aspect-w-16>*{width:100%;height:100%;position:absolute;inset:0}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-4{top:calc(var(--spacing)*4)}.top-8{top:calc(var(--spacing)*8)}.right-0{right:calc(var(--spacing)*0)}.right-1\/2{right:50%}.right-4{right:calc(var(--spacing)*4)}.right-full{right:100%}.bottom-0{bottom:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-full{left:100%}.-z-20{z-index:calc(20*-1)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.col-span-6{grid-column:span 6/span 6}.col-span-12{grid-column:span 12/span 12}.float-left{float:left}.float-start{float:inline-start}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-6{margin:calc(var(--spacing)*6)}.-mx-1\.5{margin-inline:calc(var(--spacing)*-1.5)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-my-1\.5{margin-block:calc(var(--spacing)*-1.5)}.-my-2{margin-block:calc(var(--spacing)*-2)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.my-5{margin-block:calc(var(--spacing)*5)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-1{margin-top:calc(var(--spacing)*-1)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.-mr-3{margin-right:calc(var(--spacing)*-3)}.-mr-12{margin-right:calc(var(--spacing)*-12)}.-mr-24{margin-right:calc(var(--spacing)*-24)}.-mr-40{margin-right:calc(var(--spacing)*-40)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-8{margin-right:calc(var(--spacing)*8)}.-mb-16{margin-bottom:calc(var(--spacing)*-16)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-0\.5{margin-left:calc(var(--spacing)*-.5)}.-ml-6{margin-left:calc(var(--spacing)*-6)}.-ml-px{margin-left:-1px}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-40{height:calc(var(--spacing)*40)}.h-48{height:calc(var(--spacing)*48)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-60{max-height:calc(var(--spacing)*60)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-full{min-height:100%}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-40{width:calc(var(--spacing)*40)}.w-48{width:calc(var(--spacing)*48)}.w-70{width:calc(var(--spacing)*70)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-screen{width:100vw}.max-w-7xl{max-width:var(--container-7xl)}.max-w-24{max-width:calc(var(--spacing)*24)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.origin-top-right{transform-origin:100% 0}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x:calc(1/4*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-3\/4{--tw-translate-y:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1/3*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-transparent{border-color:#0000}.border-yellow-400{border-color:var(--color-yellow-400)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/60{background-color:#625fff99}@supports (color:color-mix(in lab, red, red)){.bg-indigo-500\/60{background-color:color-mix(in oklab,var(--color-indigo-500)60%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.fill-gray-600{fill:var(--color-gray-600)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-10{padding-top:calc(var(--spacing)*10)}.pt-32{padding-top:calc(var(--spacing)*32)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-6{padding-right:calc(var(--spacing)*6)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pb-40{padding-bottom:calc(var(--spacing)*40)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.leading-8{--tw-leading:calc(var(--spacing)*8);line-height:calc(var(--spacing)*8)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-black{color:var(--color-black)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-800{color:var(--color-purple-800)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.placeholder-red-300::placeholder{color:var(--color-red-300)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:inline:is(:where(.group):hover *){display:inline}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing)*4)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-violet-50::file-selector-button{background-color:var(--color-violet-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing)*2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-violet-700::file-selector-button{color:var(--color-violet-700)}.focus-within\:border-indigo-500:focus-within{border-color:var(--color-indigo-500)}.focus-within\:border-red-500:focus-within{border-color:var(--color-red-500)}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-indigo-500:focus-within{--tw-ring-color:var(--color-indigo-500)}.focus-within\:ring-red-500:focus-within{--tw-ring-color:var(--color-red-500)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-indigo-500:hover{color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-white:hover{color:var(--color-white)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:file\:bg-violet-100:hover::file-selector-button{background-color:var(--color-violet-100)}}.focus\:z-10:focus{z-index:10}.focus\:\!border-none:focus{--tw-border-style:none!important;border-style:none!important}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:bg-indigo-500:focus{background-color:var(--color-indigo-500)}.focus\:bg-red-500:focus{background-color:var(--color-red-500)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,hsl(var(--ring)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-cyan-300:focus{--tw-ring-color:var(--color-cyan-300)}.focus\:ring-cyan-500:focus{--tw-ring-color:var(--color-cyan-500)}.focus\:ring-green-300:focus{--tw-ring-color:var(--color-green-300)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-green-600:focus{--tw-ring-color:var(--color-green-600)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-sky-300:focus{--tw-ring-color:var(--color-sky-300)}.focus\:ring-sky-500:focus{--tw-ring-color:var(--color-sky-500)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:ring-yellow-600:focus{--tw-ring-color:var(--color-yellow-600)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-green-50:focus{--tw-ring-offset-color:var(--color-green-50)}.focus\:ring-offset-yellow-50:focus{--tw-ring-offset-color:var(--color-yellow-50)}.focus\:\!outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media (min-width:40rem){.sm\:relative{position:relative}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:-mx-6{margin-inline:calc(var(--spacing)*-6)}.sm\:mx-4{margin-inline:calc(var(--spacing)*4)}.sm\:mx-auto{margin-inline:auto}.sm\:my-8{margin-block:calc(var(--spacing)*8)}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:mt-5{margin-top:calc(var(--spacing)*5)}.sm\:mt-12{margin-top:calc(var(--spacing)*12)}.sm\:mt-24{margin-top:calc(var(--spacing)*24)}.sm\:-mb-48{margin-bottom:calc(var(--spacing)*-48)}.sm\:ml-6{margin-left:calc(var(--spacing)*6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table-cell{display:table-cell}.sm\:h-screen{height:100vh}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:var(--container-2xl)}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-prose{max-width:65ch}.sm\:max-w-xl{max-width:var(--container-xl)}.sm\:-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:translate-x-1\/2{--tw-translate-x:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}:where(.sm\:space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-x-reverse)))}.sm\:overflow-hidden{overflow:hidden}.sm\:rounded{border-radius:.25rem}.sm\:rounded-lg{border-radius:var(--radius-lg)}.sm\:rounded-md{border-radius:var(--radius-md)}.sm\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:border-transparent{border-color:#0000}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-0{padding-inline:calc(var(--spacing)*0)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:px-12{padding-inline:calc(var(--spacing)*12)}.sm\:py-12{padding-block:calc(var(--spacing)*12)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:pt-1{padding-top:calc(var(--spacing)*1)}.sm\:pt-3{padding-top:calc(var(--spacing)*3)}.sm\:pt-12{padding-top:calc(var(--spacing)*12)}.sm\:pt-16{padding-top:calc(var(--spacing)*16)}.sm\:pt-24{padding-top:calc(var(--spacing)*24)}.sm\:pt-32{padding-top:calc(var(--spacing)*32)}.sm\:pr-6{padding-right:calc(var(--spacing)*6)}.sm\:pb-0{padding-bottom:calc(var(--spacing)*0)}.sm\:pb-4{padding-bottom:calc(var(--spacing)*4)}.sm\:pl-3{padding-left:calc(var(--spacing)*3)}.sm\:pl-6{padding-left:calc(var(--spacing)*6)}.sm\:pl-16{padding-left:calc(var(--spacing)*16)}.sm\:text-center{text-align:center}.sm\:text-left{text-align:left}.sm\:align-middle{vertical-align:middle}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:duration-700{--tw-duration:.7s;transition-duration:.7s}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:inset-y-0{inset-block:calc(var(--spacing)*0)}.md\:mt-5{margin-top:calc(var(--spacing)*5)}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:inline{display:inline}.md\:table-cell{display:table-cell}.md\:w-64{width:calc(var(--spacing)*64)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:flex-col{flex-direction:column}.md\:place-items-center{place-items:center}.md\:rounded-lg{border-radius:var(--radius-lg)}.md\:p-4{padding:calc(var(--spacing)*4)}.md\:px-6{padding-inline:calc(var(--spacing)*6)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:absolute{position:absolute}.lg\:relative{position:relative}.lg\:inset-y-0{inset-block:calc(var(--spacing)*0)}.lg\:right-0{right:calc(var(--spacing)*0)}.lg\:left-0{left:calc(var(--spacing)*0)}.lg\:left-80{left:calc(var(--spacing)*80)}.lg\:m-0{margin:calc(var(--spacing)*0)}.lg\:-mx-8{margin-inline:calc(var(--spacing)*-8)}.lg\:mt-6{margin-top:calc(var(--spacing)*6)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:table-cell{display:table-cell}.lg\:h-full{height:100%}.lg\:w-1\/2{width:50%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-none{max-width:none}.lg\:max-w-screen-md{max-width:var(--breakpoint-md)}.lg\:-translate-x-3\/4{--tw-translate-x:calc(calc(3/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:gap-24{gap:calc(var(--spacing)*24)}.lg\:overflow-hidden{overflow:hidden}.lg\:rounded-md{border-radius:var(--radius-md)}.lg\:p-2{padding:calc(var(--spacing)*2)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:px-4{padding-inline:calc(var(--spacing)*4)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-16{padding-block:calc(var(--spacing)*16)}.lg\:py-24{padding-block:calc(var(--spacing)*24)}.lg\:py-48{padding-block:calc(var(--spacing)*48)}.lg\:pt-8{padding-top:calc(var(--spacing)*8)}.lg\:pt-24{padding-top:calc(var(--spacing)*24)}.lg\:pt-32{padding-top:calc(var(--spacing)*32)}.lg\:pr-8{padding-right:calc(var(--spacing)*8)}.lg\:pb-14{padding-bottom:calc(var(--spacing)*14)}.lg\:pl-12{padding-left:calc(var(--spacing)*12)}.lg\:text-left{text-align:left}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media (hover:hover){.lg\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}}}@media (min-width:80rem){.xl\:col-span-6{grid-column:span 6/span 6}.xl\:inline{display:inline}.xl\:table-cell{display:table-cell}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-screen-lg{max-width:var(--breakpoint-lg)}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:96rem){.\32 xl\:table-cell{display:table-cell}.\32 xl\:max-w-screen-xl{max-width:var(--breakpoint-xl)}}:where(.dark\:divide-gray-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark\:divide-gray-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-gray-800)}.dark\:border-2:where(.dark,.dark *){border-style:var(--tw-border-style);border-width:2px}.dark\:border-gray-600:where(.dark,.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:where(.dark,.dark *){border-color:var(--color-gray-700)}.dark\:border-gray-800:where(.dark,.dark *){border-color:var(--color-gray-800)}.dark\:border-indigo-400:where(.dark,.dark *){border-color:var(--color-indigo-400)}.dark\:border-pink-600:where(.dark,.dark *){border-color:var(--color-pink-600)}.dark\:bg-black:where(.dark,.dark *){background-color:var(--color-black)}.dark\:bg-blue-200:where(.dark,.dark *){background-color:var(--color-blue-200)}.dark\:bg-blue-600:where(.dark,.dark *){background-color:var(--color-blue-600)}.dark\:bg-blue-800:where(.dark,.dark *){background-color:var(--color-blue-800)}.dark\:bg-cyan-600:where(.dark,.dark *){background-color:var(--color-cyan-600)}.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:#99a1afbf}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-400\/75:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-gray-400)75%,transparent)}}.dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:where(.dark,.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-200:where(.dark,.dark *){background-color:var(--color-green-200)}.dark\:bg-green-600:where(.dark,.dark *){background-color:var(--color-green-600)}.dark\:bg-green-800:where(.dark,.dark *){background-color:var(--color-green-800)}.dark\:bg-indigo-400:where(.dark,.dark *){background-color:var(--color-indigo-400)}.dark\:bg-indigo-800:where(.dark,.dark *){background-color:var(--color-indigo-800)}.dark\:bg-indigo-900:where(.dark,.dark *){background-color:var(--color-indigo-900)}.dark\:bg-red-200:where(.dark,.dark *){background-color:var(--color-red-200)}.dark\:bg-red-600:where(.dark,.dark *){background-color:var(--color-red-600)}.dark\:bg-sky-600:where(.dark,.dark *){background-color:var(--color-sky-600)}.dark\:bg-transparent:where(.dark,.dark *){background-color:#0000}.dark\:bg-yellow-200:where(.dark,.dark *){background-color:var(--color-yellow-200)}.dark\:fill-gray-300:where(.dark,.dark *){fill:var(--color-gray-300)}.dark\:text-black:where(.dark,.dark *){color:var(--color-black)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-50:where(.dark,.dark *){color:var(--color-gray-50)}.dark\:text-gray-100:where(.dark,.dark *){color:var(--color-gray-100)}.dark\:text-gray-200:where(.dark,.dark *){color:var(--color-gray-200)}.dark\:text-gray-300:where(.dark,.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-gray-500:where(.dark,.dark *){color:var(--color-gray-500)}.dark\:text-gray-600:where(.dark,.dark *){color:var(--color-gray-600)}.dark\:text-green-100:where(.dark,.dark *){color:var(--color-green-100)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-green-500:where(.dark,.dark *){color:var(--color-green-500)}.dark\:text-green-600:where(.dark,.dark *){color:var(--color-green-600)}.dark\:text-indigo-300:where(.dark,.dark *){color:var(--color-indigo-300)}.dark\:text-indigo-400:where(.dark,.dark *){color:var(--color-indigo-400)}.dark\:text-indigo-500:where(.dark,.dark *){color:var(--color-indigo-500)}.dark\:text-red-800:where(.dark,.dark *){color:var(--color-red-800)}.dark\:text-slate-400:where(.dark,.dark *){color:var(--color-slate-400)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.dark\:ring-offset-black:where(.dark,.dark *){--tw-ring-offset-color:var(--color-black)}.dark\:ring-offset-green-200:where(.dark,.dark *){--tw-ring-offset-color:var(--color-green-200)}.dark\:file\:bg-violet-900:where(.dark,.dark *)::file-selector-button{background-color:var(--color-violet-900)}.dark\:file\:text-violet-200:where(.dark,.dark *)::file-selector-button{color:var(--color-violet-200)}@media (hover:hover){.dark\:hover\:border-blue-600:where(.dark,.dark *):hover{border-color:var(--color-blue-600)}.dark\:hover\:bg-blue-700:where(.dark,.dark *):hover{background-color:var(--color-blue-700)}.dark\:hover\:bg-blue-900:where(.dark,.dark *):hover{background-color:var(--color-blue-900)}.dark\:hover\:bg-cyan-700:where(.dark,.dark *):hover{background-color:var(--color-cyan-700)}.dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--color-gray-700)}.dark\:hover\:bg-gray-800:where(.dark,.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-green-700:where(.dark,.dark *):hover{background-color:var(--color-green-700)}.dark\:hover\:bg-indigo-300:where(.dark,.dark *):hover{background-color:var(--color-indigo-300)}.dark\:hover\:bg-indigo-800:where(.dark,.dark *):hover{background-color:var(--color-indigo-800)}.dark\:hover\:bg-red-700:where(.dark,.dark *):hover{background-color:var(--color-red-700)}.dark\:hover\:bg-sky-700:where(.dark,.dark *):hover{background-color:var(--color-sky-700)}.dark\:hover\:text-blue-200:where(.dark,.dark *):hover{color:var(--color-blue-200)}.dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--color-gray-50)}.dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--color-gray-100)}.dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--color-gray-300)}.dark\:hover\:text-gray-400:where(.dark,.dark *):hover{color:var(--color-gray-400)}.dark\:hover\:text-green-400:where(.dark,.dark *):hover{color:var(--color-green-400)}.dark\:hover\:text-indigo-400:where(.dark,.dark *):hover{color:var(--color-indigo-400)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}.dark\:hover\:file\:bg-violet-800:where(.dark,.dark *):hover::file-selector-button{background-color:var(--color-violet-800)}}.dark\:focus\:text-black:where(.dark,.dark *):focus{color:var(--color-black)}.dark\:focus\:ring-black:where(.dark,.dark *):focus{--tw-ring-color:var(--color-black)}.dark\:focus\:ring-blue-800:where(.dark,.dark *):focus{--tw-ring-color:var(--color-blue-800)}.dark\:focus\:ring-cyan-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-cyan-500)}.dark\:focus\:ring-green-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-green-500)}.dark\:focus\:ring-indigo-600:where(.dark,.dark *):focus{--tw-ring-color:var(--color-indigo-600)}.dark\:focus\:ring-red-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-red-500)}.dark\:focus\:ring-sky-500:where(.dark,.dark *):focus{--tw-ring-color:var(--color-sky-500)}@media (min-width:64rem){@media (hover:hover){.dark\:lg\:hover\:bg-gray-900:where(.dark,.dark *):hover{background-color:var(--color-gray-900)}}}}.animate-spin{animation:1s linear infinite spin}.dark{color-scheme:dark}.lang:before{content:"$ "}.notes a{color:#1e40af}.dark .notes{color:#9ca3af}.dark .notes a{color:#3f83f8}.svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%231E40AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat;padding-right:1.35rem}.dark .svg-external{background:url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%233f83f8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") 100% 100% no-repeat}div.example-code{border-radius:8px;margin:28px 0}.dark p code{color:#f3f4f6;background-color:#1e3a8a}p code{color:#3b82f6;background-color:#eff6ff;border-radius:.25rem;padding:.25em .5rem}.example ::-webkit-scrollbar{width:8px;height:6px}.example ::-webkit-scrollbar-thumb{background-color:#ccc}input:-webkit-autofill{transition:background-color 600000s,color 600000s}input:-webkit-autofill:focus{transition:background-color 600000s,color 600000s}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj index 46bd6b475cc..dc0aa7cb60f 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm/Gallery.Wasm.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm/Gallery.Wasm.csproj index 0c4294cae9f..38791ed8084 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm/Gallery.Wasm.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm/Gallery.Wasm.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable MyApp diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj index ec098241b53..6779756fb7e 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable aspnet-MyApp-7b2ab71a-0b50-423f-969d-e35a9402b1b5 diff --git a/ServiceStack.Core/build/build.bat b/ServiceStack.Core/build/build.bat deleted file mode 100644 index 0f92114869b..00000000000 --- a/ServiceStack.Core/build/build.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet build build.proj /property:Configuration=Release \ No newline at end of file diff --git a/ServiceStack.Core/build/build.proj b/ServiceStack.Core/build/build.proj index e17fad16e12..607403725e3 100644 --- a/ServiceStack.Core/build/build.proj +++ b/ServiceStack.Core/build/build.proj @@ -34,7 +34,6 @@ - @@ -50,149 +49,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.Core/build/build.sh b/ServiceStack.Core/build/build.sh deleted file mode 100644 index a1e7d97541e..00000000000 --- a/ServiceStack.Core/build/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -dotnet build ./build.proj /property:Configuration=Release diff --git a/ServiceStack.Core/build/build.tasks b/ServiceStack.Core/build/build.tasks deleted file mode 100644 index 85b58d4ea49..00000000000 --- a/ServiceStack.Core/build/build.tasks +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.Core/src/Directory.Build.props b/ServiceStack.Core/src/Directory.Build.props deleted file mode 100644 index ba36236d6cf..00000000000 --- a/ServiceStack.Core/src/Directory.Build.props +++ /dev/null @@ -1,59 +0,0 @@ - - - - 8.10.1 - ServiceStack - ServiceStack, Inc. - © 2008-2022 ServiceStack, Inc - true - https://github.com/ServiceStack/ServiceStack - https://servicestack.net/terms - https://servicestack.net/img/logo-128.png - https://docs.servicestack.net/release-notes-history - git - https://github.com/ServiceStack/ServiceStack.git - embedded - latest - true - true - false - $(NoWarn);1591;CS3001;CS3002;CS3003;CS3005;CS3009;CS3015;CS3024;CS3027 - - - - true - true - - - - $(DefineConstants);NETFX;NET45;NET472 - True - False - ../../../servicestack.snk - - - - $(DefineConstants);NETSTANDARD;NETSTANDARD2_0 - - - - $(DefineConstants);NET6_0;NET6_0_OR_GREATER - - - - $(DefineConstants);NET8_0;NET6_0_OR_GREATER - - - - $(DefineConstants);NETCORE;NETCORE_SUPPORT - - - - - - - - DEBUG - - - diff --git a/ServiceStack.Core/src/ServiceStack.Core.sln b/ServiceStack.Core/src/ServiceStack.Core.sln deleted file mode 100644 index 2988ad705d7..00000000000 --- a/ServiceStack.Core/src/ServiceStack.Core.sln +++ /dev/null @@ -1,191 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.16 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack", "..\..\ServiceStack\src\ServiceStack\ServiceStack.Core.csproj", "{2CBDA033-FD96-4875-973C-59F1EE410672}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Api.OpenApi", "..\..\ServiceStack\src\ServiceStack.Api.OpenApi\ServiceStack.Api.OpenApi.Core.csproj", "{97A23599-F3AE-4586-948E-8F0DC91F12E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Client", "..\..\ServiceStack\src\ServiceStack.Client\ServiceStack.Client.Core.csproj", "{3A9F5F61-B0CA-455D-9199-917F5A4C8ACC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Common", "..\..\ServiceStack\src\ServiceStack.Common\ServiceStack.Common.Core.csproj", "{847F545E-FE54-4BB9-B740-FE571429C798}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.HttpClient", "..\..\ServiceStack\src\ServiceStack.HttpClient\ServiceStack.HttpClient.Core.csproj", "{19BBFD40-F64C-42F9-8017-C248763EA160}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Interfaces", "..\..\ServiceStack\src\ServiceStack.Interfaces\ServiceStack.Interfaces.Core.csproj", "{2F752020-EC92-4D55-AA5C-5E9B242F7A75}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Kestrel", "..\..\ServiceStack\src\ServiceStack.Kestrel\ServiceStack.Kestrel.Core.csproj", "{4F775AE0-D5E9-4A50-914D-78137DB4520B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.MsgPack", "..\..\ServiceStack\src\ServiceStack.MsgPack\ServiceStack.MsgPack.Core.csproj", "{4AFCCEBB-6861-452D-BE3F-2398CCA0D311}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.ProtoBuf", "..\..\ServiceStack\src\ServiceStack.ProtoBuf\ServiceStack.ProtoBuf.Core.csproj", "{0AFE2A54-11CB-4208-8471-B28BFBC78754}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Mvc", "..\..\ServiceStack\src\ServiceStack.Mvc\ServiceStack.Mvc.Core.csproj", "{DE290F4E-6579-4D09-A0D1-3D589C7DAEFD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Server", "..\..\ServiceStack\src\ServiceStack.Server\ServiceStack.Server.Core.csproj", "{C982D183-C545-4F6F-ABC4-F4EB75E44A1F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9E7B19A2-161E-40FE-B486-C8C0EA9ED61A}" - ProjectSection(SolutionItems) = preProject - ..\build\build.proj = ..\build\build.proj - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Aws.Core", "..\..\ServiceStack.Aws\src\ServiceStack.Aws\ServiceStack.Aws.Core.csproj", "{AF5A0CAA-1809-498F-9D3B-5479826E9BB0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Azure.Core", "..\..\ServiceStack.Azure\src\ServiceStack.Azure\ServiceStack.Azure.Core.csproj", "{7B2C053B-944B-4033-9E0A-E2F4BA6E7191}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Logging.Log4Net.Core", "..\..\ServiceStack.Logging\src\ServiceStack.Logging.Log4Net\ServiceStack.Logging.Log4Net.Core.csproj", "{2C961AFA-429E-4AFE-AE3A-AE4751FF7241}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Logging.NLog.Core", "..\..\ServiceStack.Logging\src\ServiceStack.Logging.NLog\ServiceStack.Logging.NLog.Core.csproj", "{E1B00A69-2466-40AE-A84A-C89D06901B5C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Core", "..\..\ServiceStack.Text\src\ServiceStack.Text\ServiceStack.Text.Core.csproj", "{99E40C1A-852F-432A-94A0-EA38626DA898}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite\ServiceStack.OrmLite.Core.csproj", "{8D76F90E-B7DD-4BAD-83E3-870266055809}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.Firebird.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.Firebird\ServiceStack.OrmLite.Firebird.Core.csproj", "{01AB886F-25F9-4460-894D-401E0E0357F0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.MySql.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.MySql\ServiceStack.OrmLite.MySql.Core.csproj", "{8E1F1F7B-5928-46CC-93A4-0D0AF9383ED3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.MySqlConnector.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.MySqlConnector\ServiceStack.OrmLite.MySqlConnector.Core.csproj", "{00CD4E7C-CBE8-4CAD-8C28-5F8BBFCB9961}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.PostgreSQL.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.PostgreSQL\ServiceStack.OrmLite.PostgreSQL.Core.csproj", "{3CB5EE50-CFD6-4B9C-B503-804CF12A745D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.Sqlite.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.Sqlite\ServiceStack.OrmLite.Sqlite.Core.csproj", "{BD6BAB4B-476C-4904-A27F-AFB2FD864B04}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.SqlServer.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.SqlServer\ServiceStack.OrmLite.SqlServer.Core.csproj", "{1915189D-AD7D-4244-B064-F2EA072DAE72}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.SqlServer.Data.Core", "..\..\ServiceStack.OrmLite\src\ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.Core.csproj", "{5ED6DD3E-1A26-4E93-B536-CBBCA4BA20B7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Redis.Core", "..\..\ServiceStack.Redis\src\ServiceStack.Redis\ServiceStack.Redis.Core.csproj", "{A6ACA9FB-BFAA-4905-8A62-411B6A30C825}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Stripe.Core", "..\..\ServiceStack.Stripe\src\ServiceStack.Stripe\ServiceStack.Stripe.Core.csproj", "{59AFE593-45BA-4164-90D5-2CF3BD524E2A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.RabbitMq.Core", "..\..\ServiceStack\src\ServiceStack.RabbitMq\ServiceStack.RabbitMq.Core.csproj", "{2EB7E75A-1DBC-4A30-BAC7-AD43BC176E0D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Authentication.MongoDb.Core", "..\..\ServiceStack\src\ServiceStack.Authentication.MongoDb\ServiceStack.Authentication.MongoDb.Core.csproj", "{A1D681C1-864D-4746-A787-8E539B57B843}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2CBDA033-FD96-4875-973C-59F1EE410672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CBDA033-FD96-4875-973C-59F1EE410672}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CBDA033-FD96-4875-973C-59F1EE410672}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CBDA033-FD96-4875-973C-59F1EE410672}.Release|Any CPU.Build.0 = Release|Any CPU - {97A23599-F3AE-4586-948E-8F0DC91F12E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97A23599-F3AE-4586-948E-8F0DC91F12E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97A23599-F3AE-4586-948E-8F0DC91F12E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97A23599-F3AE-4586-948E-8F0DC91F12E8}.Release|Any CPU.Build.0 = Release|Any CPU - {3A9F5F61-B0CA-455D-9199-917F5A4C8ACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A9F5F61-B0CA-455D-9199-917F5A4C8ACC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A9F5F61-B0CA-455D-9199-917F5A4C8ACC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A9F5F61-B0CA-455D-9199-917F5A4C8ACC}.Release|Any CPU.Build.0 = Release|Any CPU - {847F545E-FE54-4BB9-B740-FE571429C798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {847F545E-FE54-4BB9-B740-FE571429C798}.Debug|Any CPU.Build.0 = Debug|Any CPU - {847F545E-FE54-4BB9-B740-FE571429C798}.Release|Any CPU.ActiveCfg = Release|Any CPU - {847F545E-FE54-4BB9-B740-FE571429C798}.Release|Any CPU.Build.0 = Release|Any CPU - {19BBFD40-F64C-42F9-8017-C248763EA160}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19BBFD40-F64C-42F9-8017-C248763EA160}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19BBFD40-F64C-42F9-8017-C248763EA160}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19BBFD40-F64C-42F9-8017-C248763EA160}.Release|Any CPU.Build.0 = Release|Any CPU - {2F752020-EC92-4D55-AA5C-5E9B242F7A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F752020-EC92-4D55-AA5C-5E9B242F7A75}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F752020-EC92-4D55-AA5C-5E9B242F7A75}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F752020-EC92-4D55-AA5C-5E9B242F7A75}.Release|Any CPU.Build.0 = Release|Any CPU - {4F775AE0-D5E9-4A50-914D-78137DB4520B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F775AE0-D5E9-4A50-914D-78137DB4520B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F775AE0-D5E9-4A50-914D-78137DB4520B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F775AE0-D5E9-4A50-914D-78137DB4520B}.Release|Any CPU.Build.0 = Release|Any CPU - {4AFCCEBB-6861-452D-BE3F-2398CCA0D311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4AFCCEBB-6861-452D-BE3F-2398CCA0D311}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4AFCCEBB-6861-452D-BE3F-2398CCA0D311}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4AFCCEBB-6861-452D-BE3F-2398CCA0D311}.Release|Any CPU.Build.0 = Release|Any CPU - {0AFE2A54-11CB-4208-8471-B28BFBC78754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0AFE2A54-11CB-4208-8471-B28BFBC78754}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0AFE2A54-11CB-4208-8471-B28BFBC78754}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0AFE2A54-11CB-4208-8471-B28BFBC78754}.Release|Any CPU.Build.0 = Release|Any CPU - {DE290F4E-6579-4D09-A0D1-3D589C7DAEFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE290F4E-6579-4D09-A0D1-3D589C7DAEFD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE290F4E-6579-4D09-A0D1-3D589C7DAEFD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE290F4E-6579-4D09-A0D1-3D589C7DAEFD}.Release|Any CPU.Build.0 = Release|Any CPU - {C982D183-C545-4F6F-ABC4-F4EB75E44A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C982D183-C545-4F6F-ABC4-F4EB75E44A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C982D183-C545-4F6F-ABC4-F4EB75E44A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C982D183-C545-4F6F-ABC4-F4EB75E44A1F}.Release|Any CPU.Build.0 = Release|Any CPU - {AF5A0CAA-1809-498F-9D3B-5479826E9BB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF5A0CAA-1809-498F-9D3B-5479826E9BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF5A0CAA-1809-498F-9D3B-5479826E9BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF5A0CAA-1809-498F-9D3B-5479826E9BB0}.Release|Any CPU.Build.0 = Release|Any CPU - {7B2C053B-944B-4033-9E0A-E2F4BA6E7191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B2C053B-944B-4033-9E0A-E2F4BA6E7191}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B2C053B-944B-4033-9E0A-E2F4BA6E7191}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B2C053B-944B-4033-9E0A-E2F4BA6E7191}.Release|Any CPU.Build.0 = Release|Any CPU - {2C961AFA-429E-4AFE-AE3A-AE4751FF7241}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C961AFA-429E-4AFE-AE3A-AE4751FF7241}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C961AFA-429E-4AFE-AE3A-AE4751FF7241}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C961AFA-429E-4AFE-AE3A-AE4751FF7241}.Release|Any CPU.Build.0 = Release|Any CPU - {E1B00A69-2466-40AE-A84A-C89D06901B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E1B00A69-2466-40AE-A84A-C89D06901B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E1B00A69-2466-40AE-A84A-C89D06901B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E1B00A69-2466-40AE-A84A-C89D06901B5C}.Release|Any CPU.Build.0 = Release|Any CPU - {99E40C1A-852F-432A-94A0-EA38626DA898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99E40C1A-852F-432A-94A0-EA38626DA898}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99E40C1A-852F-432A-94A0-EA38626DA898}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99E40C1A-852F-432A-94A0-EA38626DA898}.Release|Any CPU.Build.0 = Release|Any CPU - {8D76F90E-B7DD-4BAD-83E3-870266055809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D76F90E-B7DD-4BAD-83E3-870266055809}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D76F90E-B7DD-4BAD-83E3-870266055809}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D76F90E-B7DD-4BAD-83E3-870266055809}.Release|Any CPU.Build.0 = Release|Any CPU - {01AB886F-25F9-4460-894D-401E0E0357F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01AB886F-25F9-4460-894D-401E0E0357F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01AB886F-25F9-4460-894D-401E0E0357F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01AB886F-25F9-4460-894D-401E0E0357F0}.Release|Any CPU.Build.0 = Release|Any CPU - {8E1F1F7B-5928-46CC-93A4-0D0AF9383ED3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E1F1F7B-5928-46CC-93A4-0D0AF9383ED3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E1F1F7B-5928-46CC-93A4-0D0AF9383ED3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E1F1F7B-5928-46CC-93A4-0D0AF9383ED3}.Release|Any CPU.Build.0 = Release|Any CPU - {00CD4E7C-CBE8-4CAD-8C28-5F8BBFCB9961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00CD4E7C-CBE8-4CAD-8C28-5F8BBFCB9961}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00CD4E7C-CBE8-4CAD-8C28-5F8BBFCB9961}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00CD4E7C-CBE8-4CAD-8C28-5F8BBFCB9961}.Release|Any CPU.Build.0 = Release|Any CPU - {3CB5EE50-CFD6-4B9C-B503-804CF12A745D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CB5EE50-CFD6-4B9C-B503-804CF12A745D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CB5EE50-CFD6-4B9C-B503-804CF12A745D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CB5EE50-CFD6-4B9C-B503-804CF12A745D}.Release|Any CPU.Build.0 = Release|Any CPU - {BD6BAB4B-476C-4904-A27F-AFB2FD864B04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD6BAB4B-476C-4904-A27F-AFB2FD864B04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD6BAB4B-476C-4904-A27F-AFB2FD864B04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD6BAB4B-476C-4904-A27F-AFB2FD864B04}.Release|Any CPU.Build.0 = Release|Any CPU - {1915189D-AD7D-4244-B064-F2EA072DAE72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1915189D-AD7D-4244-B064-F2EA072DAE72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1915189D-AD7D-4244-B064-F2EA072DAE72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1915189D-AD7D-4244-B064-F2EA072DAE72}.Release|Any CPU.Build.0 = Release|Any CPU - {5ED6DD3E-1A26-4E93-B536-CBBCA4BA20B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5ED6DD3E-1A26-4E93-B536-CBBCA4BA20B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5ED6DD3E-1A26-4E93-B536-CBBCA4BA20B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5ED6DD3E-1A26-4E93-B536-CBBCA4BA20B7}.Release|Any CPU.Build.0 = Release|Any CPU - {A6ACA9FB-BFAA-4905-8A62-411B6A30C825}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6ACA9FB-BFAA-4905-8A62-411B6A30C825}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6ACA9FB-BFAA-4905-8A62-411B6A30C825}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6ACA9FB-BFAA-4905-8A62-411B6A30C825}.Release|Any CPU.Build.0 = Release|Any CPU - {59AFE593-45BA-4164-90D5-2CF3BD524E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59AFE593-45BA-4164-90D5-2CF3BD524E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59AFE593-45BA-4164-90D5-2CF3BD524E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59AFE593-45BA-4164-90D5-2CF3BD524E2A}.Release|Any CPU.Build.0 = Release|Any CPU - {2EB7E75A-1DBC-4A30-BAC7-AD43BC176E0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2EB7E75A-1DBC-4A30-BAC7-AD43BC176E0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2EB7E75A-1DBC-4A30-BAC7-AD43BC176E0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2EB7E75A-1DBC-4A30-BAC7-AD43BC176E0D}.Release|Any CPU.Build.0 = Release|Any CPU - {A1D681C1-864D-4746-A787-8E539B57B843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1D681C1-864D-4746-A787-8E539B57B843}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1D681C1-864D-4746-A787-8E539B57B843}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1D681C1-864D-4746-A787-8E539B57B843}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection -EndGlobal \ No newline at end of file diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.Core.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.Core.csproj deleted file mode 100644 index 4daf5ccdceb..00000000000 --- a/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.Core.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - ServiceStack.Logging.Log4Net.Core - ServiceStack.Logging.Log4Net - ServiceStack.Logging.Log4Net - netstandard2.0;net6.0;net8.0 - ServiceStack.Logging.Log4Net .NET Standard 2.0 - - Provides log4net logging integration for other ServiceStack projects - - servicestack;log;logging;log4net - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.csproj index b41872eba72..26446592d64 100644 --- a/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.csproj +++ b/ServiceStack.Logging/src/ServiceStack.Logging.Log4Net/ServiceStack.Logging.Log4Net.csproj @@ -2,7 +2,7 @@ ServiceStack.Logging.Log4Net ServiceStack.Logging.Log4Net - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.Logging.Log4Net Provides log4net logging integration for other ServiceStack projects @@ -21,10 +21,14 @@ - + - - + + + + + + \ No newline at end of file diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.Core.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.Core.csproj deleted file mode 100644 index 47a95b6a0b6..00000000000 --- a/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.Core.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - ServiceStack.Logging.NLog.Core - ServiceStack.Logging.NLog - ServiceStack.Logging.NLog - netstandard2.0;net6.0;net8.0 - ServiceStack.Logging.NLog .NET Standard 2.0 - - Provides NLog logging integration for other ServiceStack projects - - servicestack;log;logging;nlog - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.csproj index e825f3dc4b2..45837da72a4 100644 --- a/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.csproj +++ b/ServiceStack.Logging/src/ServiceStack.Logging.NLog/ServiceStack.Logging.NLog.csproj @@ -3,7 +3,7 @@ ServiceStack.Logging.NLog ServiceStack.Logging.NLog - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.Logging.NLog Provides NLog logging integration for other ServiceStack projects @@ -12,7 +12,7 @@ - + diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.Serilog/ServiceStack.Logging.Serilog.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.Serilog/ServiceStack.Logging.Serilog.csproj index eea6369298e..989f7998270 100644 --- a/ServiceStack.Logging/src/ServiceStack.Logging.Serilog/ServiceStack.Logging.Serilog.csproj +++ b/ServiceStack.Logging/src/ServiceStack.Logging.Serilog/ServiceStack.Logging.Serilog.csproj @@ -3,7 +3,7 @@ ServiceStack.Logging.Serilog ServiceStack.Logging.Serilog - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.Logging.Serilog Provides Serilog logging integration for other ServiceStack projects diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.Slack/ServiceStack.Logging.Slack.csproj b/ServiceStack.Logging/src/ServiceStack.Logging.Slack/ServiceStack.Logging.Slack.csproj index 2763a35d189..763ec60ae6e 100644 --- a/ServiceStack.Logging/src/ServiceStack.Logging.Slack/ServiceStack.Logging.Slack.csproj +++ b/ServiceStack.Logging/src/ServiceStack.Logging.Slack/ServiceStack.Logging.Slack.csproj @@ -3,7 +3,7 @@ ServiceStack.Logging.Slack ServiceStack.Logging.Slack - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0 ServiceStack.Logging.Slack Provides Slack logging integration for other ServiceStack projects diff --git a/ServiceStack.Logging/src/ServiceStack.Logging.sln b/ServiceStack.Logging/src/ServiceStack.Logging.sln index e2e5028ff94..5abe185e035 100644 --- a/ServiceStack.Logging/src/ServiceStack.Logging.sln +++ b/ServiceStack.Logging/src/ServiceStack.Logging.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Logging.Elmah" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CA7CF53F-9C02-4A5B-B9AF-390B1F7B095C}" ProjectSection(SolutionItems) = preProject - ServiceStack.Logging.Log4Net\ServiceStack.Logging.Log4Net.Core.csproj = ServiceStack.Logging.Log4Net\ServiceStack.Logging.Log4Net.Core.csproj - ServiceStack.Logging.NLog\ServiceStack.Logging.NLog.Core.csproj = ServiceStack.Logging.NLog\ServiceStack.Logging.NLog.Core.csproj Directory.Build.props = Directory.Build.props ..\tests\Directory.Build.props = ..\tests\Directory.Build.props ..\build\build.proj = ..\build\build.proj diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.Core.csproj deleted file mode 100644 index fee1e5482d6..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.Core.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - ServiceStack.OrmLite.Firebird.Core - ServiceStack.OrmLite.Firebird - ServiceStack.OrmLite.Firebird - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.Firebird .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.Firebird - - Firebird;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.csproj index 4d0c96bf47f..d80e96814f7 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Firebird/ServiceStack.OrmLite.Firebird.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite.Firebird ServiceStack.OrmLite.Firebird - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite.Firebird - Fast, code-first, config-free POCO ORM Light, simple and fast convention-based code-first POCO ORM for Firebird. diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.Core.csproj deleted file mode 100644 index e22ed39fafd..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.Core.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - ServiceStack.OrmLite.MySql.Core - ServiceStack.OrmLite.MySql - ServiceStack.OrmLite.MySql - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.MySql .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.MySql - - MySQL;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.csproj index 76c5f519f52..5a814e28990 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySql/ServiceStack.OrmLite.MySql.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite.MySql ServiceStack.OrmLite.MySql - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite.MySql - Fast, code-first, config-free POCO ORM Light, simple and fast convention-based code-first POCO ORM for MySQL. diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.Core.csproj deleted file mode 100644 index f3725f54811..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.Core.csproj +++ /dev/null @@ -1,49 +0,0 @@ - - - ServiceStack.OrmLite.MySqlConnector.Core - ServiceStack.OrmLite.MySqlConnector - ServiceStack.OrmLite.MySqlConnector - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.MySql - ServiceStack.OrmLite.MySqlConnector .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.MySqlConnector - - MySqlConnector;MySQL;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.csproj index a83f17b5eca..4b7f6bbe537 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.MySqlConnector/ServiceStack.OrmLite.MySqlConnector.csproj @@ -1,6 +1,6 @@  - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.OrmLite.MySqlConnector ServiceStack.OrmLite.MySqlConnector ServiceStack.OrmLite.MySql diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.Core.csproj deleted file mode 100644 index 0810a17aecd..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.Core.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - net6.0;net8.0 - ServiceStack.OrmLite.Oracle.Core - ServiceStack.OrmLite.Oracle - ServiceStack.OrmLite.Oracle - OrmLite.Oracle - Fast, code-first, config-free POCO ORM - - Light, simple and fast convention-based code-first POCO ORM for Oracle RDBMS (Unofficial). - Support for Creating and Dropping Table Schemas from POCOs, Complex Property types transparently - stored in schemaless text blobs in Oracle. - - Oracle;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - - - - - - - - - - - - Component - - - Component - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.csproj index 8095ae86de6..5976a736be2 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Oracle/ServiceStack.OrmLite.Oracle.csproj @@ -1,7 +1,7 @@  - net472;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.OrmLite.Oracle ServiceStack.OrmLite.Oracle OrmLite.Oracle - Fast, code-first, config-free POCO ORM @@ -18,7 +18,7 @@ - + diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.Core.csproj deleted file mode 100644 index 300e974623b..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.Core.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - ServiceStack.OrmLite.PostgreSQL.Core - ServiceStack.OrmLite.PostgreSQL - ServiceStack.OrmLite.PostgreSQL - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.PostgreSQL .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.PostgreSQL - - PostgreSQL;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj index 2704867db45..12cc4210a9c 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite.PostgreSQL ServiceStack.OrmLite.PostgreSQL - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite.PostgreSQL - Fast, code-first, config-free POCO ORM Light, simple and fast convention-based code-first POCO ORM for PostgreSQL. @@ -29,5 +29,8 @@ + + + diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Converters/ServiceStack.OrmLite.SqlServer.Converters.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Converters/ServiceStack.OrmLite.SqlServer.Converters.csproj index 7cb8642caeb..2c7bfa6783f 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Converters/ServiceStack.OrmLite.SqlServer.Converters.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Converters/ServiceStack.OrmLite.SqlServer.Converters.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite.SqlServer.Converters ServiceStack.OrmLite.SqlServer.Converters - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite.SqlServer.Converters - Converters for SQL Server Types Add OrmLite Converters for SQL Servers SqlGeography and SqlGeometry Types diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.Core.csproj deleted file mode 100644 index 57b453ee854..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.Core.csproj +++ /dev/null @@ -1,105 +0,0 @@ - - - - ServiceStack.OrmLite.SqlServer.Data.Core - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.SqlServer.Data - ServiceStack.OrmLite.SqlServer - ServiceStack.OrmLite.SqlServer.Data .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.SqlServer.Data - - SQLServer;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC;MSDATA - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - Converters\SqlServerBoolConverter.cs - - - Converters\SqlServerByteArrayConverter.cs - - - Converters\SqlServerDateTime2Converter.cs - - - Converters\SqlServerDateTimeConverter.cs - - - Converters\SqlServerFloatConverters.cs - - - Converters\SqlServerGuidConverter.cs - - - Converters\SqlServerIntegerConverters.cs - - - Converters\SqlServerJsonStringConverters.cs - - - Converters\SqlServerSpecialConverters.cs - - - Converters\SqlServerStringConverters.cs - - - Converters\SqlServerTimeConverter.cs - - - SqlServer2008OrmLiteDialectProvider.cs - - - SqlServer2012OrmLiteDialectProvider.cs - - - SqlServer2014OrmLiteDialectProvider.cs - - - SqlServer2016Expression.cs - - - SqlServerDialect.cs - - - SqlServerExpression.cs - - - SqlServerOrmLiteDialectProvider.cs - - - SqlServerOrmLiteDialectProviderVersions.cs - - - SqlServerTableHint.cs - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj index cca60877e97..371c974a390 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj @@ -2,7 +2,7 @@ ServiceStack.OrmLite.SqlServer.Data - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.OrmLite.SqlServer.Data ServiceStack.OrmLite.SqlServer OrmLite.SqlServer - Fast, code-first, config-free POCO ORM @@ -29,8 +29,8 @@ - - + + diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.Core.csproj deleted file mode 100644 index 64b1531e980..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.Core.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - ServiceStack.OrmLite.SqlServer.Core - ServiceStack.OrmLite.SqlServer - ServiceStack.OrmLite.SqlServer - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.SqlServer .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.SqlServer - - SQLServer;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.csproj index aa602360494..844d0030c01 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer/ServiceStack.OrmLite.SqlServer.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite.SqlServer ServiceStack.OrmLite.SqlServer - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite.SqlServer - Fast, code-first, config-free POCO ORM Light, simple and fast convention-based code-first POCO ORM for Sql Server. @@ -20,11 +20,7 @@ - - - - - + diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj index 826250f408c..febc4a27c23 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj @@ -1,7 +1,7 @@  - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.OrmLite.Sqlite ServiceStack.OrmLite.Sqlite.Data ServiceStack.OrmLite.Sqlite @@ -20,7 +20,10 @@ $(DefineConstants);NETCORE;NET6_0 - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.Core.csproj deleted file mode 100644 index 100b1d9389c..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.Core.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - ServiceStack.OrmLite.Sqlite.Core - ServiceStack.OrmLite.Sqlite - ServiceStack.OrmLite.Sqlite - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite.Sqlite .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite.Sqlite - - SQLite;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj index 0ba84d113a6..65ed6eb8717 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj @@ -1,7 +1,7 @@  - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.OrmLite.Sqlite ServiceStack.OrmLite.Sqlite OrmLite.Sqlite - Fast, code-first, config-free POCO ORM @@ -19,10 +19,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln index 358418e8cd4..421e683529a 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln @@ -8,19 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{6CEB3EDE ..\build\build.bat = ..\build\build.bat ..\build\build.proj = ..\build\build.proj Directory.Build.props = Directory.Build.props - ServiceStack.OrmLite\ServiceStack.OrmLite.Core.csproj = ServiceStack.OrmLite\ServiceStack.OrmLite.Core.csproj - ServiceStack.OrmLite.Firebird\ServiceStack.OrmLite.Firebird.Core.csproj = ServiceStack.OrmLite.Firebird\ServiceStack.OrmLite.Firebird.Core.csproj - ServiceStack.OrmLite.MySql\ServiceStack.OrmLite.MySql.Core.csproj = ServiceStack.OrmLite.MySql\ServiceStack.OrmLite.MySql.Core.csproj - ServiceStack.OrmLite.MySqlConnector\ServiceStack.OrmLite.MySqlConnector.Core.csproj = ServiceStack.OrmLite.MySqlConnector\ServiceStack.OrmLite.MySqlConnector.Core.csproj - ServiceStack.OrmLite.PostgreSQL\ServiceStack.OrmLite.PostgreSQL.Core.csproj = ServiceStack.OrmLite.PostgreSQL\ServiceStack.OrmLite.PostgreSQL.Core.csproj - ServiceStack.OrmLite.Sqlite\ServiceStack.OrmLite.Sqlite.Core.csproj = ServiceStack.OrmLite.Sqlite\ServiceStack.OrmLite.Sqlite.Core.csproj - ServiceStack.OrmLite.SqlServer\ServiceStack.OrmLite.SqlServer.Core.csproj = ServiceStack.OrmLite.SqlServer\ServiceStack.OrmLite.SqlServer.Core.csproj - ServiceStack.OrmLite.Sqlite.Data\ServiceStack.OrmLite.Sqlite.Data.csproj = ServiceStack.OrmLite.Sqlite.Data\ServiceStack.OrmLite.Sqlite.Data.csproj - ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.csproj = ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.csproj - ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.Core.csproj = ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.Core.csproj ..\tests\Directory.Build.props = ..\tests\Directory.Build.props ServiceStack.OrmLite.Oracle\ServiceStack.OrmLite.Oracle.csproj = ServiceStack.OrmLite.Oracle\ServiceStack.OrmLite.Oracle.csproj - ServiceStack.OrmLite.Oracle\ServiceStack.OrmLite.Oracle.Core.csproj = ServiceStack.OrmLite.Oracle\ServiceStack.OrmLite.Oracle.Core.csproj EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.OrmLite.Sqlite", "ServiceStack.OrmLite.Sqlite\ServiceStack.OrmLite.Sqlite.csproj", "{CF68A37D-D071-469D-AE04-68594CB95382}" diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.Core.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.Core.csproj deleted file mode 100644 index 73c99ddfd92..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.Core.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - ServiceStack.OrmLite.Core - ServiceStack.OrmLite - ServiceStack.OrmLite - netstandard2.0;net6.0;net8.0 - ServiceStack.OrmLite .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.OrmLite - - OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj index 3ad04d026a1..5f018be90ca 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj @@ -3,7 +3,7 @@ ServiceStack.OrmLite ServiceStack.OrmLite - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 OrmLite - Fast, code-first, config-free POCO ORM Common library for the Light, simple and fast convention-based code-first POCO, OrmLite. @@ -20,10 +20,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj index 48880ec020c..34423542a3a 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj @@ -26,4 +26,7 @@ + + + \ No newline at end of file diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Redis/src/ServiceStack.Redis.sln b/ServiceStack.Redis/src/ServiceStack.Redis.sln index 1d3fd36e360..cfbfff5c889 100644 --- a/ServiceStack.Redis/src/ServiceStack.Redis.sln +++ b/ServiceStack.Redis/src/ServiceStack.Redis.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{38F69F8F ..\build\build.proj = ..\build\build.proj Directory.Build.props = Directory.Build.props ..\tests\Directory.Build.props = ..\tests\Directory.Build.props - ServiceStack.Redis\ServiceStack.Redis.Core.csproj = ServiceStack.Redis\ServiceStack.Redis.Core.csproj EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Redis", "ServiceStack.Redis\ServiceStack.Redis.csproj", "{AF99F19B-4C04-4F58-81EF-B092F1FCC540}" diff --git a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj b/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj deleted file mode 100644 index 4377a962e80..00000000000 --- a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - ServiceStack.Redis.Core - ServiceStack.Redis - ServiceStack.Redis - netstandard2.0;net6.0;net8.0 - ServiceStack.Redis .NET Standard 2.0 - - .NET Standard 2.0 version of ServiceStack.Redis - - Redis;NoSQL;Client;Distributed;Cache;PubSub;Messaging;Transactions - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj b/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj index c7bd7240ec4..8fa98f15846 100644 --- a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj +++ b/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj @@ -2,7 +2,7 @@ ServiceStack.Redis ServiceStack.Redis - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 C# Redis client for the Redis NoSQL DB C# Redis Client for the worlds fastest distributed NoSQL datastore. @@ -22,7 +22,10 @@ $(DefineConstants);NETCORE;NET6_0 - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -34,17 +37,5 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.Redis/src/ServiceStack.Redis/Support/OrderedDictionary.cs b/ServiceStack.Redis/src/ServiceStack.Redis/Support/OrderedDictionary.cs index 1b37b62d244..408e9ae24a4 100644 --- a/ServiceStack.Redis/src/ServiceStack.Redis/Support/OrderedDictionary.cs +++ b/ServiceStack.Redis/src/ServiceStack.Redis/Support/OrderedDictionary.cs @@ -1,3 +1,4 @@ +#if !NET10_0_OR_GREATER using System; using System.Collections; using System.Collections.Generic; @@ -634,4 +635,5 @@ bool ICollection>.Remove(KeyValuePair i { return Remove(item.Key); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Redis/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj b/ServiceStack.Redis/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj index 5cdba11f7e1..13137cf67fc 100644 --- a/ServiceStack.Redis/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj +++ b/ServiceStack.Redis/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj @@ -2,7 +2,8 @@ Exe - net8.0;net472 + net10.0;net472 + net10.0 8 diff --git a/ServiceStack.Redis/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj b/ServiceStack.Redis/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj index 18d48ec7352..0ba3c6c3657 100644 --- a/ServiceStack.Redis/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj +++ b/ServiceStack.Redis/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj @@ -1,7 +1,8 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Redis.Tests.Sentinel Library @@ -41,11 +42,8 @@ - + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER - - - diff --git a/ServiceStack.Redis/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj b/ServiceStack.Redis/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj index 8dc589c5f4e..47c95a1b5a9 100644 --- a/ServiceStack.Redis/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj +++ b/ServiceStack.Redis/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj @@ -7,7 +7,8 @@ - netcoreapp2.1 is .NET Core with async, but without some features like memory/stream APIs - net5.0 is .NET Core with all the bells and whistles --> - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Redis.Tests Library @@ -34,9 +35,6 @@ $(DefineConstants);NETFX - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER - @@ -47,6 +45,4 @@ - - \ No newline at end of file diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Stripe/src/ServiceStack.Stripe.sln b/ServiceStack.Stripe/src/ServiceStack.Stripe.sln index ace58f0bed2..540b304b2b9 100644 --- a/ServiceStack.Stripe/src/ServiceStack.Stripe.sln +++ b/ServiceStack.Stripe/src/ServiceStack.Stripe.sln @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{8336FEA3 ..\build\build.proj = ..\build\build.proj Directory.Build.props = Directory.Build.props ..\tests\Directory.Build.props = ..\tests\Directory.Build.props - ServiceStack.Stripe\ServiceStack.Stripe.Core.csproj = ServiceStack.Stripe\ServiceStack.Stripe.Core.csproj EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stripe", "ServiceStack.Stripe\ServiceStack.Stripe.csproj", "{34E0CAC2-14C6-492A-AA02-DAFBF873F6FA}" diff --git a/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.Core.csproj b/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.Core.csproj deleted file mode 100644 index 0d530c8a918..00000000000 --- a/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.Core.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - ServiceStack.Stripe.Core - Stripe - Stripe - netstandard2.0;net6.0;net8.0 - ServiceStack.Stripe .NET Standard 2.0 - - A typed message-based .NET client gateway for accessing Stripe's REST API. - Used by servicestack.net to process merchant payments and recurring subscriptions. - - Stripe;Typed;REST;API;JSON;Gateway;Message - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.csproj b/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.csproj index 43db4be770a..47fd9a43c56 100644 --- a/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.csproj +++ b/ServiceStack.Stripe/src/ServiceStack.Stripe/ServiceStack.Stripe.csproj @@ -3,7 +3,7 @@ ServiceStack.Stripe Stripe - net472;net6.0;netstandard2.0;net8.0 + net472;net6.0;net8.0;net10.0 Typed message-based .NET Client Gateway for Stripe's REST API A typed message-based .NET client gateway for accessing Stripe's REST API. @@ -18,10 +18,13 @@ $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -35,14 +38,4 @@ - - - - - - - - - - diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj b/ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj index 9a739201941..4245c3fc8ae 100644 --- a/ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj +++ b/ServiceStack.Stripe/tests/ServiceStack.Stripe.Tests/ServiceStack.Stripe.Tests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable Stripe.Tests Library @@ -29,6 +30,4 @@ - - \ No newline at end of file diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Text/src/ServiceStack.Text.sln b/ServiceStack.Text/src/ServiceStack.Text.sln index 46d4d869923..60ca47a2f36 100644 --- a/ServiceStack.Text/src/ServiceStack.Text.sln +++ b/ServiceStack.Text/src/ServiceStack.Text.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{F7FB50ED ..\build\build.proj = ..\build\build.proj ..\build\build.tasks = ..\build\build.tasks Directory.Build.props = Directory.Build.props - ServiceStack.Text\ServiceStack.Text.Core.csproj = ServiceStack.Text\ServiceStack.Text.Core.csproj ..\tests\Directory.Build.props = ..\tests\Directory.Build.props EndProjectSection EndProject diff --git a/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs b/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs index 005244241b2..94cf685eb62 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs @@ -1,4 +1,4 @@ -#if (NETCORE || NET6_0_OR_GREATER) && !NETSTANDARD2_0 +#if (NET6_0_OR_GREATER) && !NETSTANDARD2_0 using System; using ServiceStack.Text; diff --git a/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.Core.csproj b/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.Core.csproj deleted file mode 100644 index 6d94820657a..00000000000 --- a/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.Core.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - ServiceStack.Text.Core - ServiceStack.Text - ServiceStack.Text - netstandard2.0;net6.0;net8.0 - ServiceStack.Text .NET Standard 2.0 - - .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. - Contains ServiceStack's high-performance text-processing powers, for more info see: - https://github.com/ServiceStack/ServiceStack.Text - - JSON;Text;Serializer;CSV;JSV;HTTP;Auto Mapping;Dump;Reflection;JS;Utils;Fast - 1591 - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.csproj b/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.csproj index 8344ccf526a..eec530817b8 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.csproj +++ b/ServiceStack.Text/src/ServiceStack.Text/ServiceStack.Text.csproj @@ -2,7 +2,7 @@ ServiceStack.Text ServiceStack.Text - net472;netstandard2.0;net6.0;net8.0 + net472;netstandard2.0;net6.0;net8.0;net10.0 .NET's fastest JSON Serializer by ServiceStack .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. @@ -19,10 +19,13 @@ $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -43,4 +46,7 @@ + + + diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack.Text/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj b/ServiceStack.Text/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj index 5dee4bdb2a1..caf9dbe3dfb 100644 --- a/ServiceStack.Text/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj +++ b/ServiceStack.Text/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj @@ -1,7 +1,7 @@  Exe - net8.0 + net10.0 @@ -10,7 +10,7 @@ - + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER \ No newline at end of file diff --git a/ServiceStack/build/run-webhost-tests.sh b/ServiceStack/build/run-webhost-tests.sh index 4d19669dd87..714dfb9cf3d 100644 --- a/ServiceStack/build/run-webhost-tests.sh +++ b/ServiceStack/build/run-webhost-tests.sh @@ -2,5 +2,5 @@ docker-compose -f ./webhost-tests-environment.yml up -d export PGSQL_CONNECTION="Server=localhost;Port=48303;User Id=postgres;Password=test;Database=test;Pooling=true;MinPoolSize=0;MaxPoolSize=200" export MSSQL_CONNECTION="Server=localhost,48501;Database=master;User Id=sa;Password=Test!tesT;MultipleActiveResultSets=True;" -dotnet clean --framework net8.0 ../tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj -dotnet test --framework net8.0 ../tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj +dotnet clean --framework net10.0 ../tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj +dotnet test --framework net10.0 ../tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack/src/ServiceStack.AI.Chat/ServiceStack.AI.Chat.csproj b/ServiceStack/src/ServiceStack.AI.Chat/ServiceStack.AI.Chat.csproj index 3b798982fdb..07d3d08e0db 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/ServiceStack.AI.Chat.csproj +++ b/ServiceStack/src/ServiceStack.AI.Chat/ServiceStack.AI.Chat.csproj @@ -4,7 +4,7 @@ ServiceStack.AI.Chat ServiceStack.AI.Chat ServiceStack.AI - net8.0 + net8.0;net10.0 enable enable ServiceStack functionality for OpenAI, Client, Server and UI @@ -13,9 +13,6 @@ AI;GPT;OpenAI - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - diff --git a/ServiceStack/src/ServiceStack.AI/ServiceStack.AI.csproj b/ServiceStack/src/ServiceStack.AI/ServiceStack.AI.csproj index ef02c92c128..09df0f1ef3c 100644 --- a/ServiceStack/src/ServiceStack.AI/ServiceStack.AI.csproj +++ b/ServiceStack/src/ServiceStack.AI/ServiceStack.AI.csproj @@ -3,7 +3,7 @@ ServiceStack.AI ServiceStack.AI - net472;netstandard2.0;net6.0;net8.0 + net8.0;net10.0 enable enable ServiceStack functionality for AI, Microsoft.SemanticKernel, OpenAI Whisper and TypeChat @@ -12,18 +12,6 @@ AI;GPT;SemanticKernel;node;TypeChat;OpenAI;Whisper - - $(DefineConstants);NETFX;NET472 - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/OpenApiService.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/OpenApiService.cs index 81ecd80d6d3..0e1cb73a77e 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/OpenApiService.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/OpenApiService.cs @@ -10,9 +10,12 @@ using ServiceStack.NativeTypes; using ServiceStack.Text; using ServiceStack.Web; -using ServiceStack.Api.OpenApi.Support; using ServiceStack.Api.OpenApi.Specification; +#if !NET10_0_OR_GREATER +using ServiceStack.Api.OpenApi.Support; +#endif + namespace ServiceStack.Api.OpenApi { [DataContract] diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.Core.csproj b/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.Core.csproj deleted file mode 100644 index 67cbcdee60c..00000000000 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.Core.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - ServiceStack.Api.OpenApi.Core - ServiceStack.Api.OpenApi - ServiceStack.Api.OpenApi - netstandard2.0;net6.0;net8.0 - ServiceStack.Api.OpenApi .NET Standard 2.0 - - Implements v2.0 of the Open API Specification (https://www.openapis.org). - Open API is a specification and complete framework implementation for describing, producing, consuming, and visualizing RESTful web services. - - OpenAPI;metadata;Swagger;API;REST;Metadata;Docs;ServiceStack - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.csproj b/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.csproj index bb52994c968..ed48246f338 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.csproj +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/ServiceStack.Api.OpenApi.csproj @@ -3,7 +3,7 @@ ServiceStack.Api.OpenApi ServiceStack.Api.OpenApi - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack support for Open API v2.0 and integrated Swagger UI Implements v2.0 of the Open API Specification (https://www.openapis.org). @@ -11,6 +11,15 @@ OpenAPI;metadata;Swagger;API;REST;Metadata;Docs;ServiceStack + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + @@ -33,13 +42,4 @@ - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiDeclaration.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiDeclaration.cs index 8083a9fba5d..2441d966662 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiDeclaration.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiDeclaration.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using System.Runtime.Serialization; -using ServiceStack.Api.OpenApi.Support; using ServiceStack.DataAnnotations; +#if !NET10_0_OR_GREATER +using ServiceStack.Api.OpenApi.Support; +#endif + namespace ServiceStack.Api.OpenApi.Specification { [DataContract] diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiOperation.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiOperation.cs index 0dcbdc0f6f8..ef696dae41b 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiOperation.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiOperation.cs @@ -1,7 +1,10 @@ -using ServiceStack.Api.OpenApi.Support; -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; +#if !NET10_0_OR_GREATER +using ServiceStack.Api.OpenApi.Support; +#endif + namespace ServiceStack.Api.OpenApi.Specification { [DataContract] diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiSchema.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiSchema.cs index 3ffa870e02f..7708321c2e0 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiSchema.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/Specification/OpenApiSchema.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; -using ServiceStack.Api.OpenApi.Support; using System.Runtime.Serialization; +#if !NET10_0_OR_GREATER +using ServiceStack.Api.OpenApi.Support; +#endif + namespace ServiceStack.Api.OpenApi.Specification { [DataContract] diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/Support/IOrderedDictionary.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/Support/IOrderedDictionary.cs index 23703a0073e..d45e36b4994 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/Support/IOrderedDictionary.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/Support/IOrderedDictionary.cs @@ -1,4 +1,5 @@ -using System; +#if !NET10_0_OR_GREATER +using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -55,4 +56,5 @@ public interface IOrderedDictionary set; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.Api.OpenApi/Support/OrderedDictionary.cs b/ServiceStack/src/ServiceStack.Api.OpenApi/Support/OrderedDictionary.cs index 1f9626e5380..d2be542f1e0 100644 --- a/ServiceStack/src/ServiceStack.Api.OpenApi/Support/OrderedDictionary.cs +++ b/ServiceStack/src/ServiceStack.Api.OpenApi/Support/OrderedDictionary.cs @@ -1,4 +1,5 @@ -#pragma warning disable CS1734 +#if !NET10_0_OR_GREATER +#pragma warning disable CS1734 using System; using System.Collections; @@ -638,3 +639,4 @@ bool ICollection>.Remove(KeyValuePair i } } } +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs index f9c59f97845..57ccc685828 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs @@ -1,4 +1,6 @@ -using System; +#if !NET10_0_OR_GREATER + +using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -54,4 +56,6 @@ public interface IOrderedDictionary get; set; } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs index b3a494c99ee..85f04aa47af 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs @@ -1,4 +1,6 @@ -#pragma warning disable CS1734 +#if !NET10_0_OR_GREATER + +#pragma warning disable CS1734 using System; using System.Collections; @@ -636,4 +638,6 @@ bool ICollection>.Remove(KeyValuePair i { return Remove(item.Key); } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj index 5ad074f89a0..2d70164c512 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj @@ -1,17 +1,32 @@ - net8.0 + net8.0 enable enable + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + - + + + + + + + + + diff --git a/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.Core.csproj b/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.Core.csproj deleted file mode 100644 index 143a3534cbd..00000000000 --- a/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.Core.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - ServiceStack.Authentication.MongoDb.Core - ServiceStack.Authentication.MongoDb - netstandard2.0;net6.0;net8.0 - ServiceStack.Authentication.MongoDb .NET Standard 2.0 - - A MongoDB IUserAuthRepository to persist User registration and auth info in a NoSQL Mongo DB. - Implements ServiceStack's built-in AuthProvider Model: - https://docs.servicestack.net/authentication-and-authorization - inc. Twitter, Facebook, Basic, Digest, Credentials Auth modes out-of-the-box. - - ServiceStack;Authentication;Authorization;MongoDB;NoSQL;POCO;Code-First - - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.csproj b/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.csproj index c749aa58468..8318285c080 100644 --- a/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.csproj +++ b/ServiceStack/src/ServiceStack.Authentication.MongoDb/ServiceStack.Authentication.MongoDb.csproj @@ -3,7 +3,7 @@ ServiceStack.Authentication.MongoDb ServiceStack.Authentication.MongoDb - net472;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 A MongoDB IUserAuthRepository to persist User registration and auth info in a NoSQL Mongo DB A MongoDB IUserAuthRepository to persist User registration and auth info in a NoSQL Mongo DB. diff --git a/ServiceStack/src/ServiceStack.Authentication.RavenDb/ServiceStack.Authentication.RavenDb.csproj b/ServiceStack/src/ServiceStack.Authentication.RavenDb/ServiceStack.Authentication.RavenDb.csproj index 3ffa61a5cd0..67f4aa9bfd8 100644 --- a/ServiceStack/src/ServiceStack.Authentication.RavenDb/ServiceStack.Authentication.RavenDb.csproj +++ b/ServiceStack/src/ServiceStack.Authentication.RavenDb/ServiceStack.Authentication.RavenDb.csproj @@ -3,7 +3,7 @@ ServiceStack.Authentication.RavenDb ServiceStack.Authentication.RavenDb - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 A RavenDB IUserAuthRepository to persist User registration and auth info in a NoSQL Raven DB A RavenDB IUserAuthRepository to persist User registration and auth info in a NoSQL Raven DB. diff --git a/ServiceStack/src/ServiceStack.Caching.Memcached/ServiceStack.Caching.Memcached.csproj b/ServiceStack/src/ServiceStack.Caching.Memcached/ServiceStack.Caching.Memcached.csproj index 7037da4ba28..2ea9328f941 100644 --- a/ServiceStack/src/ServiceStack.Caching.Memcached/ServiceStack.Caching.Memcached.csproj +++ b/ServiceStack/src/ServiceStack.Caching.Memcached/ServiceStack.Caching.Memcached.csproj @@ -3,7 +3,7 @@ ServiceStack.Caching.Memcached ServiceStack.Caching.Memcached - net472 + net472 Memcached ServiceStack Caching Provider An Memcached cache client implementing ServiceStack's ICacheClient: diff --git a/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.Core.csproj b/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.Core.csproj deleted file mode 100644 index a0bdb6e2ec1..00000000000 --- a/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.Core.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - ServiceStack.Client.Core - ServiceStack.Client - ServiceStack.Client - netstandard2.0;net6.0;net8.0 - ServiceStack.Client .NET Standard 2.0 - - JSON, XML, CSV, JSV, SOAP and MQ Generic Service Clients. - - ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.csproj b/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.csproj index f98a4ae2bb8..d178b8eddfe 100644 --- a/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.csproj +++ b/ServiceStack/src/ServiceStack.Client/ServiceStack.Client.csproj @@ -4,7 +4,7 @@ ServiceStack.Client ServiceStack.Client ServiceStack - net472;netstandard2.0;net6.0;net8.0 + net472;netstandard2.0;net6.0;net8.0;net10.0 .NET and .NET Core fast, end-to-end typed, code-gen free Service Clients JSON, XML, CSV, JSV, SOAP and MQ Generic Service Clients. ServiceStack;Common;Framework;Clients;ServiceClients;Gateway @@ -16,10 +16,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -47,5 +50,9 @@ + + + + diff --git a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.Core.csproj b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.Core.csproj deleted file mode 100644 index ccb27c1832a..00000000000 --- a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.Core.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - ServiceStack.Common.Core - ServiceStack.Common - ServiceStack.Common - netstandard2.0;net6.0;net8.0 - ServiceStack - ServiceStack.Common .NET Standard 2.0 - - #Script, Virtual File System, SimpleContainer and Common library for ServiceStack projects. - - ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj index 80c808ca120..48c9bf87332 100644 --- a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj +++ b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj @@ -12,10 +12,10 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack.Desktop/ServiceStack.Desktop.csproj b/ServiceStack/src/ServiceStack.Desktop/ServiceStack.Desktop.csproj index 3f5cd87a02a..38cfc67286c 100644 --- a/ServiceStack/src/ServiceStack.Desktop/ServiceStack.Desktop.csproj +++ b/ServiceStack/src/ServiceStack.Desktop/ServiceStack.Desktop.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net6.0;net8.0 + net6.0;net8.0;net10.0 ServiceStack.Desktop Desktop Assets, Vue and React Framework libraries, Bootstrap, Material Design SVGs, Font Awesome and Win32 APIs for ServiceStack CEF .NET Core Desktop Apps diff --git a/ServiceStack/src/ServiceStack.Extensions/ServiceStack.Extensions.csproj b/ServiceStack/src/ServiceStack.Extensions/ServiceStack.Extensions.csproj index 5bf041229f6..05190f6972d 100644 --- a/ServiceStack/src/ServiceStack.Extensions/ServiceStack.Extensions.csproj +++ b/ServiceStack/src/ServiceStack.Extensions/ServiceStack.Extensions.csproj @@ -9,14 +9,14 @@ enable ServiceStack extensions for .NET Core Apps, including GrpcFeature ServiceStack;gRPC;server;GrpcFeature - net6.0;net8.0 + net6.0;net8.0;net10.0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER @@ -29,6 +29,11 @@ + + + + + diff --git a/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj b/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj index c8855cab760..5aa317e9197 100644 --- a/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj +++ b/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj @@ -1,7 +1,7 @@ - net472;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 enable enable ServiceStack integration for Google Cloud Platform (GCP) @@ -15,10 +15,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -38,12 +41,6 @@ - - - - - - diff --git a/ServiceStack/src/ServiceStack.GrpcClient/ServiceStack.GrpcClient.csproj b/ServiceStack/src/ServiceStack.GrpcClient/ServiceStack.GrpcClient.csproj index 9c7353ac6a1..4d1e8609edb 100644 --- a/ServiceStack/src/ServiceStack.GrpcClient/ServiceStack.GrpcClient.csproj +++ b/ServiceStack/src/ServiceStack.GrpcClient/ServiceStack.GrpcClient.csproj @@ -3,7 +3,7 @@ ServiceStack.GrpcClient ServiceStack.GrpcClient - netstandard2.1;net6.0;net8.0 + net6.0;net8.0;net10.0 ServiceStack default ServiceStack.GrpcClient diff --git a/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.Core.csproj b/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.Core.csproj deleted file mode 100644 index 2068bb8488b..00000000000 --- a/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.Core.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - ServiceStack.HttpClient.Core - ServiceStack.HttpClient - ServiceStack.HttpClient - netstandard2.0;net6.0;net8.0 - ServiceStack.HttpClient .NET Standard 2.0 - - Typed .NET Core and .NET Framework ServiceClients based on .NET's HttpClient - - ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.csproj b/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.csproj index d4cc632e397..01fde907cd7 100644 --- a/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.csproj +++ b/ServiceStack/src/ServiceStack.HttpClient/ServiceStack.HttpClient.csproj @@ -3,13 +3,13 @@ ServiceStack.HttpClient ServiceStack.HttpClient - net472;netstandard2.0;net6.0;net8.0 + net472;netstandard2.0;net6.0;net8.0;net10.0 Common libraries for ServiceStack projects Typed .NET Core and .NET Framework ServiceClients based on .NET's HttpClient ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + $(DefineConstants);NET6_0_OR_GREATER @@ -31,7 +31,4 @@ - - - diff --git a/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.Core.csproj b/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.Core.csproj deleted file mode 100644 index 3e8fe27dfb0..00000000000 --- a/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.Core.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - ServiceStack.Interfaces.Core - ServiceStack.Interfaces - ServiceStack.Interfaces - netstandard2.0;net6.0;net8.0 - ServiceStack.Interfaces .NET Standard 2.0 - - Lightweight and implementation-free interfaces for DTO's, providers and adapters. - - ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj b/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj index dd77e42e496..287ed00867e 100644 --- a/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj +++ b/ServiceStack/src/ServiceStack.Interfaces/ServiceStack.Interfaces.csproj @@ -4,7 +4,7 @@ ServiceStack.Interfaces ServiceStack.Interfaces ServiceStack - net472;netstandard2.0;net6.0;net8.0 + net472;netstandard2.0;net6.0;net8.0;net10.0 Lightweight and implementation-free interfaces for ServiceStack Lightweight and implementation-free interfaces for DTO's, providers and adapters. ServiceStack;Common;Framework;Clients;ServiceClients;Gateway @@ -17,10 +17,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -35,7 +38,4 @@ - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.Jobs/ServiceStack.Jobs.csproj b/ServiceStack/src/ServiceStack.Jobs/ServiceStack.Jobs.csproj index 67af5a2992e..80bcfa71cd8 100644 --- a/ServiceStack/src/ServiceStack.Jobs/ServiceStack.Jobs.csproj +++ b/ServiceStack/src/ServiceStack.Jobs/ServiceStack.Jobs.csproj @@ -2,7 +2,7 @@ ServiceStack.Jobs ServiceStack.Jobs - net8.0 + net8.0;net10.0 ServiceStack Background Jobs, Workers and Loggers Background Job Feature and Workers diff --git a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.Core.csproj b/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.Core.csproj deleted file mode 100644 index 4ae715fab27..00000000000 --- a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.Core.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - ServiceStack.Kestrel.Core - ServiceStack.Kestrel - ServiceStack.Kestrel - netstandard2.0;net6.0;net8.0 - ServiceStack.Kestrel .NET Standard 2.0 - - Provides AppSelfHostBase implementation for .NET Core's Kestrel Self Host HTTP Server - - To get started see: https://servicestack.net/getting-started - - ServiceStack;SelfHost;Host;Kestrel;HTTP;Server - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj b/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj index 3c66285303f..9b6a9b71bec 100644 --- a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj +++ b/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj @@ -3,7 +3,7 @@ ServiceStack.Kestrel ServiceStack.Kestrel - netstandard2.0;net6.0;net8.0 + net6.0;net8.0;net10.0 ServiceStack Self Host for .NET Core's Kestrel HTTP Server Provides AppSelfHostBase implementation for .NET Core's Kestrel Self Host HTTP Server @@ -16,34 +16,26 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - - - - - - - - - - - - - - - - - - + + + + + + - - + + diff --git a/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.Core.csproj b/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.Core.csproj deleted file mode 100644 index ff3a5118a13..00000000000 --- a/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.Core.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - ServiceStack.MsgPack.Core - ServiceStack.MsgPack - ServiceStack.MsgPack - netstandard2.0;net6.0;net8.0 - ServiceStack.MsgPack .NET Standard 2.0 - - Add the MsgPack binary format and endpoint to a ServiceStack web service host. - - MsgPack;MessagePack;Message;Pack;Fast;Binary;Serializer;Format;ContentType;REST;Web;Services;ServiceStack - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.csproj b/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.csproj index 336fb76afe4..d6553911962 100644 --- a/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.csproj +++ b/ServiceStack/src/ServiceStack.MsgPack/ServiceStack.MsgPack.csproj @@ -3,7 +3,7 @@ ServiceStack.MsgPack ServiceStack.MsgPack - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 Message Pack support for ServiceStack. Includes typed MsgPack Client Add the MsgPack binary format and endpoint to a ServiceStack web service host. diff --git a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.Core.csproj b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.Core.csproj deleted file mode 100644 index 437d89d780c..00000000000 --- a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.Core.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - ServiceStack.Mvc.Core - ServiceStack.Mvc - ServiceStack.Mvc - netstandard2.0;net6.0;net8.0 - ServiceStack.Mvc .NET Standard 2.0 - - MVC Adapter classes to provide tight integration and re-usable functionality between ServiceStack and MVC. - Including adapters for: MiniProfiler, FluentValidation, Funq IOC Controller Factory, Funq Validator Factory ControllerBase (configured with access to ServiceStack's ICacheClient, ISession, typed UserSession dependencies). - - MVC;ServiceStack;MiniProfiler;FluentValidation;Controller - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj index 3a5f2fff749..6757a2007e8 100644 --- a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj +++ b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj @@ -2,7 +2,7 @@ ServiceStack.Mvc ServiceStack.Mvc - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0 MVC Adapters for integrating with ServiceStack webservices MVC Adapter classes to provide tight integration and re-usable functionality between ServiceStack and MVC. @@ -17,10 +17,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -42,12 +45,4 @@ - - - - - - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.NetFramework/ServiceStack.NetFramework.csproj b/ServiceStack/src/ServiceStack.NetFramework/ServiceStack.NetFramework.csproj index 9472889cb69..f0c0f9cba5a 100644 --- a/ServiceStack/src/ServiceStack.NetFramework/ServiceStack.NetFramework.csproj +++ b/ServiceStack/src/ServiceStack.NetFramework/ServiceStack.NetFramework.csproj @@ -2,7 +2,7 @@ ServiceStack.NetFramework ServiceStack.NetFramework - net472 + net472 ServiceStack .NET v4.5 only features for ServiceStack diff --git a/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.Core.csproj b/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.Core.csproj deleted file mode 100644 index 4096615441f..00000000000 --- a/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.Core.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - ServiceStack.ProtoBuf.Core - ServiceStack.ProtoBuf - ServiceStack.ProtoBuf - netstandard2.0;net6.0;net8.0 - ServiceStack.ProtoBuf .NET Standard 2.0 - - Add the ProtoBuf binary format and endpoint to a ServiceStack web service host. - - ProtoBuf;Fast;Binary;Serializer;Format;ContentType;REST;Web;Services;ServiceStack - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.csproj b/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.csproj index c88b6604eed..a5ad614d633 100644 --- a/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.csproj +++ b/ServiceStack/src/ServiceStack.ProtoBuf/ServiceStack.ProtoBuf.csproj @@ -2,7 +2,7 @@ ServiceStack.ProtoBuf ServiceStack.ProtoBuf - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 Protocol Buffers support for ServiceStack. Includes typed ProtoBuf Client Add the ProtoBuf binary format and endpoint to a ServiceStack web service host. diff --git a/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.Core.csproj b/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.Core.csproj deleted file mode 100644 index ff33a621721..00000000000 --- a/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.Core.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - ServiceStack.RabbitMq.Core - ServiceStack.RabbitMq - ServiceStack.RabbitMq - netstandard2.0;net6.0;net8.0 - ServiceStack.RabbitMq .NET Standard 2.0 - - Rabbit MQ client and server adapters for the ServiceStack web services framework. - This library enables consuming and publishing to ServiceStack services when hosted within a Rabbit MQ Server Host. - - MQ;Message;Queue;Web;Service;Framework;Fast;JSON;ServiceStack - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.csproj b/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.csproj index 9bf1fe476d0..4bef051bc38 100644 --- a/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.csproj +++ b/ServiceStack/src/ServiceStack.RabbitMq/ServiceStack.RabbitMq.csproj @@ -3,7 +3,7 @@ ServiceStack.RabbitMq ServiceStack.RabbitMq - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack.RabbitMq Rabbit MQ client and server adapters for the ServiceStack web services framework. diff --git a/ServiceStack/src/ServiceStack.Razor/ServiceStack.Razor.csproj b/ServiceStack/src/ServiceStack.Razor/ServiceStack.Razor.csproj index d565f09a0cd..ea4e93eafa7 100644 --- a/ServiceStack/src/ServiceStack.Razor/ServiceStack.Razor.csproj +++ b/ServiceStack/src/ServiceStack.Razor/ServiceStack.Razor.csproj @@ -2,7 +2,7 @@ ServiceStack.Razor ServiceStack.Razor - net472 + net472 ServiceStack.Razor - ServiceStack's HTML story including MVC Razor ServiceStack Razor Documentation: https://razor.netcore.io diff --git a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.Core.csproj b/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.Core.csproj deleted file mode 100644 index 9548d5ac3b2..00000000000 --- a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.Core.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - ServiceStack.Server.Core - ServiceStack.Server - ServiceStack.Server - netstandard2.0;net6.0;net8.0 - ServiceStack.Server .NET Standard 2.0 - - Integration libraries and adapters with other major ServiceStack server components. - Includes high-level API's that use OrmLite and Redis including: - AutoQuery, Redis MQ, OrmLiteAuthRepository, OrmLiteCacheClient, OrmLiteAppSettings, - RedisServerEvents and RedisRequestLogger. - - ServiceStack;AutoQuery;OrmLite;Redis;MQ;Caching;ServerEvents - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - diff --git a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj b/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj index 86294145e6f..b080b774516 100644 --- a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj +++ b/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj @@ -4,7 +4,7 @@ ServiceStack.Server ServiceStack.Server ServiceStack - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 enable ServiceStack Server integration with Redis and OrmLite @@ -27,10 +27,13 @@ $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack/ServiceStack.Core.csproj b/ServiceStack/src/ServiceStack/ServiceStack.Core.csproj deleted file mode 100644 index 8c572df6f68..00000000000 --- a/ServiceStack/src/ServiceStack/ServiceStack.Core.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - ServiceStack.Core - ServiceStack - ServiceStack - netstandard2.0;net6.0;net8.0 - ServiceStack .NET Standard 2.0 - - ServiceStack is a simple and fast alternative to WCF, MVC and Web API in one cohesive framework for all your services - and web apps that's intuitive and Easy to use! - - To get started see: https://servicestack.net/getting-started - - Fast;JSON;XML;CSV;HTML;SOAP;JSV;REST;Web;Service;Framework;ServiceStack - - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack/ServiceStack.csproj b/ServiceStack/src/ServiceStack/ServiceStack.csproj index 178ae46a745..49f67a9a1b2 100644 --- a/ServiceStack/src/ServiceStack/ServiceStack.csproj +++ b/ServiceStack/src/ServiceStack/ServiceStack.csproj @@ -2,7 +2,7 @@ ServiceStack ServiceStack - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack webservice framework: Faster, Cleaner, Modern WCF alternative ServiceStack is a simple and fast alternative to WCF, MVC and Web API in one cohesive framework for all your services and web apps that's intuitive and Easy to use! @@ -14,14 +14,14 @@ $(DefineConstants);NETFX;NET472 - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - - $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER - $(DefineConstants);NETCORE;NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -44,26 +44,7 @@ - - - - - - - - - - - - - - - - - - - - + diff --git a/ServiceStack/tests/Adhoc/Adhoc.csproj b/ServiceStack/tests/Adhoc/Adhoc.csproj index c3c8f3cad18..c6ed65fa2c8 100644 --- a/ServiceStack/tests/Adhoc/Adhoc.csproj +++ b/ServiceStack/tests/Adhoc/Adhoc.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable MyApp diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index 294efe3d767..dfdcc9026bb 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable MyApp @@ -15,11 +15,12 @@ - - - - - + + + + + + diff --git a/ServiceStack/tests/CheckCoreApi/CheckCoreApi.csproj b/ServiceStack/tests/CheckCoreApi/CheckCoreApi.csproj index be590d9168a..c464179180b 100644 --- a/ServiceStack/tests/CheckCoreApi/CheckCoreApi.csproj +++ b/ServiceStack/tests/CheckCoreApi/CheckCoreApi.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/ServiceStack/tests/CheckGrpc/CheckGrpc.csproj b/ServiceStack/tests/CheckGrpc/CheckGrpc.csproj index da8fbff9b80..74d6882c645 100644 --- a/ServiceStack/tests/CheckGrpc/CheckGrpc.csproj +++ b/ServiceStack/tests/CheckGrpc/CheckGrpc.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/ServiceStack/tests/CheckRazorCore/CheckRazorCore.csproj b/ServiceStack/tests/CheckRazorCore/CheckRazorCore.csproj index f15f27df30b..b46b40ba8fe 100644 --- a/ServiceStack/tests/CheckRazorCore/CheckRazorCore.csproj +++ b/ServiceStack/tests/CheckRazorCore/CheckRazorCore.csproj @@ -2,7 +2,7 @@ false - net8.0 + net10.0 diff --git a/ServiceStack/tests/CheckRazorPages/CheckRazorPages.csproj b/ServiceStack/tests/CheckRazorPages/CheckRazorPages.csproj index 04aa5b831dd..f245b5673bc 100644 --- a/ServiceStack/tests/CheckRazorPages/CheckRazorPages.csproj +++ b/ServiceStack/tests/CheckRazorPages/CheckRazorPages.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable MyApp @@ -12,10 +12,10 @@ - - - - + + + + diff --git a/ServiceStack/tests/CheckTemplatesCore/CheckTemplatesCore.csproj b/ServiceStack/tests/CheckTemplatesCore/CheckTemplatesCore.csproj index 530224457f7..e068035fcc0 100644 --- a/ServiceStack/tests/CheckTemplatesCore/CheckTemplatesCore.csproj +++ b/ServiceStack/tests/CheckTemplatesCore/CheckTemplatesCore.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable CheckTemplatesCore diff --git a/ServiceStack/tests/CheckWebCore/CheckWebCore.csproj b/ServiceStack/tests/CheckWebCore/CheckWebCore.csproj index 8453b7b14af..07a5a533458 100644 --- a/ServiceStack/tests/CheckWebCore/CheckWebCore.csproj +++ b/ServiceStack/tests/CheckWebCore/CheckWebCore.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 enable enable latest diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/ServiceStack/tests/NetCoreTests/NetCoreTests.csproj b/ServiceStack/tests/NetCoreTests/NetCoreTests.csproj index a3759fab62a..c1b10b8cc42 100644 --- a/ServiceStack/tests/NetCoreTests/NetCoreTests.csproj +++ b/ServiceStack/tests/NetCoreTests/NetCoreTests.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 portable Library enable diff --git a/ServiceStack/tests/NorthwindAuto/NorthwindAuto.csproj b/ServiceStack/tests/NorthwindAuto/NorthwindAuto.csproj index cc066a6db95..99aeb552c68 100644 --- a/ServiceStack/tests/NorthwindAuto/NorthwindAuto.csproj +++ b/ServiceStack/tests/NorthwindAuto/NorthwindAuto.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable MyApp @@ -12,11 +12,11 @@ - - - - - + + + + + diff --git a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj index 07a0f214fa6..a8dd5d8627e 100644 --- a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj +++ b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable aspnet-NorthwindBlazor-4090C1D0-7557-4847-8314-1DFD16B99DAF @@ -14,11 +14,11 @@ - - - - - + + + + + diff --git a/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj b/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj index 24ef89a22e0..5f53d7b715d 100644 --- a/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 portable ServiceStack.Common.Tests ServiceStack.Common.Tests @@ -51,10 +52,10 @@ - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + diff --git a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj index c74e53066c2..589b08abb3e 100644 --- a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj @@ -1,11 +1,11 @@  - net8.0 + net10.0 default - + $(DefineConstants);AUTOQUERY_CRUD @@ -38,8 +38,8 @@ - - + + diff --git a/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.csproj b/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.csproj index 0441a9229f8..2fb4a25fa19 100644 --- a/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 full ServiceStack.Server.Tests Library @@ -14,9 +15,9 @@ false false - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER - + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + @@ -60,14 +61,11 @@ - - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + - - \ No newline at end of file diff --git a/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.sln b/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.sln new file mode 100644 index 00000000000..f6e46406ce7 --- /dev/null +++ b/ServiceStack/tests/ServiceStack.Server.Tests/ServiceStack.Server.Tests.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Server.Tests", "ServiceStack.Server.Tests.csproj", "{1D2CFB14-A4CA-49CB-24A6-BB2E5C9CFDE5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D2CFB14-A4CA-49CB-24A6-BB2E5C9CFDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D2CFB14-A4CA-49CB-24A6-BB2E5C9CFDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D2CFB14-A4CA-49CB-24A6-BB2E5C9CFDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D2CFB14-A4CA-49CB-24A6-BB2E5C9CFDE5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4E94145E-1844-4BE8-878D-AE7E8591A782} + EndGlobalSection +EndGlobal diff --git a/ServiceStack/tests/ServiceStack.Server.Tests/Shared/CacheClientTestsAsyncBase.cs b/ServiceStack/tests/ServiceStack.Server.Tests/Shared/CacheClientTestsAsyncBase.cs index f8f1e89009a..b90bb0d40d1 100644 --- a/ServiceStack/tests/ServiceStack.Server.Tests/Shared/CacheClientTestsAsyncBase.cs +++ b/ServiceStack/tests/ServiceStack.Server.Tests/Shared/CacheClientTestsAsyncBase.cs @@ -7,7 +7,7 @@ using ServiceStack.Auth; using ServiceStack.Caching; using ServiceStack.OrmLite; -using ServiceStack.Text; +using AsyncEnumerableExtensions = System.Linq.AsyncEnumerable; namespace ServiceStack.Server.Tests.Shared { @@ -242,7 +242,7 @@ public async Task Can_retrieve_TimeToLive_on_IAuthSession() [Test] public async Task Can_retrieve_IAuthSession_with_global_ExcludeTypeInfo_set() { - JsConfig.ExcludeTypeInfo = true; + Text.JsConfig.ExcludeTypeInfo = true; IAuthSession session = new CustomAuthSession { @@ -261,7 +261,7 @@ public async Task Can_retrieve_IAuthSession_with_global_ExcludeTypeInfo_set() Assert.That(typedSession, Is.Not.Null); Assert.That(typedSession.Custom, Is.EqualTo("custom")); - JsConfig.Reset(); + Text.JsConfig.Reset(); } [Test] @@ -284,7 +284,7 @@ public async Task Can_GetKeysByPattern() if (!(Cache is ICacheClientExtended)) return; - JsConfig.ExcludeTypeInfo = true; + Text.JsConfig.ExcludeTypeInfo = true; for (int i = 0; i < 5; i++) { @@ -302,7 +302,7 @@ public async Task Can_GetKeysByPattern() var sessionPattern = IdUtils.CreateUrn(""); Assert.That(sessionPattern, Is.EqualTo("urn:iauthsession:")); -#if !NETFX +#if !NETFX var sessionKeys = await Cache.GetKeysStartingWithAsync(sessionPattern).ToListAsync(); Assert.That(sessionKeys.Count, Is.EqualTo(5)); @@ -311,16 +311,16 @@ public async Task Can_GetKeysByPattern() var allSessions = await Cache.GetAllAsync(sessionKeys); Assert.That(allSessions.Values.Count(x => x != null), Is.EqualTo(sessionKeys.Count)); - var allKeys = (await Cache.GetAllKeysAsync().ToListAsync()).ToList(); + var allKeys = await Cache.GetAllKeysAsync().ToListAsync(); Assert.That(allKeys.Count, Is.EqualTo(10)); #endif - JsConfig.Reset(); + Text.JsConfig.Reset(); } [Test] public async Task Can_Cache_AllFields() { - JsConfig.DateHandler = DateHandler.ISO8601; + Text.JsConfig.DateHandler = Text.DateHandler.ISO8601; var dto = new AllFields { @@ -350,7 +350,7 @@ public async Task Can_Cache_AllFields() Assert.That(fromCache.Equals(dto)); - JsConfig.Reset(); + Text.JsConfig.Reset(); } #if !NETFX @@ -362,12 +362,12 @@ public async Task Can_RemoveAll_and_GetKeysStartingWith_with_prefix() await cache.SetAsync("test_QUERY_Deposit__Query_Deposit_10_1", "A"); await cache.SetAsync("test_QUERY_Deposit__0_1___CUSTOM", "B"); - var keys = (await cache.GetKeysStartingWithAsync("test_QUERY_Deposit").ToListAsync()).ToList(); + var keys = await System.Linq.AsyncEnumerable.ToListAsync(cache.GetKeysStartingWithAsync("test_QUERY_Deposit")); Assert.That(keys.Count, Is.EqualTo(2)); await cache.RemoveAllAsync(keys); - var newKeys = (await cache.GetKeysStartingWithAsync("test_QUERY_Deposit").ToListAsync()).ToList(); + var newKeys = await System.Linq.AsyncEnumerable.ToListAsync(cache.GetKeysStartingWithAsync("test_QUERY_Deposit")); Assert.That(newKeys.Count, Is.EqualTo(0)); } #endif diff --git a/ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj b/ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj index 646a511e6fe..0cd3e0b9bbb 100644 --- a/ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.ServiceModel.Tests/ServiceStack.ServiceModel.Tests.csproj @@ -1,7 +1,8 @@  - net472;net8.0 + net10.0;net472 + net10.0 ServiceStack.ServiceModel.Tests ServiceStack.ServiceModel.Tests false diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj index cc06c456fb7..19f06fac7e0 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj @@ -1,6 +1,7 @@  - net472;net8.0 + net10.0;net472 + net10.0 Library ServiceStack.WebHost.Endpoints.Tests ServiceStack.WebHost.Endpoints.Tests @@ -17,8 +18,8 @@ $(DefineConstants);NETFX;NET472 - - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0;NET8_0_OR_GREATER + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER @@ -68,7 +69,7 @@ - + diff --git a/build/build-all.sh b/build/build-all.sh index b0ca0f22a1d..cfaecc7d529 100644 --- a/build/build-all.sh +++ b/build/build-all.sh @@ -17,5 +17,3 @@ cd ../../ServiceStack.Stripe/build ./build.sh cd ../../ServiceStack.Text/build ./build.sh -cd ../../ServiceStack.Core/build -./build.sh diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index ba36236d6cf..c5875f9267f 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -41,10 +41,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT diff --git a/build/stage-output.sh b/build/stage-output.sh index f8b36113558..273512b321a 100644 --- a/build/stage-output.sh +++ b/build/stage-output.sh @@ -10,4 +10,3 @@ cp ../ServiceStack.OrmLite/NuGet/*.* ./staging/ cp ../ServiceStack.Redis/NuGet/*.* ./staging/ cp ../ServiceStack.Stripe/NuGet/*.* ./staging/ cp ../ServiceStack.Text/NuGet/*.* ./staging/ -cp ../ServiceStack.Core/NuGet/*.* ./staging/ diff --git a/build/sync.bat b/build/sync.bat index 39e0c36c7ee..1514b6016dc 100644 --- a/build/sync.bat +++ b/build/sync.bat @@ -33,6 +33,3 @@ COPY build\*.* ..\ServiceStack.Stripe\build\ COPY src\*.* ..\ServiceStack.Text\src\ COPY tests\*.* ..\ServiceStack.Text\tests\ COPY build\*.* ..\ServiceStack.Text\build\ - -COPY src\*.* ..\ServiceStack.Core\src\ -COPY build\*.* ..\ServiceStack.Core\build\ diff --git a/build/sync.sh b/build/sync.sh index d2cff9c4d72..0a223a224c4 100755 --- a/build/sync.sh +++ b/build/sync.sh @@ -33,6 +33,3 @@ cp build/*.* ../ServiceStack.Stripe/build/ cp src/*.* ../ServiceStack.Text/src/ cp tests/*.* ../ServiceStack.Text/tests/ cp build/*.* ../ServiceStack.Text/build/ - -cp src/*.* ../ServiceStack.Core/src/ -cp build/*.* ../ServiceStack.Core/build/ diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index 8a38bd474fa..07b209fe645 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -23,10 +23,14 @@ - $(DefineConstants);NET8_0;NET6_0_OR_GREATER + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + + $(DefineConstants);NET8_0;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT From 7eb4f74d2ddfafce80762527f16df8e1b0fbb108 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 15:58:04 +0800 Subject: [PATCH 009/140] fix Blazor build --- .../Client/MyApp.Client.csproj | 10 +++---- .../Server/MyApp.Server.csproj | 2 +- .../Tests/MyApp.Tests.csproj | 2 +- .../Tests/PrerenderTasks.cs | 30 +++++++++---------- .../Server/MyApp.Server.csproj | 4 +-- .../Tests/MyApp.Tests.csproj | 2 +- .../Client/MyApp.Client.csproj | 10 +++---- .../Server/MyApp.Server.csproj | 2 +- .../Tests/MyApp.Tests.csproj | 2 +- .../Gallery.Server/Gallery.Server.csproj | 4 +-- .../Gallery.Wasm.Client.csproj | 10 +++---- .../Gallery.Wasm.Tests.csproj | 2 +- .../UI.Gallery/Gallery/MyApp/Gallery.csproj | 29 +++++------------- .../tests/UI.Gallery/Gallery/MyApp/Program.cs | 2 +- 14 files changed, 49 insertions(+), 62 deletions(-) diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj index 306f766e34f..aa417d16d24 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Client/MyApp.Client.csproj @@ -9,11 +9,11 @@ - - - - - + + + + + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj index a5862178a27..5b468ee3915 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Server/MyApp.Server.csproj @@ -9,7 +9,7 @@ - + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj index dc45c7a47ec..2e247d22af5 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/MyApp.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/PrerenderTasks.cs b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/PrerenderTasks.cs index 3cfcff50b1e..b7996083fd4 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/PrerenderTasks.cs +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Bootstrap.Tests/Tests/PrerenderTasks.cs @@ -32,25 +32,25 @@ public PrerenderTasks() FileSystemVirtualFiles.RecreateDirectory(PrerenderDir); } - void Render(params ComponentParameter[] parameters) where T : IComponent - { - WriteLine($"Rendering: {typeof(T).FullName}..."); - var component = Context.RenderComponent(parameters); - var route = typeof(T).GetCustomAttribute()?.Template; - if (string.IsNullOrEmpty(route)) - throw new Exception($"Couldn't infer @page for component {typeof(T).Name}"); - - var fileName = route.EndsWith("/") ? route + "index.html" : $"{route}.html"; - - var writeTo = Path.GetFullPath(PrerenderDir.CombineWith(fileName)); - WriteLine($"Written to {writeTo}"); - File.WriteAllText(writeTo, component.Markup); - } + // void Render(params ComponentParameter[] parameters) where T : IComponent + // { + // WriteLine($"Rendering: {typeof(T).FullName}..."); + // var component = Context.RenderComponent(parameters); + // var route = typeof(T).GetCustomAttribute()?.Template; + // if (string.IsNullOrEmpty(route)) + // throw new Exception($"Couldn't infer @page for component {typeof(T).Name}"); + // + // var fileName = route.EndsWith("/") ? route + "index.html" : $"{route}.html"; + // + // var writeTo = Path.GetFullPath(PrerenderDir.CombineWith(fileName)); + // WriteLine($"Written to {writeTo}"); + // File.WriteAllText(writeTo, component.Markup); + // } [Test] public void PrerenderPages() { - Render(); + // Render(); } [Test] diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj index 10343a98493..5a70129ed8e 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Server/MyApp.Server.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj index 0f06a5bc09d..d358f0774a5 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Server.Tests/Tests/MyApp.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj index ab3b800bd67..da407431972 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Client/MyApp.Client.csproj @@ -10,11 +10,11 @@ - - - - - + + + + + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj index 55399c8c925..0948bbdd57c 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Server/MyApp.Server.csproj @@ -9,7 +9,7 @@ - + diff --git a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj index dc45c7a47ec..426e66fad0a 100644 --- a/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj +++ b/ServiceStack.Blazor/tests/ServiceStack.Blazor.Tailwind.Tests/Tests/MyApp.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj index fd777a4596e..4166c652361 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Server/Gallery.Server.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj index 3552d22ed1c..ccdd209d11e 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Client/Gallery.Wasm.Client.csproj @@ -16,11 +16,11 @@ - - - - - + + + + + diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj index dc0aa7cb60f..1809cf2a97c 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery.Wasm/Gallery.Wasm.Tests/Gallery.Wasm.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj index 6779756fb7e..70b8957d0ac 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Gallery.csproj @@ -17,27 +17,14 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Program.cs b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Program.cs index 60626561c26..d4f176ff633 100644 --- a/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Program.cs +++ b/ServiceStack.Blazor/tests/UI.Gallery/Gallery/MyApp/Program.cs @@ -34,7 +34,7 @@ var connectionString = config.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); services.AddDbContext(options => options.UseSqlite(connectionString, b => b.MigrationsAssembly(nameof(MyApp)))); -services.AddDatabaseDeveloperPageExceptionFilter(); +//services.AddDatabaseDeveloperPageExceptionFilter(); services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) .AddRoles() From 6ce1371131575755bf9b32e4af82097a1386600d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 17:11:23 +0800 Subject: [PATCH 010/140] Remove Sqlite.Windows and Sqlite.Cil --- .github/workflows/nuget-pack.yml | 4 +- ServiceStack.OrmLite/build/build.proj | 10 -- .../Properties/AssemblyInfo.cs | 1 - .../ServiceStack.OrmLite.Sqlite.Cil.csproj | 61 ------------ .../SqliteOrmLiteDialectProvider.cs | 33 ------- .../Properties/AssemblyInfo.cs | 1 - ...ServiceStack.OrmLite.Sqlite.Windows.csproj | 57 ------------ .../SqliteDialect.cs | 10 -- .../SqliteOrmLiteDialectProvider.cs | 30 ------ .../src/ServiceStack.OrmLite.sln | 37 -------- ...eStack.OrmLite.Sqlite.Windows.Tests.csproj | 25 ----- .../UseCase/NullableDateTimeOffset.cs | 92 ------------------- .../ServiceStack.OrmLite.Tests.csproj | 1 - ServiceStack/src/ServiceStack.sln | 14 --- 14 files changed, 2 insertions(+), 374 deletions(-) delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/Properties/AssemblyInfo.cs delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/ServiceStack.OrmLite.Sqlite.Cil.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/SqliteOrmLiteDialectProvider.cs delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/Properties/AssemblyInfo.cs delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/ServiceStack.OrmLite.Sqlite.Windows.csproj delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteDialect.cs delete mode 100644 ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteOrmLiteDialectProvider.cs delete mode 100644 ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/ServiceStack.OrmLite.Sqlite.Windows.Tests.csproj delete mode 100644 ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/UseCase/NullableDateTimeOffset.cs diff --git a/.github/workflows/nuget-pack.yml b/.github/workflows/nuget-pack.yml index d907e9a84de..9c11b585eed 100644 --- a/.github/workflows/nuget-pack.yml +++ b/.github/workflows/nuget-pack.yml @@ -237,9 +237,9 @@ jobs: echo "number_of_packages=${number_of_packages}" >> $GITHUB_ENV - name: Check number of packages - if: env.number_of_packages < 73 + if: env.number_of_packages < 49 run: | - echo "Expected at least 73 packages, got ${{ env.number_of_packages }}, failing." + echo "Expected at least 49 packages, got ${{ env.number_of_packages }}, failing." exit 1 - uses: actions/upload-artifact@v4 diff --git a/ServiceStack.OrmLite/build/build.proj b/ServiceStack.OrmLite/build/build.proj index cbd3d2ff5a8..3b86db22a5e 100644 --- a/ServiceStack.OrmLite/build/build.proj +++ b/ServiceStack.OrmLite/build/build.proj @@ -70,16 +70,6 @@ Targets="Build;Pack" Properties="Configuration=$(Configuration)" /> - - - - - - - - - net6.0 - ServiceStack.OrmLite.Sqlite - ServiceStack.OrmLite.Sqlite.Cil - OrmLite.Sqlite with SQLitePCLRaw CIL - - Light, simple and fast convention-based code-first POCO ORM for SQLite. - Support for Creating and Dropping Table Schemas from POCOs, Complex Property types transparently stored in schemaless text blobs in SQLite. - - SQLite;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - - - - - - - - - - - - Converters\SqliteBoolConverter.cs - - - Converters\SqliteByteArrayConverter.cs - - - Converters\SqliteCharConverter.cs - - - Converters\SqliteDateTimeConverter.cs - - - Converters\SqliteDateTimeOffsetConverter.cs - - - Converters\SqliteGuidConverter.cs - - - Converters\SqliteStringConverters.cs - - - SqliteConfiguration.cs - - - SqliteDialect.cs - - - SqliteExpression.cs - - - SqliteOrmLiteDialectProviderBase.cs - - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/SqliteOrmLiteDialectProvider.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/SqliteOrmLiteDialectProvider.cs deleted file mode 100644 index 8f723445672..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Cil/SqliteOrmLiteDialectProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Data; -using Microsoft.Data.Sqlite; -using ServiceStack.OrmLite.Sqlite.Converters; - -namespace ServiceStack.OrmLite.Sqlite -{ - public class SqliteOrmLiteDialectProvider : SqliteOrmLiteDialectProviderBase - { - public static SqliteOrmLiteDialectProvider Instance = new(); - - public SqliteOrmLiteDialectProvider() - { - base.RegisterConverter(new SqliteDataDateTimeConverter()); - base.RegisterConverter(new SqliteDataGuidConverter()); - } - - protected override IDbConnection CreateConnection(string connectionString) - { - // Microsoft.Data.Sqlite no like - connectionString = connectionString - .Replace(";Version=3", "") - .Replace(";New=True", "") - .Replace(";Compress=True", ""); - return new SqliteConnection(connectionString); - } - - public override IDbDataParameter CreateParam() - { - return new SqliteParameter(); - } - } -} \ No newline at end of file diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/Properties/AssemblyInfo.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/Properties/AssemblyInfo.cs deleted file mode 100644 index 22ae012d6cc..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/Properties/AssemblyInfo.cs +++ /dev/null @@ -1 +0,0 @@ -[assembly: System.Reflection.AssemblyVersion("6.0.0.0")] diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/ServiceStack.OrmLite.Sqlite.Windows.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/ServiceStack.OrmLite.Sqlite.Windows.csproj deleted file mode 100644 index d64a9674192..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/ServiceStack.OrmLite.Sqlite.Windows.csproj +++ /dev/null @@ -1,57 +0,0 @@ - - - net472 - ServiceStack.OrmLite.Sqlite.Windows - ServiceStack.OrmLite.Sqlite.Windows - OrmLite.Sqlite - Fast, code-first, config-free POCO ORM - - For cross-platform Sqlite use ServiceStack.OrmLite.Sqlite instead. - Light, simple and fast convention-based code-first POCO ORM for SQLite. - Support for Creating and Dropping Table Schemas from POCOs, Complex Property types transparently stored in schemaless text blobs in SQLite. - - SQLite;Windows;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs - $(DefineConstants);ASYNC - - - - - - - - - - - - - Converters\SqliteBoolConverter.cs - - - Converters\SqliteByteArrayConverter.cs - - - Converters\SqliteCharConverter.cs - - - Converters\SqliteDateTimeConverter.cs - - - Converters\SqliteDateTimeOffsetConverter.cs - - - Converters\SqliteGuidConverter.cs - - - Converters\SqliteStringConverters.cs - - - SqliteConfiguration.cs - - - SqliteExpression.cs - - - SqliteOrmLiteDialectProviderBase.cs - - - - \ No newline at end of file diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteDialect.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteDialect.cs deleted file mode 100644 index d64130e8c3f..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteDialect.cs +++ /dev/null @@ -1,10 +0,0 @@ -using ServiceStack.OrmLite.Sqlite; - -namespace ServiceStack.OrmLite; - -public static class SqliteDialect -{ - public static IOrmLiteDialectProvider Provider => SqliteOrmLiteDialectProvider.Instance; - public static SqliteOrmLiteDialectProvider Instance => SqliteOrmLiteDialectProvider.Instance; - public static SqliteOrmLiteDialectProviderBase Create() => new SqliteOrmLiteDialectProvider(); -} diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteOrmLiteDialectProvider.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteOrmLiteDialectProvider.cs deleted file mode 100644 index ecd5187882d..00000000000 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Windows/SqliteOrmLiteDialectProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Data; -using System.Data.SQLite; -using ServiceStack.OrmLite.Sqlite.Converters; - -namespace ServiceStack.OrmLite.Sqlite; - -//Alias -public class SqliteWindowsOrmLiteDialectProvider : SqliteOrmLiteDialectProvider {} - -public class SqliteOrmLiteDialectProvider : SqliteOrmLiteDialectProviderBase -{ - public static SqliteOrmLiteDialectProvider Instance = new SqliteOrmLiteDialectProvider(); - - public SqliteOrmLiteDialectProvider() : base() - { - OrmLiteConfig.DeoptimizeReader = true; - base.RegisterConverter(new SqliteWindowsDateTimeConverter()); - } - - protected override IDbConnection CreateConnection(string connectionString) - { - return new SQLiteConnection(connectionString, parseViaFramework: ParseViaFramework); - } - - public override IDbDataParameter CreateParam() - { - return new SQLiteParameter(); - } -} \ No newline at end of file diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln index 421e683529a..71fb4bed0ba 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.sln @@ -45,8 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "T4", "T4", "{ACB237F8-B931- T4\OrmLite.SP.tt = T4\OrmLite.SP.tt EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.OrmLite.Sqlite.Windows", "ServiceStack.OrmLite.Sqlite.Windows\ServiceStack.OrmLite.Sqlite.Windows.csproj", "{A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.OrmLite.SqliteTests", "..\tests\ServiceStack.OrmLite.SqliteTests\ServiceStack.OrmLite.SqliteTests.csproj", "{956FD518-A6CC-46B1-A93A-1C92779BF942}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.OrmLite.SqlServer.Converters", "ServiceStack.OrmLite.SqlServer.Converters\ServiceStack.OrmLite.SqlServer.Converters.csproj", "{7E9BB57B-53E3-425E-9882-B6E078C5C9BA}" @@ -63,8 +61,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.Sqlite EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.SqlServer.Data", "ServiceStack.OrmLite.SqlServer.Data\ServiceStack.OrmLite.SqlServer.Data.csproj", "{A889689C-3B66-42E8-91EE-E2BCCDC00592}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OrmLite.Sqlite.Cil", "ServiceStack.OrmLite.Sqlite.Cil\ServiceStack.OrmLite.Sqlite.Cil.csproj", "{0A8CE66F-2D9B-4E52-9043-710A6E250660}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{E887DD4B-99AE-4FF0-A531-12167283332A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text", "..\..\ServiceStack.Text\src\ServiceStack.Text\ServiceStack.Text.csproj", "{E6CD1A20-1E43-44CE-9580-634C759FA45A}" @@ -273,21 +269,6 @@ Global {DE5CA3EB-010D-492A-A0B7-501D7941FA51}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {DE5CA3EB-010D-492A-A0B7-501D7941FA51}.Release|Mixed Platforms.Build.0 = Release|Any CPU {DE5CA3EB-010D-492A-A0B7-501D7941FA51}.Release|x86.ActiveCfg = Release|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Debug|x86.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Default|Any CPU.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Default|Any CPU.Build.0 = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Default|Mixed Platforms.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Default|Mixed Platforms.Build.0 = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Default|x86.ActiveCfg = Debug|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Release|Any CPU.Build.0 = Release|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {A5541444-70EE-4B5E-8A5F-C7D8A0048EBE}.Release|x86.ActiveCfg = Release|Any CPU {956FD518-A6CC-46B1-A93A-1C92779BF942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {956FD518-A6CC-46B1-A93A-1C92779BF942}.Debug|Any CPU.Build.0 = Debug|Any CPU {956FD518-A6CC-46B1-A93A-1C92779BF942}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -427,24 +408,6 @@ Global {A889689C-3B66-42E8-91EE-E2BCCDC00592}.Release|Mixed Platforms.Build.0 = Release|Any CPU {A889689C-3B66-42E8-91EE-E2BCCDC00592}.Release|x86.ActiveCfg = Release|Any CPU {A889689C-3B66-42E8-91EE-E2BCCDC00592}.Release|x86.Build.0 = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|x86.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Debug|x86.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|Any CPU.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|Any CPU.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|Mixed Platforms.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|x86.ActiveCfg = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Default|x86.Build.0 = Debug|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|Any CPU.Build.0 = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|x86.ActiveCfg = Release|Any CPU - {0A8CE66F-2D9B-4E52-9043-710A6E250660}.Release|x86.Build.0 = Release|Any CPU {E6CD1A20-1E43-44CE-9580-634C759FA45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E6CD1A20-1E43-44CE-9580-634C759FA45A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E6CD1A20-1E43-44CE-9580-634C759FA45A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/ServiceStack.OrmLite.Sqlite.Windows.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/ServiceStack.OrmLite.Sqlite.Windows.Tests.csproj deleted file mode 100644 index 65d7512e6bd..00000000000 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/ServiceStack.OrmLite.Sqlite.Windows.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - net472 - net8.0 - ServiceStack.OrmLite.Sqlite.Windows.Testss - ServiceStack.OrmLite.Sqlite.Windows.Tests - default - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/UseCase/NullableDateTimeOffset.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/UseCase/NullableDateTimeOffset.cs deleted file mode 100644 index cda0aea693e..00000000000 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Sqlite.Windows.Tests/UseCase/NullableDateTimeOffset.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using NUnit.Framework; -using ServiceStack.OrmLite.Sqlite; -using ServiceStack.DataAnnotations; - -namespace ServiceStack.OrmLite.Tests.UseCase -{ - [TestFixture] - public class NullableDateTimeOffset - { - [OneTimeSetUp] - public void TestFixtureSetUp() - { - //Inject your database provider here - OrmLiteConfig.DialectProvider = new SqliteWindowsOrmLiteDialectProvider(); - } - - public class Record - { - [AutoIncrement] - public long Id { get; set; } - - public DateTimeOffset CreatedDate { get; set; } - - public DateTimeOffset? ModifiedDate { get; set; } - } - - [Test] - public void Can_Insert_RecordWithNullable_DateTimeOffset() - { - var path = SqliteDb.FileConnection; - if (File.Exists(path)) - File.Delete(path); - - using (IDbConnection db = path.OpenDbConnection()) - { - db.CreateTable(true); - - Assert.DoesNotThrow(() => db.Insert(new OrmLite.Tests.UseCase.NullableDateTimeOffset.Record() { Id = 1, CreatedDate = DateTime.Now })); - } - - File.Delete(path); - } - - [Test] - public void Can_Update_RecordWithNullable_DateTimeOffset() - { - var path = SqliteDb.FileConnection; - if (File.Exists(path)) - File.Delete(path); - - using (IDbConnection db = path.OpenDbConnection()) - { - db.CreateTable(true); - - db.Insert(new OrmLite.Tests.UseCase.NullableDateTimeOffset.Record() {Id = 1, CreatedDate = DateTime.Now }); - - var record = db.LoadSingleById(1); - record.ModifiedDate = DateTimeOffset.Now; - - Assert.DoesNotThrow(() => db.Update(record)); - } - - File.Delete(path); - } - - [Test] - public void Can_UpdateWithNull_RecordWithNullable_DateTimeOffset() - { - var path = SqliteDb.FileConnection; - if (File.Exists(path)) - File.Delete(path); - - using (IDbConnection db = path.OpenDbConnection()) - { - db.CreateTable(true); - - db.Insert(new OrmLite.Tests.UseCase.NullableDateTimeOffset.Record() { Id = 1, CreatedDate = DateTime.Now, ModifiedDate = DateTimeOffset.Now }); - - var record = db.LoadSingleById(1); - record.ModifiedDate = null; - - Assert.DoesNotThrow(() => db.Update(record)); - } - - File.Delete(path); - } - } -} diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj index f16be9650c8..33d3dfabaaf 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj @@ -22,7 +22,6 @@ - diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index 8e501091728..bb018060a05 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -87,8 +87,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CheckGrpc", "..\tests\Check EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CheckCoreApi", "..\tests\CheckCoreApi\CheckCoreApi.csproj", "{E0AF102A-4503-4768-A15F-AE3CFE05DD50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Desktop", "ServiceStack.Desktop\ServiceStack.Desktop.csproj", "{6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NorthwindAuto", "..\tests\NorthwindAuto\NorthwindAuto.csproj", "{4087FC9D-F03D-43FC-8AE0-78B0E3AF0007}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text", "..\..\ServiceStack.Text\src\ServiceStack.Text\ServiceStack.Text.csproj", "{A7070566-EEE2-46D3-B8BF-A2C79873D267}" @@ -533,18 +531,6 @@ Global {E0AF102A-4503-4768-A15F-AE3CFE05DD50}.Release|Mixed Platforms.Build.0 = Release|Any CPU {E0AF102A-4503-4768-A15F-AE3CFE05DD50}.Release|x86.ActiveCfg = Release|Any CPU {E0AF102A-4503-4768-A15F-AE3CFE05DD50}.Release|x86.Build.0 = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|x86.ActiveCfg = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Debug|x86.Build.0 = Debug|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|Any CPU.Build.0 = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|x86.ActiveCfg = Release|Any CPU - {6DD5C1E3-14AF-401D-98A3-105EE78D4FEF}.Release|x86.Build.0 = Release|Any CPU {4087FC9D-F03D-43FC-8AE0-78B0E3AF0007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4087FC9D-F03D-43FC-8AE0-78B0E3AF0007}.Debug|Any CPU.Build.0 = Debug|Any CPU {4087FC9D-F03D-43FC-8AE0-78B0E3AF0007}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU From 18f4479469e1617c4c6caf76f59278fd756e04d6 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 17:17:03 +0800 Subject: [PATCH 011/140] remove ServiceStack.Desktop build --- ServiceStack/build/build.proj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceStack/build/build.proj b/ServiceStack/build/build.proj index e90e351864c..6a542e306b7 100644 --- a/ServiceStack/build/build.proj +++ b/ServiceStack/build/build.proj @@ -155,9 +155,9 @@ Properties="Configuration=$(Configuration)" /> - + Properties="Configuration=$(Configuration)" /> --> From ddcc767443d8c3fe603ecb97726477396bd93120 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 17:21:29 +0800 Subject: [PATCH 012/140] Re-add ServiceStack.Desktop --- ServiceStack/build/build.proj | 4 ++-- ServiceStack/src/ServiceStack.sln | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ServiceStack/build/build.proj b/ServiceStack/build/build.proj index 6a542e306b7..e90e351864c 100644 --- a/ServiceStack/build/build.proj +++ b/ServiceStack/build/build.proj @@ -155,9 +155,9 @@ Properties="Configuration=$(Configuration)" /> - + Properties="Configuration=$(Configuration)" /> diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index bb018060a05..a29e2a03efa 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -131,6 +131,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.AI.Chat", "Ser EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdhocNew", "..\tests\AdhocNew\AdhocNew.csproj", "{0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Desktop", "ServiceStack.Desktop\ServiceStack.Desktop.csproj", "{6FFE8429-51F7-45C5-9402-FBA0FC216B85}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -795,6 +797,18 @@ Global {0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5}.Release|x86.ActiveCfg = Release|Any CPU {0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5}.Release|x86.Build.0 = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|x86.ActiveCfg = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Debug|x86.Build.0 = Debug|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|Any CPU.Build.0 = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|x86.ActiveCfg = Release|Any CPU + {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2615a1228cb7c84f671b222bc552305f3eb710d8 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 17:34:00 +0800 Subject: [PATCH 013/140] Fix version test --- .github/workflows/pre-release-pack.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-release-pack.yml b/.github/workflows/pre-release-pack.yml index aa96e01a227..5a25d65817d 100644 --- a/.github/workflows/pre-release-pack.yml +++ b/.github/workflows/pre-release-pack.yml @@ -42,9 +42,9 @@ jobs: - name: Check number of packages - if: env.number_of_packages < 73 + if: env.number_of_packages < 49 run: | - echo "less packages produced (${{ env.number_of_packages }}) than expected (>=73), failing." + echo "less packages produced (${{ env.number_of_packages }}) than expected (>=49), failing." ls -1 ./build/staging/*.nupkg exit 1 From ca843f35356df260164727e4db2ad98eb49aadf3 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 18 Nov 2025 17:54:41 +0800 Subject: [PATCH 014/140] Change number_of_packages condition to 48+ --- .github/workflows/feedz-push.yml | 4 ++-- .github/workflows/github-push.yml | 4 ++-- .github/workflows/myget-push.yml | 4 ++-- .github/workflows/nuget-pack.yml | 4 ++-- .github/workflows/nuget-push.yml | 4 ++-- .github/workflows/pre-release-pack.yml | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/feedz-push.yml b/.github/workflows/feedz-push.yml index bd7a9c23d9b..3067a068ff0 100644 --- a/.github/workflows/feedz-push.yml +++ b/.github/workflows/feedz-push.yml @@ -46,8 +46,8 @@ jobs: - name: Push to GitHub working-directory: ./build/staging run: | - # Check if more than 73 packages - if [[ ${number_of_packages} -gt 73 ]]; then + # Check if more than 48 packages + if [[ ${number_of_packages} -gt 48 ]]; then echo "Publishing to Azure Artifacts" dotnet nuget push '*.nupkg' --source ${{ env.FEED_URL }} --api-key ${{ secrets.FEEDZ_TOKEN }} --skip-duplicate else diff --git a/.github/workflows/github-push.yml b/.github/workflows/github-push.yml index 371fabc5f94..69b1498175d 100644 --- a/.github/workflows/github-push.yml +++ b/.github/workflows/github-push.yml @@ -44,8 +44,8 @@ jobs: - name: Push to GitHub working-directory: ./build/staging run: | - # Check if more than 73 packages - if [[ ${number_of_packages} -gt 73 ]]; then + # Check if more than 48 packages + if [[ ${number_of_packages} -gt 48 ]]; then echo "Pushing to GitHub Packages" dotnet nuget push '*.nupkg' --source https://nuget.pkg.github.com/ServiceStack/index.json --api-key ${{ secrets.GITHUB_TOKEN }} else diff --git a/.github/workflows/myget-push.yml b/.github/workflows/myget-push.yml index 7d98138d0ec..7430cacdfff 100644 --- a/.github/workflows/myget-push.yml +++ b/.github/workflows/myget-push.yml @@ -32,8 +32,8 @@ jobs: - name: Push to MyGet working-directory: ./build/staging run: | - # Check if more than 73 packages - if [[ ${number_of_packages} -gt 73 ]]; then + # Check if more than 48 packages + if [[ ${number_of_packages} -gt 48 ]]; then echo "Pushing to MyGet" dotnet nuget push '*.nupkg' -s 'https://www.myget.org/F/servicestack/api/v2/package' -k '${{ secrets.MYGET_APIKEY }}' --skip-duplicate else diff --git a/.github/workflows/nuget-pack.yml b/.github/workflows/nuget-pack.yml index 9c11b585eed..b9819289350 100644 --- a/.github/workflows/nuget-pack.yml +++ b/.github/workflows/nuget-pack.yml @@ -237,9 +237,9 @@ jobs: echo "number_of_packages=${number_of_packages}" >> $GITHUB_ENV - name: Check number of packages - if: env.number_of_packages < 49 + if: env.number_of_packages < 48 run: | - echo "Expected at least 49 packages, got ${{ env.number_of_packages }}, failing." + echo "Expected at least 48 packages, got ${{ env.number_of_packages }}, failing." exit 1 - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/nuget-push.yml b/.github/workflows/nuget-push.yml index 70f6c0d6f54..2a64deeac4d 100644 --- a/.github/workflows/nuget-push.yml +++ b/.github/workflows/nuget-push.yml @@ -32,8 +32,8 @@ jobs: - name: Push to NuGet working-directory: ./build/staging run: | - # Check if more than 73 packages - if [[ ${number_of_packages} -gt 73 ]]; then + # Check if more than 48 packages + if [[ ${number_of_packages} -gt 48 ]]; then echo "Pushing to NuGet" dotnet nuget push '*.nupkg' -s 'https://api.nuget.org/v3/index.json' -k '${{ secrets.NUGET_APIKEY }}' --skip-duplicate else diff --git a/.github/workflows/pre-release-pack.yml b/.github/workflows/pre-release-pack.yml index 5a25d65817d..0f8d1cd9e36 100644 --- a/.github/workflows/pre-release-pack.yml +++ b/.github/workflows/pre-release-pack.yml @@ -42,7 +42,7 @@ jobs: - name: Check number of packages - if: env.number_of_packages < 49 + if: env.number_of_packages < 48 run: | echo "less packages produced (${{ env.number_of_packages }}) than expected (>=49), failing." ls -1 ./build/staging/*.nupkg From 18c74bb1337817f598364af052c3a2b7edd65244 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 02:39:32 +0800 Subject: [PATCH 015/140] Move existing impl to net8 --- .../ConfigureServiceStackSwagger.cs | 2 + .../net8/ConfigureServiceStackSwagger.cs | 37 + .../{ => net8}/IOrderedDictionary.cs | 0 .../net8/OpenApiMetadata.cs | 1060 +++++++++++++++++ .../net8/OpenApiType.cs | 13 + .../net8/OpenApiTypeFormat.cs | 17 + .../{ => net8}/OrderedDictionary.cs | 0 .../net8/ServiceStackDocumentFilter.cs | 105 ++ .../net8/ServiceStackOpenApiExtensions.cs | 80 ++ .../net8/SwaggerUtils.cs | 18 + 10 files changed, 1332 insertions(+) create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs rename ServiceStack/src/ServiceStack.AspNetCore.OpenApi/{ => net8}/IOrderedDictionary.cs (100%) create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs rename ServiceStack/src/ServiceStack.AspNetCore.OpenApi/{ => net8}/OrderedDictionary.cs (100%) create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs index cb9c2a5cfec..2058a55d858 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs @@ -1,3 +1,4 @@ +#if NET10_0_OR_GREATER using Microsoft.Extensions.Options; using ServiceStack.AspNetCore.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; @@ -33,3 +34,4 @@ public void Configure(ServiceStackOptions options) options.WithOpenApi(); } } +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs new file mode 100644 index 00000000000..c96742b7a86 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs @@ -0,0 +1,37 @@ +#if !NET10_0_OR_GREATER +using Microsoft.Extensions.Options; +using ServiceStack.AspNetCore.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack; + +public class ConfigureServiceStackSwagger(OpenApiMetadata metadata) : + IConfigureOptions, + IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + foreach (var filterType in metadata.DocumentFilterTypes) + { + options.DocumentFilterDescriptors.Add(new FilterDescriptor + { + Type = filterType, + Arguments = [], + }); + } + foreach (var filterType in metadata.SchemaFilterTypes) + { + options.SchemaFilterDescriptors.Add(new FilterDescriptor + { + Type = filterType, + Arguments = [], + }); + } + } + + public void Configure(ServiceStackOptions options) + { + options.WithOpenApi(); + } +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/IOrderedDictionary.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs rename to ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/IOrderedDictionary.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs new file mode 100644 index 00000000000..e0dc3d3c54a --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs @@ -0,0 +1,1060 @@ +#if !NET10_0_OR_GREATER +using System.Collections.Concurrent; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using ServiceStack.Host; +using ServiceStack.NativeTypes; +using ServiceStack.Text; +using ServiceStack.Web; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiSecurity +{ + public static OpenApiSecurityRequirement BasicAuth { get; } = new() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = BasicAuthenticationHandler.Scheme + } + }, + Array.Empty() + } + }; + public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "HTTP Basic access authentication", + Type = SecuritySchemeType.Http, + Scheme = BasicAuthenticationHandler.Scheme, + }; + + public static OpenApiSecurityRequirement JwtBearer { get; } = new() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme, + } + }, + [] + } + }; + public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "JWT Bearer Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = JwtBearerDefaults.AuthenticationScheme, + }; + + public static OpenApiSecurityRequirement ApiKey { get; } = new() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "ApiKey", + } + }, + [] + } + }; + public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() + { + Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "ApiKey" + }; +} + +public class OpenApiMetadata +{ + public static OpenApiMetadata Instance { get; } = new(); + + public List DocumentFilterTypes { get; set; } = [ + typeof(ServiceStackDocumentFilter), + ]; + public List SchemaFilterTypes { get; set; } = [ + ]; + + public Func? Ignore { get; set; } + + public Action? OperationFilter { get; set; } + public Action? SchemaFilter { get; set; } + public static Action? SchemaPropertyFilter { get; set; } + + private static readonly Dictionary ClrTypesToSwaggerScalarTypes = new() + { + {typeof(byte[]), OpenApiType.String}, + {typeof(sbyte[]), OpenApiType.String}, + {typeof(byte), OpenApiType.Integer}, + {typeof(sbyte), OpenApiType.Integer}, + {typeof(bool), OpenApiType.Boolean}, + {typeof(short), OpenApiType.Integer}, + {typeof(ushort), OpenApiType.Integer}, + {typeof(int), OpenApiType.Integer}, + {typeof(uint), OpenApiType.Integer}, + {typeof(long), OpenApiType.Integer}, + {typeof(ulong), OpenApiType.Integer}, + {typeof(float), OpenApiType.Number}, + {typeof(double), OpenApiType.Number}, + {typeof(decimal), OpenApiType.Number}, + {typeof(string), OpenApiType.String}, + {typeof(DateTime), OpenApiType.String}, + {typeof(DateTimeOffset), OpenApiType.String}, + }; + + private static readonly Dictionary ClrTypesToSwaggerScalarFormats = new() + { + {typeof(byte[]), OpenApiTypeFormat.Byte}, + {typeof(sbyte[]), OpenApiTypeFormat.Byte}, + {typeof(byte), OpenApiTypeFormat.Int}, + {typeof(sbyte), OpenApiTypeFormat.Int}, + {typeof(short), OpenApiTypeFormat.Int}, + {typeof(ushort), OpenApiTypeFormat.Int}, + {typeof(int), OpenApiTypeFormat.Int}, + {typeof(uint), OpenApiTypeFormat.Int}, + {typeof(long), OpenApiTypeFormat.Long}, + {typeof(ulong), OpenApiTypeFormat.Long}, + {typeof(float), OpenApiTypeFormat.Float}, + {typeof(double), OpenApiTypeFormat.Double}, + {typeof(decimal), OpenApiTypeFormat.Double}, + {typeof(DateTime), OpenApiTypeFormat.DateTime}, + {typeof(DateTimeOffset), OpenApiTypeFormat.DateTime}, + }; + + public ConcurrentDictionary Schemas { get; } = new(); + internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); + + public OpenApiSecurityScheme? SecurityDefinition { get; set; } + public OpenApiSecurityRequirement? SecurityRequirement { get; set; } + + public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } + public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } + + /// + /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI + /// + public HashSet ExcludeRequestTypes { get; set; } = []; + + public void AddBasicAuth() + { + SecurityDefinition = OpenApiSecurity.BasicAuthScheme; + SecurityRequirement = OpenApiSecurity.BasicAuth; + } + + public void AddJwtBearer() + { + SecurityDefinition = OpenApiSecurity.JwtBearerScheme; + SecurityRequirement = OpenApiSecurity.JwtBearer; + } + + public void AddApiKeys() + { + ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; + ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; + } + + public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) + { + if (ExcludeRequestTypes.Contains(operation.RequestType)) + return op; + //Console.WriteLine($"AddOperation {verb} {route} {operation.RequestType.Name}..."); + + // Response is handled by Endpoints Metadata + op.Summary = operation.RequestType.GetDescription(); + op.Description = operation.RequestType.FirstAttribute()?.Notes; + + var hasRequestBody = HttpUtils.HasRequestBody(verb); + if (!hasRequestBody) + { + var parameters = CreateParameters(operation.RequestType, route, verb); + op.Parameters.AddDistinctRange(parameters); + } + + var apiAttr = operation.RequestType.FirstAttribute(); + if (hasRequestBody) + { + var openApiType = CreateSchema(operation.RequestType, route, verb); + if (openApiType != null) + { + // Move path parameters from body + var inPaths = new List(); + foreach (var entry in openApiType.Properties) + { + var inPath = route.Contains("{" + entry.Key + "}", StringComparison.OrdinalIgnoreCase); + if (inPath) + { + var propNameUsed = route.Contains("{" + entry.Key + "}") + ? entry.Key + : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name + ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); + inPaths.Add(entry.Key); + OpenApiSchema? prop = entry.Value; + op.Parameters.Add(new OpenApiParameter + { + Name = propNameUsed, + In = ParameterLocation.Path, + Required = true, + Schema = prop, + Style = ParameterStyle.Simple, + Explode = true, + }); + } + } + + var formType = new OpenApiMediaType + { + Schema = new(openApiType), + }; + foreach (var propName in inPaths) + { + formType.Schema.Properties.Remove(propName); + } + + foreach (var entry in formType.Schema.Properties) + { + formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + } + op.RequestBody = new() + { + Content = { + [MimeTypes.MultiPartFormData] = formType + } + }; + if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) + { + op.RequestBody.Content[MimeTypes.Json] = new OpenApiMediaType + { + Schema = new() + { + Reference = ToOpenApiReference(operation.RequestType), + } + }; + } + SchemaFilter?.Invoke(op, openApiType); + } + } + + if (operation.RequiresAuthentication) + { + if (SecurityRequirement != null) + op.Security.Add(SecurityRequirement); + } + if (operation.RequiresApiKey) + { + if (ApiKeySecurityDefinition != null) + op.Security.Add(ApiKeySecurityRequirement); + } + + var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); + if (userTags.Count > 0) + { + // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag + op.Tags.Clear(); + userTags.Each(tag => op.Tags.Add(new OpenApiTag { Name = tag })); + } + + OperationFilter?.Invoke(verb, op, operation); + + return op; + } + + internal static OpenApiReference ToOpenApiReference(Type type) => + new() { + Type = ReferenceType.Schema, + Id = GetSchemaDefinitionRef(type), + }; + + private static bool IsKeyValuePairType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + } + + private static bool IsSwaggerScalarType(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.ContainsKey(lookupType) + || lookupType.IsEnum + || (lookupType.IsValueType && !IsKeyValuePairType(lookupType)); + } + + private static string GetSwaggerTypeName(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.TryGetValue(lookupType, out var scalarType) + ? scalarType + : GetSchemaTypeName(lookupType); + } + + private static string GetSwaggerTypeFormat(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + return ClrTypesToSwaggerScalarFormats.GetValueOrDefault(lookupType); + } + + private OpenApiSchema? GetListSchema(Type schemaType) + { + if (!IsListType(schemaType)) + return null; + + var listItemType = GetListElementType(schemaType); + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.Array, + Items = new() + { + Type = listItemType != null && IsSwaggerScalarType(listItemType) + ? GetSwaggerTypeName(listItemType) + : null, + Reference = listItemType != null && !IsSwaggerScalarType(listItemType) + ? ToOpenApiReference(listItemType) + : null, + }, + }; + } + + private static bool IsDictionaryType(Type type) + { + if (!type.IsGenericType) return false; + var genericType = type.GetGenericTypeDefinition(); + return genericType == typeof(Dictionary<,>) + || genericType == typeof(IDictionary<,>) + || genericType == typeof(IReadOnlyDictionary<,>) + || genericType == typeof(SortedDictionary<,>); + } + + private OpenApiSchema? CreateDictionarySchema(Type schemaType) + { + if (!IsDictionaryType(schemaType)) + return null; + + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.Object, + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + AdditionalProperties = GetOpenApiProperty(valueType) + }; + } + + private OpenApiSchema? GetKeyValuePairSchema(Type schemaType) + { + if (!IsKeyValuePairType(schemaType)) + return null; + + var keyType = schemaType.GetGenericArguments()[0]; + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Type = OpenApiType.Object, + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary + { + ["Key"] = GetOpenApiProperty(keyType), + ["Value"] = GetOpenApiProperty(valueType), + } + }; + } + + private static bool IsRequiredType(Type type) + { + return !type.IsNullableType() && type != typeof(string); + } + + public static string GetSchemaTypeName(Type schemaType) + { + if (schemaType.IsEnum) + return schemaType.Name; + + if ((!IsKeyValuePairType(schemaType) && schemaType.IsValueType) || schemaType.IsNullableType()) + return OpenApiType.String; + + if (!schemaType.IsGenericType) + return schemaType.Name; + + var typeName = schemaType.ToPrettyName(); + return typeName; + } + + private static string[]? GetEnumValues(ApiAllowableValuesAttribute? attr) + { + return attr?.Values?.ToArray(); + } + + private static Type? GetListElementType(Type type) + { + if (type.IsArray) + return type.GetElementType(); + if (!type.IsGenericType) + return null; + var genericType = type.GetGenericTypeDefinition(); + if (genericType == typeof(List<>) || genericType == typeof(IList<>) || genericType == typeof(IEnumerable<>)) + return type.GetGenericArguments()[0]; + return null; + } + + private static bool IsListType(Type type) + { + //Swagger2 specification has a special data format for type byte[] ('byte', 'binary' or 'file'), so it's not a list + if (type == typeof(byte[])) + return false; + + return GetListElementType(type) != null; + } + + private List CreateParameters(Type operationType, string route, string verb) + { + var hasDataContract = operationType.HasAttribute(); + + var properties = operationType.GetProperties(); + var paramAttrs = new Dictionary(); + var propertyTypes = new Dictionary(); + var allowableParams = new List(); + var defaultOperationParameters = new List(); + + var hasApiMembers = false; + + foreach (var property in properties) + { + if (property.HasAttribute()) + continue; + + var attr = hasDataContract + ? property.FirstAttribute() + : null; + + var propertyName = attr?.Name ?? property.Name; + + var apiMembers = property.AllAttributes(); + if (apiMembers.Length > 0) + hasApiMembers = true; + + paramAttrs[propertyName] = apiMembers; + propertyTypes[propertyName] = property.PropertyType; + var allowableValuesAttrs = property.AllAttributes(); + var allowableValuesAttr = allowableValuesAttrs.FirstOrDefault(); + allowableParams.AddRange(allowableValuesAttrs); + + if (hasDataContract && attr == null) + continue; + + var inPath = route.Contains("{" + propertyName + "}", StringComparison.OrdinalIgnoreCase); + var paramLocation = inPath + ? ParameterLocation.Path + : ParameterLocation.Query; + + var parameter = CreateParameter(property.PropertyType, + propertyName, + paramLocation, + enumValues: GetEnumValues(allowableValuesAttr)); + + defaultOperationParameters.Add(parameter); + } + + var methodOperationParameters = defaultOperationParameters; + if (hasApiMembers) + { + methodOperationParameters = []; + foreach (var key in paramAttrs.Keys) + { + var apiMembers = paramAttrs[key]; + foreach (var member in apiMembers) + { + if ((member.Verb == null || string.Compare(member.Verb, verb, StringComparison.OrdinalIgnoreCase) == 0) + && (member.Route == null || route.StartsWith(member.Route)) + && !string.Equals(member.ParameterType, "model") + && methodOperationParameters.All(x => x.Name != (member.Name ?? key))) + { + var allowableValuesAttr = allowableParams.FirstOrDefault(attr => attr.Name == (member.Name ?? key)); + var p = CreateParameter(propertyTypes[key], + member.Name ?? key, + GetParamLocation(member.GetParamType(operationType, member.Verb ?? verb)), + enumValues: GetEnumValues(allowableValuesAttr), + isApiMember:true); + // p.Type = member.DataType ?? p.Type; + // p.Format = member.Format ?? p.Format; + p.Required = p.In == ParameterLocation.Path || member.IsRequired; + p.Description = member.Description ?? p.Description; + + methodOperationParameters.Add(p); + } + } + } + } + + return methodOperationParameters; + } + + private OpenApiParameter CreateParameter(Type propType, string paramName, + ParameterLocation? paramLocation, + string[]? enumValues = null, + bool isApiMember = false) + { + if (propType.IsEnum) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Reference = ToOpenApiReference(propType), + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsSwaggerScalarType(propType)) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = new() + { + Type = GetSwaggerTypeName(propType), + Enum = enumValues?.Select(x => new OpenApiString(x)).Cast().ToList() ?? [], + Nullable = !IsRequiredType(propType), + Format = GetSwaggerTypeFormat(propType), + }, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (!isApiMember) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = new() + { + Type = OpenApiType.String, + }, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsDictionaryType(propType)) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = CreateDictionarySchema(propType) + }; + } + + if (IsListType(propType)) + { + return CreateArrayParameter(propType, paramName, paramLocation); + } + + OpenApiSchema openApiSchema; + + if (IsInlineSchema(propType)) + { + openApiSchema = CreateSchema(propType); + } + else + { + openApiSchema = new OpenApiSchema { + Reference = ToOpenApiReference(propType) + }; + } + + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = openApiSchema + }; + } + + private static ParameterLocation? GetParamLocation(string paramIn) + { + ParameterLocation? paramLocation = paramIn switch + { + "query" => ParameterLocation.Query, + "header" => ParameterLocation.Header, + "path" => ParameterLocation.Path, + "cookie" => ParameterLocation.Cookie, + _ => null, + }; + return paramLocation; + } + + private OpenApiParameter CreateArrayParameter(Type listType, + string paramName, + ParameterLocation? paramLocation) + { + var listItemType = GetListElementType(listType); + var parameter = new OpenApiParameter + { + In = paramLocation, + Schema = new() { + Type = OpenApiType.Array, + Items = new() + { + Type = listItemType != null && IsSwaggerScalarType(listItemType) + ? GetSwaggerTypeName(listItemType) + : null, + Reference = listItemType != null && !IsSwaggerScalarType(listItemType) + ? ToOpenApiReference(listItemType) + : null, + } + }, + Description = listType.GetDescription(), + Name = paramName, + Required = paramLocation == ParameterLocation.Path, + Style = ParameterStyle.Form, + Explode = true, + }; + return parameter; + } + + private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); + + private OpenApiSchema GetOpenApiProperty(PropertyInfo pi) + { + var schema = GetOpenApiProperty(pi.PropertyType); + schema.Nullable = pi.IsAssignableToNull(); + return schema; + } + + private OpenApiSchema GetOpenApiProperty(Type propertyType) + { + var schemaProp = new OpenApiSchema { + Nullable = propertyType.IsNullableType(), + }; + + propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (IsKeyValuePairType(propertyType)) + { + if (IsInlineSchema(propertyType)) + { + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + } + else + { + schemaProp.Reference = ToOpenApiReference(propertyType); + } + } + else if (IsListType(propertyType)) + { + schemaProp.Type = OpenApiType.Array; + var listItemType = GetListElementType(propertyType); + if (listItemType == null) return schemaProp; + if (IsSwaggerScalarType(listItemType)) + { + schemaProp.Items = new OpenApiSchema { + Type = GetSwaggerTypeName(listItemType), + Format = GetSwaggerTypeFormat(listItemType), + }; + if (IsRequiredType(listItemType)) + { + schemaProp.Nullable = false; + } + } + else if (IsInlineSchema(listItemType)) + { + var schema = CreateSchema(listItemType); + if (schema != null) InlineSchema(schema, schemaProp); + } + else + { + schemaProp.Items = new OpenApiSchema + { + Reference = ToOpenApiReference(listItemType) + }; + } + } + else if (IsDictionaryType(propertyType)) + { + schemaProp = CreateDictionarySchema(propertyType); + } + else if (propertyType.IsEnum) + { + schemaProp.Reference = ToOpenApiReference(propertyType); + } + else if (IsSwaggerScalarType(propertyType)) + { + schemaProp.Type = GetSwaggerTypeName(propertyType); + schemaProp.Format = GetSwaggerTypeFormat(propertyType); + schemaProp.Nullable = !IsRequiredType(propertyType); + //schemaProp.Required = IsRequiredType(propertyType) ? true : (bool?)null; + } + else if (IsInlineSchema(propertyType)) + { + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + } + else + { + //CreateSchema(propertyType, route, verb); + schemaProp.Reference = ToOpenApiReference(propertyType); + } + + return schemaProp; + } + + public static OpenApiSchema CreateEnumSchema(Type propertyType) + { + var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (!enumType.IsEnum) + throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); + + var schema = new OpenApiSchema(); + if (enumType.IsNumericType()) + { + var underlyingType = Enum.GetUnderlyingType(enumType); + schema.Type = GetSwaggerTypeName(underlyingType); + schema.Format = GetSwaggerTypeFormat(underlyingType); + schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); + } + else + { + schema.Type = OpenApiType.String; + schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); + } + return schema; + } + + private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) + { + schemaProp.Title = schema?.Title ?? schemaProp.Title; + schemaProp.Type = schema?.Type ?? schemaProp.Type; + schemaProp.Format = schema?.Format ?? schemaProp.Format; + schemaProp.Description = schema?.Description ?? schemaProp.Description; + schemaProp.Maximum = schema?.Maximum ?? schemaProp.Maximum; + schemaProp.ExclusiveMaximum = schema?.ExclusiveMaximum ?? schemaProp.ExclusiveMaximum; + schemaProp.Minimum = schema?.Minimum ?? schemaProp.Minimum; + schemaProp.ExclusiveMinimum = schema?.ExclusiveMinimum ?? schemaProp.ExclusiveMinimum; + schemaProp.MaxLength = schema?.MaxLength ?? schemaProp.MaxLength; + schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; + schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; + schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; + schemaProp.Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); + schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; + schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; + schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + schemaProp.Not = schema?.Not != null ? new(schema.Not) : null; + schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; + schemaProp.Items = schema?.Items != null ? new(schema.Items) : null; + schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; + schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; + schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; + schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; + schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; + schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; + schemaProp.AdditionalProperties = new(schema?.AdditionalProperties); + schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; + schemaProp.Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); + schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; + schemaProp.Nullable = schema?.Nullable ?? schemaProp.Nullable; + schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; + schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; + schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; + schemaProp.UnresolvedReference = schema?.UnresolvedReference ?? schemaProp.UnresolvedReference; + schemaProp.Reference = schema?.Reference != null ? new(schema.Reference) : null; + } + + private bool IsInlineSchema(Type schemaType) + { + return schemaType.Namespace != null && InlineSchemaTypesInNamespaces.Contains(schemaType.Namespace); + } + + List RequiredValidators { get; } = ["NotNull", "NotEmpty"]; + + public OpenApiSchema? CreateSchema(Type schemaType, string? route=null, string? verb=null, HashSet? allTypes = null) + { + if (schemaType.ExcludesFeature(Feature.Metadata) || schemaType.ExcludesFeature(Feature.ApiExplorer)) + return null; + + if (IsSwaggerScalarType(schemaType) && !schemaType.IsEnum) + return null; + + var schemaId = GetSchemaDefinitionRef(schemaType); + if (Schemas.TryGetValue(schemaId, out var schema)) + return schema; + + schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType); + + bool parseProperties = false; + if (schema == null) + { + if (schemaType.IsEnum) + { + schema = CreateEnumSchema(schemaType); + } + else + { + schema = new OpenApiSchema + { + Type = OpenApiType.Object, + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary() + }; + parseProperties = schemaType.IsUserType(); + } + + if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) + { + schema.AllOf.Add(new OpenApiSchema { Reference = ToOpenApiReference(schemaType.BaseType) }); + } + } + Schemas[schemaId] = schema; + + var properties = schemaType.GetProperties() + .Where(pi => !SwaggerUtils.IgnoreProperty(pi)) + .ToArray(); + + // Order schema properties by DataMember.Order if [DataContract] and [DataMember](s) defined + // Ordering defined by: http://msdn.microsoft.com/en-us/library/ms729813.aspx + var dataContractAttr = schemaType.FirstAttribute(); + if (dataContractAttr != null && properties.Any(prop => prop.IsDefined(typeof(DataMemberAttribute), true))) + { + var typeOrder = new List { schemaType }; + var baseType = schemaType.BaseType; + while (baseType != null) + { + typeOrder.Add(baseType); + baseType = baseType.BaseType; + } + + var propsWithDataMember = properties.Where(prop => prop.IsDefined(typeof(DataMemberAttribute), true)); + var propDataMemberAttrs = properties.ToDictionary(prop => prop, prop => prop.FirstAttribute()); + + properties = propsWithDataMember + .OrderBy(prop => propDataMemberAttrs[prop].Order) // Order by DataMember.Order + .ThenByDescending(prop => typeOrder.IndexOf(prop.DeclaringType)) // Then by BaseTypes First + .ThenBy(prop => // Then by [DataMember].Name / prop.Name + { + var name = propDataMemberAttrs[prop].Name; + return name.IsNullOrEmpty() ? prop.Name : name; + }).ToArray(); + } + + if (parseProperties) + { + foreach (var prop in properties) + { + if (prop.HasAttributeOf()) + continue; + + var apiMembers = prop + .AllAttributes() + .OrderByDescending(attr => attr.Route) + .ToList(); + var apiDoc = apiMembers + .Where(attr => string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(attr.Verb) || (verb ?? "").Equals(attr.Verb)) + .Where(attr => string.IsNullOrEmpty(route) || string.IsNullOrEmpty(attr.Route) || (route ?? "").StartsWith(attr.Route)) + .FirstOrDefault(attr => attr.ParameterType is "body" or "model"); + + if (apiMembers.Any(x => x.ExcludeInSchema)) + continue; + var schemaProperty = GetOpenApiProperty(prop); + var schemaPropertyName = GetSchemaPropertyName(prop); + + schemaProperty.Description = prop.GetDescription() ?? apiDoc?.Description; + + var propAttr = prop.FirstAttribute(); + var validateAttrs = prop.AllAttributes(); + + var isRequired = propAttr?.IsRequired == true + || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) + || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); + + if (propAttr != null) + { + if (propAttr.DataType != null) + schemaProperty.Type = propAttr.DataType; + + if (propAttr.Format != null) + schemaProperty.Format = propAttr.Format; + } + + if (isRequired) + { + schema.Required.Add(schemaPropertyName); + } + + var uploadTo = prop.FirstAttribute(); + if (uploadTo != null) + { + schemaProperty.Reference = null; + if (schemaProperty.Type != OpenApiType.Array) + { + schemaProperty.Type = "file"; + } + schemaProperty.Items = new OpenApiSchema + { + Type = OpenApiType.String, + Format = OpenApiTypeFormat.Binary, + }; + } + + schemaProperty.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); + + SchemaPropertyFilter?.Invoke(schemaProperty); + schema.Properties[schemaPropertyName] = schemaProperty; + } + } + return schema; + } + + private static string GetSchemaPropertyName(PropertyInfo prop) + { + var dataMemberAttr = prop.FirstAttribute(); + if (dataMemberAttr?.Name != null) + return dataMemberAttr.Name; + + return JsConfig.TextCase == TextCase.CamelCase + ? prop.Name.ToCamelCase() + : JsConfig.TextCase == TextCase.SnakeCase + ? prop.Name.ToLowercaseUnderscore() + : prop.Name; + } + + private static List GetNumericValues(Type propertyType, Type underlyingType) + { + var values = Enum.GetValues(propertyType) + .Map(x => $"{Convert.ChangeType(x, underlyingType)} ({x})"); + + return values; + } + + private OpenApiSchema? GetResponseSchema(IRestPath restPath, out string schemaDescription) + { + schemaDescription = string.Empty; + + // Given: class MyDto : IReturn. Determine the type X. + foreach (var i in restPath.RequestType.GetInterfaces()) + { + if (i == typeof(IReturnVoid)) + return GetSchemaForResponseType(typeof(void), out schemaDescription); + + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>)) + { + var schemaType = i.GetGenericArguments()[0]; + return GetSchemaForResponseType(schemaType, out schemaDescription); + } + } + + return new OpenApiSchema { + Type = OpenApiType.Object, + }; + } + + private OpenApiSchema? GetSchemaForResponseType(Type schemaType, out string schemaDescription) + { + if (schemaType == typeof(IReturnVoid) || schemaType == typeof(void)) + { + schemaDescription = "No Content"; + return null; + } + + var schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType) + ?? (IsSwaggerScalarType(schemaType) + ? new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = GetSwaggerTypeName(schemaType), + Format = GetSwaggerTypeFormat(schemaType) + } + : IsInlineSchema(schemaType) + ? CreateSchema(schemaType) + : new OpenApiSchema { Reference = ToOpenApiReference(schemaType) }); + + schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; + + return schema; + } + + private OrderedDictionary GetMethodResponseCodes(IRestPath restPath, IDictionary schemas, Type requestType) + { + var responses = new OrderedDictionary(); + + var responseSchema = GetResponseSchema(restPath, out string schemaDescription); + //schema is null when return type is IReturnVoid + var statusCode = responseSchema == null && HostConfig.Instance.Return204NoContentForEmptyResponse + ? ((int)HttpStatusCode.NoContent).ToString() + : ((int)HttpStatusCode.OK).ToString(); + + responses.Add(statusCode, new OpenApiResponse + { + Content = + { + [MimeTypes.Json] = new OpenApiMediaType + { + Schema = responseSchema, + } + }, + Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" + }); + + foreach (var attr in requestType.AllAttributes()) + { + string apiSchemaDescription = string.Empty; + + var response = new OpenApiResponse + { + Content = + { + [MimeTypes.Json] = new OpenApiMediaType + { + Schema = attr.ResponseType != null + ? GetSchemaForResponseType(attr.ResponseType, out apiSchemaDescription) + : responseSchema, + } + }, + Description = attr.Description ?? apiSchemaDescription + }; + + statusCode = attr.IsDefaultResponse ? "default" : attr.StatusCode.ToString(); + if (!responses.ContainsKey(statusCode)) + responses.Add(statusCode, response); + else + responses[statusCode] = response; + } + + return responses; + } + +} + +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs new file mode 100644 index 00000000000..b89ae5fe9f7 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs @@ -0,0 +1,13 @@ +#if !NET10_0_OR_GREATER +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiType +{ + public const string Array = "array"; + public const string Boolean = "boolean"; + public const string Number = "number"; + public const string Integer = "integer"; + public const string String = "string"; + public const string Object = "object"; +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs new file mode 100644 index 00000000000..b91e50de778 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs @@ -0,0 +1,17 @@ +#if !NET10_0_OR_GREATER +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiTypeFormat +{ + public const string Array = "int32"; + public const string Byte = "byte"; + public const string Binary = "binary"; + public const string Date = "date"; + public const string DateTime = "date-time"; + public const string Double = "double"; + public const string Float = "float"; + public const string Int = "int32"; + public const string Long = "int64"; + public const string Password = "password"; +} +#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OrderedDictionary.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs rename to ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OrderedDictionary.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs new file mode 100644 index 00000000000..dd91ba5ab6f --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs @@ -0,0 +1,105 @@ +#if !NET10_0_OR_GREATER +using System.Text.Json.Serialization; +using Microsoft.OpenApi.Models; +using ServiceStack.Host; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack.AspNetCore.OpenApi; + +// Last OpenApi Filter to run +public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFilter +{ + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + //Console.WriteLine(GetType().Name + "..."); + if (metadata.SecurityDefinition != null) + { + swaggerDoc.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; + } + + if (metadata.ApiKeySecurityDefinition != null) + { + swaggerDoc.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; + } + + var operations = HostContext.Metadata.OperationsMap.Values.ToList(); + var dtos = new HashSet(); + foreach (var op in operations) + { + if (!IsDtoTypeOrEnum(op.RequestType)) + continue; + + AddReferencedTypes(dtos, op.RequestType, IsDtoTypeOrEnum, includeBaseTypes:HttpUtils.HasRequestBody(op.Method)); + } + + var orderedDtos = dtos.OrderBy(x => x.Name); + foreach (var type in orderedDtos) + { + //Console.WriteLine("Type: " + type.ToPrettyName() + " ..."); + var schema = metadata.CreateSchema(type, allTypes: dtos); + if (schema != null) + { + swaggerDoc.Components.Schemas[OpenApiMetadata.GetSchemaTypeName(type)] = schema; + } + } + } + + public static void AddReferencedTypes(HashSet to, Type? type, Func include, bool includeBaseTypes) + { + if (type == null || to.Contains(type) || !include(type)) + return; + + to.Add(type); + + var baseType = type.BaseType; + if (includeBaseTypes && baseType != null && include(baseType) && !to.Contains(baseType)) + { + AddReferencedTypes(to, baseType, include, includeBaseTypes); + + var genericArgs = type.IsGenericType + ? type.GetGenericArguments() + : Type.EmptyTypes; + + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + + foreach (var pi in type.GetSerializableProperties()) + { + // Skip Obsolete properties + if (SwaggerUtils.IgnoreProperty(pi)) + continue; + + if (to.Contains(pi.PropertyType)) + continue; + + if (include(pi.PropertyType)) + AddReferencedTypes(to, pi.PropertyType, include, includeBaseTypes); + + var genericArgs = pi.PropertyType.IsGenericType + ? pi.PropertyType.GetGenericArguments() + : Type.EmptyTypes; + + if (genericArgs.Length > 0) + { + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + else if (pi.PropertyType.IsArray) + { + var elType = pi.PropertyType.HasElementType ? pi.PropertyType.GetElementType() : null; + AddReferencedTypes(to, elType, include, includeBaseTypes); + } + } + } + + public static bool IsDtoTypeOrEnum(Type? type) => type != null + && (ServiceMetadata.IsDtoType(type) || type.IsEnum) + && !type.IsGenericTypeDefinition + && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs new file mode 100644 index 00000000000..58fc51de7bb --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs @@ -0,0 +1,80 @@ +#if !NET10_0_OR_GREATER +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Any; +using ServiceStack.AspNetCore.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack; + +public static class ServiceStackOpenApiExtensions +{ + public static void WithOpenApi(this ServiceStackOptions options) + { + if (!options.MapEndpointRouting) + throw new NotSupportedException("MapEndpointRouting must be enabled to use OpenApi"); + + options.RouteHandlerBuilders.Add((builder, operation, method, route) => + { + if (OpenApiMetadata.Instance.Ignore?.Invoke(operation) == true) + return; + builder.WithOpenApi(op => + { + OpenApiMetadata.Instance.AddOperation(op, operation, method, route); + return op; + }); + }); + } + + public static void AddSwagger(this ServiceStackServicesOptions options, Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + options.Services!.AddSingleton(OpenApiMetadata.Instance); + options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); + options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); + + options.Services!.ConfigurePlugin(feature => { + feature.AddPluginLink("/swagger/index.html", "Swagger UI"); + }); + } + + public static void AddServiceStackSwagger(this IServiceCollection services, Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + services.AddSingleton(OpenApiMetadata.Instance); + services.AddSingleton, ConfigureServiceStackSwagger>(); + services.AddSingleton, ConfigureServiceStackSwagger>(); + + services.ConfigurePlugin(feature => { + feature.AddPluginLink("/swagger/index.html", "Swagger UI"); + }); + } + + public static AuthenticationBuilder AddBasicAuth(this IServiceCollection services) + where TUser : IdentityUser, new() { + OpenApiMetadata.Instance.AddBasicAuth(); + return new AuthenticationBuilder(services).AddBasicAuth(); + } + + public static void AddJwtAuth(this IServiceCollection services) { + OpenApiMetadata.Instance.AddJwtBearer(); + } + + public static void AddBasicAuth(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.BasicAuthScheme.Scheme, OpenApiSecurity.BasicAuthScheme); + + public static void AddJwtAuth(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.JwtBearerScheme.Scheme, OpenApiSecurity.JwtBearerScheme); + + public static void AddApiKeys(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.ApiKeyScheme.Scheme, OpenApiSecurity.ApiKeyScheme); + + internal static List ToOpenApiEnums(this IEnumerable? enums) => + enums.Safe().Map(x => (IOpenApiAny)new OpenApiString(x)); +} +#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs new file mode 100644 index 00000000000..7815b2d554b --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs @@ -0,0 +1,18 @@ +#if !NET10_0_OR_GREATER +using System.Reflection; +using System.Text.Json.Serialization; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class SwaggerUtils +{ + public static Func IgnoreProperty { get; set; } = DefaultIgnoreProperty; + + public static bool DefaultIgnoreProperty(PropertyInfo pi) + { + var propAttrs = pi.AllAttributes(); + return propAttrs.Any(x => x is ObsoleteAttribute or JsonIgnoreAttribute + or Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute); + } +} +#endif \ No newline at end of file From 716c659cbb009cb3737f6b7f27bc2a438820f495 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 02:40:20 +0800 Subject: [PATCH 016/140] Add support for .NET10 and Microsoft.OpenApi --- .../OpenApiMetadata.cs | 394 ++++++++++-------- .../OpenApiType.cs | 17 +- .../OpenApiTypeFormat.cs | 4 +- .../ServiceStack.AspNetCore.OpenApi.csproj | 12 +- .../ServiceStackDocumentFilter.cs | 6 +- .../ServiceStackOpenApiExtensions.cs | 10 +- .../SwaggerUtils.cs | 4 +- 7 files changed, 250 insertions(+), 197 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs index 3a2f27bfb49..b3aeb42f9dc 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs @@ -1,14 +1,16 @@ -using System.Collections.Concurrent; +#if NET10_0_OR_GREATER +using System.Collections.Concurrent; using System.Net; using System.Reflection; using System.Runtime.Serialization; +using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using ServiceStack.Host; using ServiceStack.NativeTypes; using ServiceStack.Text; using ServiceStack.Web; +using OpenApiReference = Microsoft.OpenApi.BaseOpenApiReference; namespace ServiceStack.AspNetCore.OpenApi; @@ -17,15 +19,8 @@ public static class OpenApiSecurity public static OpenApiSecurityRequirement BasicAuth { get; } = new() { { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = BasicAuthenticationHandler.Scheme - } - }, - Array.Empty() + new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), + [] } }; public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() @@ -40,14 +35,7 @@ public static class OpenApiSecurity public static OpenApiSecurityRequirement JwtBearer { get; } = new() { { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = JwtBearerDefaults.AuthenticationScheme, - } - }, + new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), [] } }; @@ -60,18 +48,11 @@ public static class OpenApiSecurity BearerFormat = "JWT", Scheme = JwtBearerDefaults.AuthenticationScheme, }; - + public static OpenApiSecurityRequirement ApiKey { get; } = new() { { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "ApiKey", - } - }, + new OpenApiSecuritySchemeReference("ApiKey"), [] } }; @@ -208,7 +189,7 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); inPaths.Add(entry.Key); - OpenApiSchema? prop = entry.Value; + IOpenApiSchema prop = entry.Value; op.Parameters.Add(new OpenApiParameter { Name = propNameUsed, @@ -220,21 +201,22 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s }); } } - - var formType = new OpenApiMediaType - { - Schema = new(openApiType), - }; + + var formSchema = openApiType.CreateShallowCopy(); foreach (var propName in inPaths) { - formType.Schema.Properties.Remove(propName); + formSchema.Properties.Remove(propName); } - - foreach (var entry in formType.Schema.Properties) + + var formType = new OpenApiMediaType + { + Schema = formSchema, + }; + foreach (var entry in formSchema.Properties) { formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; } - op.RequestBody = new() + op.RequestBody = new OpenApiRequestBody { Content = { [MimeTypes.MultiPartFormData] = formType @@ -244,10 +226,7 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s { op.RequestBody.Content[MimeTypes.Json] = new OpenApiMediaType { - Schema = new() - { - Reference = ToOpenApiReference(operation.RequestType), - } + Schema = ToOpenApiSchemaReference(operation.RequestType) }; } SchemaFilter?.Invoke(op, openApiType); @@ -268,9 +247,9 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); if (userTags.Count > 0) { - // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag + // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag op.Tags.Clear(); - userTags.Each(tag => op.Tags.Add(new OpenApiTag { Name = tag })); + userTags.Each(tag => op.Tags.Add(new OpenApiTagReference(tag))); } OperationFilter?.Invoke(verb, op, operation); @@ -278,11 +257,8 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s return op; } - internal static OpenApiReference ToOpenApiReference(Type type) => - new() { - Type = ReferenceType.Schema, - Id = GetSchemaDefinitionRef(type), - }; + internal static IOpenApiSchema ToOpenApiSchemaReference(Type type) => + new OpenApiSchemaReference(GetSchemaDefinitionRef(type)); private static bool IsKeyValuePairType(Type type) { @@ -319,19 +295,27 @@ private static string GetSwaggerTypeFormat(Type type) return null; var listItemType = GetListElementType(schemaType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + return new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.Array, - Items = new() - { - Type = listItemType != null && IsSwaggerScalarType(listItemType) - ? GetSwaggerTypeName(listItemType) - : null, - Reference = listItemType != null && !IsSwaggerScalarType(listItemType) - ? ToOpenApiReference(listItemType) - : null, - }, + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items, }; } @@ -355,7 +339,7 @@ private static bool IsDictionaryType(Type type) return new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.Object, + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), AdditionalProperties = GetOpenApiProperty(valueType) }; @@ -371,10 +355,10 @@ private static bool IsDictionaryType(Type type) return new OpenApiSchema { - Type = OpenApiType.Object, + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), Title = GetSchemaTypeName(schemaType), Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary + Properties = new OrderedDictionary { ["Key"] = GetOpenApiProperty(keyType), ["Value"] = GetOpenApiProperty(valueType), @@ -522,24 +506,31 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, { In = paramLocation, Name = paramName, - Reference = ToOpenApiReference(propType), + Schema = CreateEnumSchema(propType), Required = paramLocation == ParameterLocation.Path, }; } if (IsSwaggerScalarType(propType)) { + var schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propType)), + Format = GetSwaggerTypeFormat(propType), + }; + if (enumValues != null && enumValues.Length > 0) + { + schema.Enum = enumValues.Select(x => (JsonNode)System.Text.Json.Nodes.JsonValue.Create(x)).ToList(); + } + if (!IsRequiredType(propType)) + { + ApplyNullable(schema, true); + } return new OpenApiParameter { In = paramLocation, Name = paramName, - Schema = new() - { - Type = GetSwaggerTypeName(propType), - Enum = enumValues?.Select(x => new OpenApiString(x)).Cast().ToList() ?? [], - Nullable = !IsRequiredType(propType), - Format = GetSwaggerTypeFormat(propType), - }, + Schema = schema, Required = paramLocation == ParameterLocation.Path, }; } @@ -550,9 +541,9 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, { In = paramLocation, Name = paramName, - Schema = new() + Schema = new OpenApiSchema { - Type = OpenApiType.String, + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), }, Required = paramLocation == ParameterLocation.Path, }; @@ -573,7 +564,7 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, return CreateArrayParameter(propType, paramName, paramLocation); } - OpenApiSchema openApiSchema; + IOpenApiSchema openApiSchema; if (IsInlineSchema(propType)) { @@ -581,9 +572,7 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, } else { - openApiSchema = new OpenApiSchema { - Reference = ToOpenApiReference(propType) - }; + openApiSchema = ToOpenApiSchemaReference(propType); } return new OpenApiParameter @@ -612,20 +601,28 @@ private OpenApiParameter CreateArrayParameter(Type listType, ParameterLocation? paramLocation) { var listItemType = GetListElementType(listType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + var parameter = new OpenApiParameter { In = paramLocation, - Schema = new() { - Type = OpenApiType.Array, - Items = new() - { - Type = listItemType != null && IsSwaggerScalarType(listItemType) - ? GetSwaggerTypeName(listItemType) - : null, - Reference = listItemType != null && !IsSwaggerScalarType(listItemType) - ? ToOpenApiReference(listItemType) - : null, - } + Schema = new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items }, Description = listType.GetDescription(), Name = paramName, @@ -638,48 +635,51 @@ private OpenApiParameter CreateArrayParameter(Type listType, private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); - private OpenApiSchema GetOpenApiProperty(PropertyInfo pi) + private IOpenApiSchema GetOpenApiProperty(PropertyInfo pi) { var schema = GetOpenApiProperty(pi.PropertyType); - schema.Nullable = pi.IsAssignableToNull(); + if (schema is OpenApiSchema openApiSchema && pi.IsAssignableToNull()) + { + openApiSchema.Type = openApiSchema.Type.HasValue + ? openApiSchema.Type.Value | JsonSchemaType.Null + : JsonSchemaType.Null; + } return schema; } - - private OpenApiSchema GetOpenApiProperty(Type propertyType) - { - var schemaProp = new OpenApiSchema { - Nullable = propertyType.IsNullableType(), - }; + private IOpenApiSchema GetOpenApiProperty(Type propertyType) + { + var isNullable = propertyType.IsNullableType(); propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; - + if (IsKeyValuePairType(propertyType)) { if (IsInlineSchema(propertyType)) { + var schemaProp = new OpenApiSchema(); var schema = CreateSchema(propertyType); if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); } else { - schemaProp.Reference = ToOpenApiReference(propertyType); + return ToOpenApiSchemaReference(propertyType); } } else if (IsListType(propertyType)) { - schemaProp.Type = OpenApiType.Array; + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array) + }; var listItemType = GetListElementType(propertyType); - if (listItemType == null) return schemaProp; + if (listItemType == null) return ApplyNullable(schemaProp, isNullable); if (IsSwaggerScalarType(listItemType)) { schemaProp.Items = new OpenApiSchema { - Type = GetSwaggerTypeName(listItemType), + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)), Format = GetSwaggerTypeFormat(listItemType), }; - if (IsRequiredType(listItemType)) - { - schemaProp.Nullable = false; - } } else if (IsInlineSchema(listItemType)) { @@ -688,39 +688,50 @@ private OpenApiSchema GetOpenApiProperty(Type propertyType) } else { - schemaProp.Items = new OpenApiSchema - { - Reference = ToOpenApiReference(listItemType) - }; + schemaProp.Items = ToOpenApiSchemaReference(listItemType); } + return ApplyNullable(schemaProp, isNullable); } else if (IsDictionaryType(propertyType)) { - schemaProp = CreateDictionarySchema(propertyType); + var schemaProp = CreateDictionarySchema(propertyType); + return ApplyNullable(schemaProp, isNullable); } else if (propertyType.IsEnum) { - schemaProp.Reference = ToOpenApiReference(propertyType); + return ToOpenApiSchemaReference(propertyType); } else if (IsSwaggerScalarType(propertyType)) { - schemaProp.Type = GetSwaggerTypeName(propertyType); - schemaProp.Format = GetSwaggerTypeFormat(propertyType); - schemaProp.Nullable = !IsRequiredType(propertyType); - //schemaProp.Required = IsRequiredType(propertyType) ? true : (bool?)null; + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propertyType)), + Format = GetSwaggerTypeFormat(propertyType), + }; + var nullable = isNullable || !IsRequiredType(propertyType); + return ApplyNullable(schemaProp, nullable); } else if (IsInlineSchema(propertyType)) { + var schemaProp = new OpenApiSchema(); var schema = CreateSchema(propertyType); if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); } else { //CreateSchema(propertyType, route, verb); - schemaProp.Reference = ToOpenApiReference(propertyType); + return ToOpenApiSchemaReference(propertyType); } + } - return schemaProp; + private static IOpenApiSchema ApplyNullable(OpenApiSchema schema, bool isNullable) + { + if (isNullable && schema.Type.HasValue) + { + schema.Type = schema.Type.Value | JsonSchemaType.Null; + } + return schema; } public static OpenApiSchema CreateEnumSchema(Type propertyType) @@ -728,18 +739,18 @@ public static OpenApiSchema CreateEnumSchema(Type propertyType) var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; if (!enumType.IsEnum) throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); - + var schema = new OpenApiSchema(); if (enumType.IsNumericType()) { var underlyingType = Enum.GetUnderlyingType(enumType); - schema.Type = GetSwaggerTypeName(underlyingType); + schema.Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(underlyingType)); schema.Format = GetSwaggerTypeFormat(underlyingType); schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); } else { - schema.Type = OpenApiType.String; + schema.Type = OpenApiType.ToJsonSchemaType(OpenApiType.String); schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); } return schema; @@ -759,32 +770,29 @@ private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; - schemaProp.Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); + schemaProp.Default = schema?.Default?.DeepClone(); schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; - schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; - schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; - schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; - schemaProp.Not = schema?.Not != null ? new(schema.Not) : null; + schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + schemaProp.Not = schema?.Not?.CreateShallowCopy(); schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; - schemaProp.Items = schema?.Items != null ? new(schema.Items) : null; + schemaProp.Items = schema?.Items?.CreateShallowCopy(); schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; - schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; - schemaProp.AdditionalProperties = new(schema?.AdditionalProperties); + schemaProp.AdditionalProperties = schema?.AdditionalProperties?.CreateShallowCopy(); schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; - schemaProp.Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); - schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; - schemaProp.Nullable = schema?.Nullable ?? schemaProp.Nullable; + schemaProp.Example = schema?.Example?.DeepClone(); + schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; - schemaProp.UnresolvedReference = schema?.UnresolvedReference ?? schemaProp.UnresolvedReference; - schemaProp.Reference = schema?.Reference != null ? new(schema.Reference) : null; } private bool IsInlineSchema(Type schemaType) @@ -821,17 +829,18 @@ private bool IsInlineSchema(Type schemaType) { schema = new OpenApiSchema { - Type = OpenApiType.Object, + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), Title = GetSchemaTypeName(schemaType), Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary() + Properties = new OrderedDictionary() }; parseProperties = schemaType.IsUserType(); } - + if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) { - schema.AllOf.Add(new OpenApiSchema { Reference = ToOpenApiReference(schemaType.BaseType) }); + schema.AllOf ??= new List(); + schema.AllOf.Add(ToOpenApiSchemaReference(schemaType.BaseType)); } } Schemas[schemaId] = schema; @@ -887,47 +896,50 @@ private bool IsInlineSchema(Type schemaType) var schemaProperty = GetOpenApiProperty(prop); var schemaPropertyName = GetSchemaPropertyName(prop); - schemaProperty.Description = prop.GetDescription() ?? apiDoc?.Description; + if (schemaProperty is OpenApiSchema openApiSchema) + { + openApiSchema.Description = prop.GetDescription() ?? apiDoc?.Description; - var propAttr = prop.FirstAttribute(); - var validateAttrs = prop.AllAttributes(); + var propAttr = prop.FirstAttribute(); + var validateAttrs = prop.AllAttributes(); - var isRequired = propAttr?.IsRequired == true - || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) - || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); - - if (propAttr != null) - { - if (propAttr.DataType != null) - schemaProperty.Type = propAttr.DataType; + var isRequired = propAttr?.IsRequired == true + || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) + || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); - if (propAttr.Format != null) - schemaProperty.Format = propAttr.Format; - } - - if (isRequired) - { - schema.Required.Add(schemaPropertyName); - } + if (propAttr != null) + { + if (propAttr.DataType != null) + openApiSchema.Type = OpenApiType.ToJsonSchemaType(propAttr.DataType); - var uploadTo = prop.FirstAttribute(); - if (uploadTo != null) - { - schemaProperty.Reference = null; - if (schemaProperty.Type != OpenApiType.Array) + if (propAttr.Format != null) + openApiSchema.Format = propAttr.Format; + } + + if (isRequired) { - schemaProperty.Type = "file"; + schema.Required ??= new HashSet(); + schema.Required.Add(schemaPropertyName); } - schemaProperty.Items = new OpenApiSchema + + var uploadTo = prop.FirstAttribute(); + if (uploadTo != null) { - Type = OpenApiType.String, - Format = OpenApiTypeFormat.Binary, - }; - } + if (openApiSchema.Type != OpenApiType.ToJsonSchemaType(OpenApiType.Array)) + { + openApiSchema.Type = JsonSchemaType.String; // "file" type doesn't exist in JsonSchemaType + } + openApiSchema.Items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + Format = OpenApiTypeFormat.Binary, + }; + } - schemaProperty.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); + openApiSchema.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); - SchemaPropertyFilter?.Invoke(schemaProperty); + SchemaPropertyFilter?.Invoke(openApiSchema); + } schema.Properties[schemaPropertyName] = schemaProperty; } } @@ -972,8 +984,8 @@ private static List GetNumericValues(Type propertyType, Type underlyingT } } - return new OpenApiSchema { - Type = OpenApiType.Object, + return new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), }; } @@ -985,19 +997,37 @@ private static List GetNumericValues(Type propertyType, Type underlyingT return null; } - var schema = CreateDictionarySchema(schemaType) + OpenApiSchema? schema = CreateDictionarySchema(schemaType) ?? GetKeyValuePairSchema(schemaType) - ?? GetListSchema(schemaType) - ?? (IsSwaggerScalarType(schemaType) - ? new OpenApiSchema + ?? GetListSchema(schemaType); + + if (schema == null) + { + if (IsSwaggerScalarType(schemaType)) + { + schema = new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = GetSwaggerTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(schemaType)), Format = GetSwaggerTypeFormat(schemaType) - } - : IsInlineSchema(schemaType) - ? CreateSchema(schemaType) - : new OpenApiSchema { Reference = ToOpenApiReference(schemaType) }); + }; + } + else if (IsInlineSchema(schemaType)) + { + schema = CreateSchema(schemaType); + } + else + { + // For references, we need to return a schema that references the type + // In v3.0, we can't use OpenApiSchema with Reference property + // Instead, we should use OpenApiSchemaReference, but since the return type is OpenApiSchema?, + // we'll create a schema with AllOf containing the reference + schema = new OpenApiSchema + { + AllOf = new List { ToOpenApiSchemaReference(schemaType) } + }; + } + } schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; @@ -1053,5 +1083,7 @@ private OrderedDictionary GetMethodResponseCodes(IRestP return responses; } + +} -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs index 96ce7a26361..d6a4647fa98 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs @@ -1,3 +1,6 @@ +#if NET10_0_OR_GREATER +using Microsoft.OpenApi; + namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiType @@ -8,4 +11,16 @@ public static class OpenApiType public const string Integer = "integer"; public const string String = "string"; public const string Object = "object"; -} \ No newline at end of file + + public static JsonSchemaType ToJsonSchemaType(string type) => type switch + { + Array => JsonSchemaType.Array, + Boolean => JsonSchemaType.Boolean, + Number => JsonSchemaType.Number, + Integer => JsonSchemaType.Integer, + String => JsonSchemaType.String, + Object => JsonSchemaType.Object, + _ => JsonSchemaType.String + }; +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs index d0909d5d926..16b3d36e425 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs @@ -1,3 +1,4 @@ +#if NET10_0_OR_GREATER namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiTypeFormat @@ -12,4 +13,5 @@ public static class OpenApiTypeFormat public const string Int = "int32"; public const string Long = "int64"; public const string Password = "password"; -} \ No newline at end of file +} +#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj index 2d70164c512..2a7766ec5b0 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj @@ -1,28 +1,26 @@ - net8.0 + net8.0;net10.0 enable enable - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NET8_0_OR_GREATER - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + $(DefineConstants);NET8_0_OR_GREATER;NET10_0_OR_GREATER - - - - + + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs index 350dbd254b0..13c5509838a 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs @@ -1,5 +1,6 @@ +#if NET10_0_OR_GREATER using System.Text.Json.Serialization; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using ServiceStack.Host; using Swashbuckle.AspNetCore.SwaggerGen; @@ -100,4 +101,5 @@ public static bool IsDtoTypeOrEnum(Type? type) => type != null && (ServiceMetadata.IsDtoType(type) || type.IsEnum) && !type.IsGenericTypeDefinition && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs index 4e995dcdf37..2133d9be49c 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs @@ -1,9 +1,10 @@ +#if NET10_0_OR_GREATER +using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Any; using ServiceStack.AspNetCore.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; @@ -73,6 +74,7 @@ public static void AddJwtAuth(this SwaggerGenOptions options) => public static void AddApiKeys(this SwaggerGenOptions options) => options.AddSecurityDefinition(OpenApiSecurity.ApiKeyScheme.Scheme, OpenApiSecurity.ApiKeyScheme); - internal static List ToOpenApiEnums(this IEnumerable? enums) => - enums.Safe().Map(x => (IOpenApiAny)new OpenApiString(x)); -} \ No newline at end of file + internal static List ToOpenApiEnums(this IEnumerable? enums) => + enums.Safe().Map(x => (JsonNode)JsonValue.Create(x)); +} +#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs index cc16ed63f56..eab12c15756 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs @@ -1,3 +1,4 @@ +#if NET10_0_OR_GREATER using System.Reflection; using System.Text.Json.Serialization; @@ -13,4 +14,5 @@ public static bool DefaultIgnoreProperty(PropertyInfo pi) return propAttrs.Any(x => x is ObsoleteAttribute or JsonIgnoreAttribute or Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute); } -} \ No newline at end of file +} +#endif \ No newline at end of file From 772ebf428b3854ec63cff1f3c25bc23f58dccd51 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 09:58:06 +0800 Subject: [PATCH 017/140] Revert ServiceStack.AspNetCore.OpenApi back to only supporting Microsoft.OpenApi v1 + add .net10 fx support --- .../ConfigureServiceStackSwagger.cs | 2 - .../{net8 => }/IOrderedDictionary.cs | 0 .../OpenApiMetadata.cs | 392 +++--- .../OpenApiType.cs | 15 - .../OpenApiTypeFormat.cs | 2 - .../{net8 => }/OrderedDictionary.cs | 0 .../ServiceStack.AspNetCore.OpenApi.csproj | 8 +- .../ServiceStackDocumentFilter.cs | 4 +- .../ServiceStackOpenApiExtensions.cs | 8 +- .../SwaggerUtils.cs | 2 - .../net8/ConfigureServiceStackSwagger.cs | 37 - .../net8/OpenApiMetadata.cs | 1060 ----------------- .../net8/OpenApiType.cs | 13 - .../net8/OpenApiTypeFormat.cs | 17 - .../net8/ServiceStackDocumentFilter.cs | 105 -- .../net8/ServiceStackOpenApiExtensions.cs | 80 -- .../net8/SwaggerUtils.cs | 18 - 17 files changed, 185 insertions(+), 1578 deletions(-) rename ServiceStack/src/ServiceStack.AspNetCore.OpenApi/{net8 => }/IOrderedDictionary.cs (100%) rename ServiceStack/src/ServiceStack.AspNetCore.OpenApi/{net8 => }/OrderedDictionary.cs (100%) delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs delete mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs index 2058a55d858..cb9c2a5cfec 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs @@ -1,4 +1,3 @@ -#if NET10_0_OR_GREATER using Microsoft.Extensions.Options; using ServiceStack.AspNetCore.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; @@ -34,4 +33,3 @@ public void Configure(ServiceStackOptions options) options.WithOpenApi(); } } -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/IOrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/IOrderedDictionary.cs rename to ServiceStack/src/ServiceStack.AspNetCore.OpenApi/IOrderedDictionary.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs index b3aeb42f9dc..e6d221018f5 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiMetadata.cs @@ -1,16 +1,14 @@ -#if NET10_0_OR_GREATER -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Net; using System.Reflection; using System.Runtime.Serialization; -using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.OpenApi; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; using ServiceStack.Host; using ServiceStack.NativeTypes; using ServiceStack.Text; using ServiceStack.Web; -using OpenApiReference = Microsoft.OpenApi.BaseOpenApiReference; namespace ServiceStack.AspNetCore.OpenApi; @@ -19,8 +17,15 @@ public static class OpenApiSecurity public static OpenApiSecurityRequirement BasicAuth { get; } = new() { { - new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), - [] + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = BasicAuthenticationHandler.Scheme + } + }, + Array.Empty() } }; public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() @@ -35,7 +40,14 @@ public static class OpenApiSecurity public static OpenApiSecurityRequirement JwtBearer { get; } = new() { { - new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme, + } + }, [] } }; @@ -48,11 +60,18 @@ public static class OpenApiSecurity BearerFormat = "JWT", Scheme = JwtBearerDefaults.AuthenticationScheme, }; - + public static OpenApiSecurityRequirement ApiKey { get; } = new() { { - new OpenApiSecuritySchemeReference("ApiKey"), + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "ApiKey", + } + }, [] } }; @@ -189,7 +208,7 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); inPaths.Add(entry.Key); - IOpenApiSchema prop = entry.Value; + OpenApiSchema? prop = entry.Value; op.Parameters.Add(new OpenApiParameter { Name = propNameUsed, @@ -201,22 +220,21 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s }); } } - - var formSchema = openApiType.CreateShallowCopy(); - foreach (var propName in inPaths) - { - formSchema.Properties.Remove(propName); - } - + var formType = new OpenApiMediaType { - Schema = formSchema, + Schema = new(openApiType), }; - foreach (var entry in formSchema.Properties) + foreach (var propName in inPaths) + { + formType.Schema.Properties.Remove(propName); + } + + foreach (var entry in formType.Schema.Properties) { formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; } - op.RequestBody = new OpenApiRequestBody + op.RequestBody = new() { Content = { [MimeTypes.MultiPartFormData] = formType @@ -226,7 +244,10 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s { op.RequestBody.Content[MimeTypes.Json] = new OpenApiMediaType { - Schema = ToOpenApiSchemaReference(operation.RequestType) + Schema = new() + { + Reference = ToOpenApiReference(operation.RequestType), + } }; } SchemaFilter?.Invoke(op, openApiType); @@ -247,9 +268,9 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); if (userTags.Count > 0) { - // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag + // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag op.Tags.Clear(); - userTags.Each(tag => op.Tags.Add(new OpenApiTagReference(tag))); + userTags.Each(tag => op.Tags.Add(new OpenApiTag { Name = tag })); } OperationFilter?.Invoke(verb, op, operation); @@ -257,8 +278,11 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s return op; } - internal static IOpenApiSchema ToOpenApiSchemaReference(Type type) => - new OpenApiSchemaReference(GetSchemaDefinitionRef(type)); + internal static OpenApiReference ToOpenApiReference(Type type) => + new() { + Type = ReferenceType.Schema, + Id = GetSchemaDefinitionRef(type), + }; private static bool IsKeyValuePairType(Type type) { @@ -295,27 +319,19 @@ private static string GetSwaggerTypeFormat(Type type) return null; var listItemType = GetListElementType(schemaType); - IOpenApiSchema? items = null; - if (listItemType != null) - { - if (IsSwaggerScalarType(listItemType)) - { - items = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) - }; - } - else - { - items = ToOpenApiSchemaReference(listItemType); - } - } - return new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), - Items = items, + Type = OpenApiType.Array, + Items = new() + { + Type = listItemType != null && IsSwaggerScalarType(listItemType) + ? GetSwaggerTypeName(listItemType) + : null, + Reference = listItemType != null && !IsSwaggerScalarType(listItemType) + ? ToOpenApiReference(listItemType) + : null, + }, }; } @@ -339,7 +355,7 @@ private static bool IsDictionaryType(Type type) return new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Type = OpenApiType.Object, Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), AdditionalProperties = GetOpenApiProperty(valueType) }; @@ -355,10 +371,10 @@ private static bool IsDictionaryType(Type type) return new OpenApiSchema { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Type = OpenApiType.Object, Title = GetSchemaTypeName(schemaType), Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary + Properties = new OrderedDictionary { ["Key"] = GetOpenApiProperty(keyType), ["Value"] = GetOpenApiProperty(valueType), @@ -506,31 +522,24 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, { In = paramLocation, Name = paramName, - Schema = CreateEnumSchema(propType), + Reference = ToOpenApiReference(propType), Required = paramLocation == ParameterLocation.Path, }; } if (IsSwaggerScalarType(propType)) { - var schema = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propType)), - Format = GetSwaggerTypeFormat(propType), - }; - if (enumValues != null && enumValues.Length > 0) - { - schema.Enum = enumValues.Select(x => (JsonNode)System.Text.Json.Nodes.JsonValue.Create(x)).ToList(); - } - if (!IsRequiredType(propType)) - { - ApplyNullable(schema, true); - } return new OpenApiParameter { In = paramLocation, Name = paramName, - Schema = schema, + Schema = new() + { + Type = GetSwaggerTypeName(propType), + Enum = enumValues?.Select(x => new OpenApiString(x)).Cast().ToList() ?? [], + Nullable = !IsRequiredType(propType), + Format = GetSwaggerTypeFormat(propType), + }, Required = paramLocation == ParameterLocation.Path, }; } @@ -541,9 +550,9 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, { In = paramLocation, Name = paramName, - Schema = new OpenApiSchema + Schema = new() { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + Type = OpenApiType.String, }, Required = paramLocation == ParameterLocation.Path, }; @@ -564,7 +573,7 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, return CreateArrayParameter(propType, paramName, paramLocation); } - IOpenApiSchema openApiSchema; + OpenApiSchema openApiSchema; if (IsInlineSchema(propType)) { @@ -572,7 +581,9 @@ private OpenApiParameter CreateParameter(Type propType, string paramName, } else { - openApiSchema = ToOpenApiSchemaReference(propType); + openApiSchema = new OpenApiSchema { + Reference = ToOpenApiReference(propType) + }; } return new OpenApiParameter @@ -601,28 +612,20 @@ private OpenApiParameter CreateArrayParameter(Type listType, ParameterLocation? paramLocation) { var listItemType = GetListElementType(listType); - IOpenApiSchema? items = null; - if (listItemType != null) - { - if (IsSwaggerScalarType(listItemType)) - { - items = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) - }; - } - else - { - items = ToOpenApiSchemaReference(listItemType); - } - } - var parameter = new OpenApiParameter { In = paramLocation, - Schema = new OpenApiSchema { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), - Items = items + Schema = new() { + Type = OpenApiType.Array, + Items = new() + { + Type = listItemType != null && IsSwaggerScalarType(listItemType) + ? GetSwaggerTypeName(listItemType) + : null, + Reference = listItemType != null && !IsSwaggerScalarType(listItemType) + ? ToOpenApiReference(listItemType) + : null, + } }, Description = listType.GetDescription(), Name = paramName, @@ -635,51 +638,48 @@ private OpenApiParameter CreateArrayParameter(Type listType, private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); - private IOpenApiSchema GetOpenApiProperty(PropertyInfo pi) + private OpenApiSchema GetOpenApiProperty(PropertyInfo pi) { var schema = GetOpenApiProperty(pi.PropertyType); - if (schema is OpenApiSchema openApiSchema && pi.IsAssignableToNull()) - { - openApiSchema.Type = openApiSchema.Type.HasValue - ? openApiSchema.Type.Value | JsonSchemaType.Null - : JsonSchemaType.Null; - } + schema.Nullable = pi.IsAssignableToNull(); return schema; } - - private IOpenApiSchema GetOpenApiProperty(Type propertyType) + + private OpenApiSchema GetOpenApiProperty(Type propertyType) { - var isNullable = propertyType.IsNullableType(); - propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + var schemaProp = new OpenApiSchema { + Nullable = propertyType.IsNullableType(), + }; + propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (IsKeyValuePairType(propertyType)) { if (IsInlineSchema(propertyType)) { - var schemaProp = new OpenApiSchema(); var schema = CreateSchema(propertyType); if (schema != null) InlineSchema(schema, schemaProp); - return ApplyNullable(schemaProp, isNullable); } else { - return ToOpenApiSchemaReference(propertyType); + schemaProp.Reference = ToOpenApiReference(propertyType); } } else if (IsListType(propertyType)) { - var schemaProp = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array) - }; + schemaProp.Type = OpenApiType.Array; var listItemType = GetListElementType(propertyType); - if (listItemType == null) return ApplyNullable(schemaProp, isNullable); + if (listItemType == null) return schemaProp; if (IsSwaggerScalarType(listItemType)) { schemaProp.Items = new OpenApiSchema { - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)), + Type = GetSwaggerTypeName(listItemType), Format = GetSwaggerTypeFormat(listItemType), }; + if (IsRequiredType(listItemType)) + { + schemaProp.Nullable = false; + } } else if (IsInlineSchema(listItemType)) { @@ -688,50 +688,39 @@ private IOpenApiSchema GetOpenApiProperty(Type propertyType) } else { - schemaProp.Items = ToOpenApiSchemaReference(listItemType); + schemaProp.Items = new OpenApiSchema + { + Reference = ToOpenApiReference(listItemType) + }; } - return ApplyNullable(schemaProp, isNullable); } else if (IsDictionaryType(propertyType)) { - var schemaProp = CreateDictionarySchema(propertyType); - return ApplyNullable(schemaProp, isNullable); + schemaProp = CreateDictionarySchema(propertyType); } else if (propertyType.IsEnum) { - return ToOpenApiSchemaReference(propertyType); + schemaProp.Reference = ToOpenApiReference(propertyType); } else if (IsSwaggerScalarType(propertyType)) { - var schemaProp = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propertyType)), - Format = GetSwaggerTypeFormat(propertyType), - }; - var nullable = isNullable || !IsRequiredType(propertyType); - return ApplyNullable(schemaProp, nullable); + schemaProp.Type = GetSwaggerTypeName(propertyType); + schemaProp.Format = GetSwaggerTypeFormat(propertyType); + schemaProp.Nullable = !IsRequiredType(propertyType); + //schemaProp.Required = IsRequiredType(propertyType) ? true : (bool?)null; } else if (IsInlineSchema(propertyType)) { - var schemaProp = new OpenApiSchema(); var schema = CreateSchema(propertyType); if (schema != null) InlineSchema(schema, schemaProp); - return ApplyNullable(schemaProp, isNullable); } else { //CreateSchema(propertyType, route, verb); - return ToOpenApiSchemaReference(propertyType); + schemaProp.Reference = ToOpenApiReference(propertyType); } - } - private static IOpenApiSchema ApplyNullable(OpenApiSchema schema, bool isNullable) - { - if (isNullable && schema.Type.HasValue) - { - schema.Type = schema.Type.Value | JsonSchemaType.Null; - } - return schema; + return schemaProp; } public static OpenApiSchema CreateEnumSchema(Type propertyType) @@ -739,18 +728,18 @@ public static OpenApiSchema CreateEnumSchema(Type propertyType) var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; if (!enumType.IsEnum) throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); - + var schema = new OpenApiSchema(); if (enumType.IsNumericType()) { var underlyingType = Enum.GetUnderlyingType(enumType); - schema.Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(underlyingType)); + schema.Type = GetSwaggerTypeName(underlyingType); schema.Format = GetSwaggerTypeFormat(underlyingType); schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); } else { - schema.Type = OpenApiType.ToJsonSchemaType(OpenApiType.String); + schema.Type = OpenApiType.String; schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); } return schema; @@ -770,29 +759,32 @@ private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; - schemaProp.Default = schema?.Default?.DeepClone(); + schemaProp.Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; - schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; - schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; - schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; - schemaProp.Not = schema?.Not?.CreateShallowCopy(); + schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + schemaProp.Not = schema?.Not != null ? new(schema.Not) : null; schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; - schemaProp.Items = schema?.Items?.CreateShallowCopy(); + schemaProp.Items = schema?.Items != null ? new(schema.Items) : null; schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; - schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; - schemaProp.AdditionalProperties = schema?.AdditionalProperties?.CreateShallowCopy(); + schemaProp.AdditionalProperties = new(schema?.AdditionalProperties); schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; - schemaProp.Example = schema?.Example?.DeepClone(); - schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; + schemaProp.Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); + schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; + schemaProp.Nullable = schema?.Nullable ?? schemaProp.Nullable; schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; + schemaProp.UnresolvedReference = schema?.UnresolvedReference ?? schemaProp.UnresolvedReference; + schemaProp.Reference = schema?.Reference != null ? new(schema.Reference) : null; } private bool IsInlineSchema(Type schemaType) @@ -829,18 +821,17 @@ private bool IsInlineSchema(Type schemaType) { schema = new OpenApiSchema { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Type = OpenApiType.Object, Title = GetSchemaTypeName(schemaType), Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary() + Properties = new OrderedDictionary() }; parseProperties = schemaType.IsUserType(); } - + if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) { - schema.AllOf ??= new List(); - schema.AllOf.Add(ToOpenApiSchemaReference(schemaType.BaseType)); + schema.AllOf.Add(new OpenApiSchema { Reference = ToOpenApiReference(schemaType.BaseType) }); } } Schemas[schemaId] = schema; @@ -896,50 +887,47 @@ private bool IsInlineSchema(Type schemaType) var schemaProperty = GetOpenApiProperty(prop); var schemaPropertyName = GetSchemaPropertyName(prop); - if (schemaProperty is OpenApiSchema openApiSchema) - { - openApiSchema.Description = prop.GetDescription() ?? apiDoc?.Description; - - var propAttr = prop.FirstAttribute(); - var validateAttrs = prop.AllAttributes(); + schemaProperty.Description = prop.GetDescription() ?? apiDoc?.Description; - var isRequired = propAttr?.IsRequired == true - || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) - || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); + var propAttr = prop.FirstAttribute(); + var validateAttrs = prop.AllAttributes(); - if (propAttr != null) - { - if (propAttr.DataType != null) - openApiSchema.Type = OpenApiType.ToJsonSchemaType(propAttr.DataType); + var isRequired = propAttr?.IsRequired == true + || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) + || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); + + if (propAttr != null) + { + if (propAttr.DataType != null) + schemaProperty.Type = propAttr.DataType; - if (propAttr.Format != null) - openApiSchema.Format = propAttr.Format; - } + if (propAttr.Format != null) + schemaProperty.Format = propAttr.Format; + } + + if (isRequired) + { + schema.Required.Add(schemaPropertyName); + } - if (isRequired) + var uploadTo = prop.FirstAttribute(); + if (uploadTo != null) + { + schemaProperty.Reference = null; + if (schemaProperty.Type != OpenApiType.Array) { - schema.Required ??= new HashSet(); - schema.Required.Add(schemaPropertyName); + schemaProperty.Type = "file"; } - - var uploadTo = prop.FirstAttribute(); - if (uploadTo != null) + schemaProperty.Items = new OpenApiSchema { - if (openApiSchema.Type != OpenApiType.ToJsonSchemaType(OpenApiType.Array)) - { - openApiSchema.Type = JsonSchemaType.String; // "file" type doesn't exist in JsonSchemaType - } - openApiSchema.Items = new OpenApiSchema - { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), - Format = OpenApiTypeFormat.Binary, - }; - } + Type = OpenApiType.String, + Format = OpenApiTypeFormat.Binary, + }; + } - openApiSchema.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); + schemaProperty.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); - SchemaPropertyFilter?.Invoke(openApiSchema); - } + SchemaPropertyFilter?.Invoke(schemaProperty); schema.Properties[schemaPropertyName] = schemaProperty; } } @@ -984,8 +972,8 @@ private static List GetNumericValues(Type propertyType, Type underlyingT } } - return new OpenApiSchema { - Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + return new OpenApiSchema { + Type = OpenApiType.Object, }; } @@ -997,37 +985,19 @@ private static List GetNumericValues(Type propertyType, Type underlyingT return null; } - OpenApiSchema? schema = CreateDictionarySchema(schemaType) + var schema = CreateDictionarySchema(schemaType) ?? GetKeyValuePairSchema(schemaType) - ?? GetListSchema(schemaType); - - if (schema == null) - { - if (IsSwaggerScalarType(schemaType)) - { - schema = new OpenApiSchema + ?? GetListSchema(schemaType) + ?? (IsSwaggerScalarType(schemaType) + ? new OpenApiSchema { Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(schemaType)), + Type = GetSwaggerTypeName(schemaType), Format = GetSwaggerTypeFormat(schemaType) - }; - } - else if (IsInlineSchema(schemaType)) - { - schema = CreateSchema(schemaType); - } - else - { - // For references, we need to return a schema that references the type - // In v3.0, we can't use OpenApiSchema with Reference property - // Instead, we should use OpenApiSchemaReference, but since the return type is OpenApiSchema?, - // we'll create a schema with AllOf containing the reference - schema = new OpenApiSchema - { - AllOf = new List { ToOpenApiSchemaReference(schemaType) } - }; - } - } + } + : IsInlineSchema(schemaType) + ? CreateSchema(schemaType) + : new OpenApiSchema { Reference = ToOpenApiReference(schemaType) }); schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; @@ -1085,5 +1055,3 @@ private OrderedDictionary GetMethodResponseCodes(IRestP } } - -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs index d6a4647fa98..f323d1245f0 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiType.cs @@ -1,6 +1,3 @@ -#if NET10_0_OR_GREATER -using Microsoft.OpenApi; - namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiType @@ -11,16 +8,4 @@ public static class OpenApiType public const string Integer = "integer"; public const string String = "string"; public const string Object = "object"; - - public static JsonSchemaType ToJsonSchemaType(string type) => type switch - { - Array => JsonSchemaType.Array, - Boolean => JsonSchemaType.Boolean, - Number => JsonSchemaType.Number, - Integer => JsonSchemaType.Integer, - String => JsonSchemaType.String, - Object => JsonSchemaType.Object, - _ => JsonSchemaType.String - }; } -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs index 16b3d36e425..3ebd7db4e97 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OpenApiTypeFormat.cs @@ -1,4 +1,3 @@ -#if NET10_0_OR_GREATER namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiTypeFormat @@ -14,4 +13,3 @@ public static class OpenApiTypeFormat public const string Long = "int64"; public const string Password = "password"; } -#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OrderedDictionary.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OrderedDictionary.cs rename to ServiceStack/src/ServiceStack.AspNetCore.OpenApi/OrderedDictionary.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj index 2a7766ec5b0..b1ff0d8a555 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStack.AspNetCore.OpenApi.csproj @@ -13,18 +13,12 @@ $(DefineConstants);NET8_0_OR_GREATER;NET10_0_OR_GREATER - + - - - - - - diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs index 13c5509838a..54897407164 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs @@ -1,6 +1,5 @@ -#if NET10_0_OR_GREATER using System.Text.Json.Serialization; -using Microsoft.OpenApi; +using Microsoft.OpenApi.Models; using ServiceStack.Host; using Swashbuckle.AspNetCore.SwaggerGen; @@ -102,4 +101,3 @@ public static bool IsDtoTypeOrEnum(Type? type) => type != null && !type.IsGenericTypeDefinition && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); } -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs index 2133d9be49c..68295e34b2c 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs @@ -1,10 +1,9 @@ -#if NET10_0_OR_GREATER -using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Any; using ServiceStack.AspNetCore.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; @@ -74,7 +73,6 @@ public static void AddJwtAuth(this SwaggerGenOptions options) => public static void AddApiKeys(this SwaggerGenOptions options) => options.AddSecurityDefinition(OpenApiSecurity.ApiKeyScheme.Scheme, OpenApiSecurity.ApiKeyScheme); - internal static List ToOpenApiEnums(this IEnumerable? enums) => - enums.Safe().Map(x => (JsonNode)JsonValue.Create(x)); + internal static List ToOpenApiEnums(this IEnumerable? enums) => + enums.Safe().Map(x => (IOpenApiAny)new OpenApiString(x)); } -#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs index eab12c15756..817d24a0454 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/SwaggerUtils.cs @@ -1,4 +1,3 @@ -#if NET10_0_OR_GREATER using System.Reflection; using System.Text.Json.Serialization; @@ -15,4 +14,3 @@ public static bool DefaultIgnoreProperty(PropertyInfo pi) or Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute); } } -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs deleted file mode 100644 index c96742b7a86..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ConfigureServiceStackSwagger.cs +++ /dev/null @@ -1,37 +0,0 @@ -#if !NET10_0_OR_GREATER -using Microsoft.Extensions.Options; -using ServiceStack.AspNetCore.OpenApi; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace ServiceStack; - -public class ConfigureServiceStackSwagger(OpenApiMetadata metadata) : - IConfigureOptions, - IConfigureOptions -{ - public void Configure(SwaggerGenOptions options) - { - foreach (var filterType in metadata.DocumentFilterTypes) - { - options.DocumentFilterDescriptors.Add(new FilterDescriptor - { - Type = filterType, - Arguments = [], - }); - } - foreach (var filterType in metadata.SchemaFilterTypes) - { - options.SchemaFilterDescriptors.Add(new FilterDescriptor - { - Type = filterType, - Arguments = [], - }); - } - } - - public void Configure(ServiceStackOptions options) - { - options.WithOpenApi(); - } -} -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs deleted file mode 100644 index e0dc3d3c54a..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiMetadata.cs +++ /dev/null @@ -1,1060 +0,0 @@ -#if !NET10_0_OR_GREATER -using System.Collections.Concurrent; -using System.Net; -using System.Reflection; -using System.Runtime.Serialization; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using ServiceStack.Host; -using ServiceStack.NativeTypes; -using ServiceStack.Text; -using ServiceStack.Web; - -namespace ServiceStack.AspNetCore.OpenApi; - -public static class OpenApiSecurity -{ - public static OpenApiSecurityRequirement BasicAuth { get; } = new() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = BasicAuthenticationHandler.Scheme - } - }, - Array.Empty() - } - }; - public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() - { - In = ParameterLocation.Header, - Name = "Authorization", - Description = "HTTP Basic access authentication", - Type = SecuritySchemeType.Http, - Scheme = BasicAuthenticationHandler.Scheme, - }; - - public static OpenApiSecurityRequirement JwtBearer { get; } = new() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = JwtBearerDefaults.AuthenticationScheme, - } - }, - [] - } - }; - public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() - { - In = ParameterLocation.Header, - Name = "Authorization", - Description = "JWT Bearer Authorization", - Type = SecuritySchemeType.Http, - BearerFormat = "JWT", - Scheme = JwtBearerDefaults.AuthenticationScheme, - }; - - public static OpenApiSecurityRequirement ApiKey { get; } = new() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "ApiKey", - } - }, - [] - } - }; - public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() - { - Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", - Name = "Authorization", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = "ApiKey" - }; -} - -public class OpenApiMetadata -{ - public static OpenApiMetadata Instance { get; } = new(); - - public List DocumentFilterTypes { get; set; } = [ - typeof(ServiceStackDocumentFilter), - ]; - public List SchemaFilterTypes { get; set; } = [ - ]; - - public Func? Ignore { get; set; } - - public Action? OperationFilter { get; set; } - public Action? SchemaFilter { get; set; } - public static Action? SchemaPropertyFilter { get; set; } - - private static readonly Dictionary ClrTypesToSwaggerScalarTypes = new() - { - {typeof(byte[]), OpenApiType.String}, - {typeof(sbyte[]), OpenApiType.String}, - {typeof(byte), OpenApiType.Integer}, - {typeof(sbyte), OpenApiType.Integer}, - {typeof(bool), OpenApiType.Boolean}, - {typeof(short), OpenApiType.Integer}, - {typeof(ushort), OpenApiType.Integer}, - {typeof(int), OpenApiType.Integer}, - {typeof(uint), OpenApiType.Integer}, - {typeof(long), OpenApiType.Integer}, - {typeof(ulong), OpenApiType.Integer}, - {typeof(float), OpenApiType.Number}, - {typeof(double), OpenApiType.Number}, - {typeof(decimal), OpenApiType.Number}, - {typeof(string), OpenApiType.String}, - {typeof(DateTime), OpenApiType.String}, - {typeof(DateTimeOffset), OpenApiType.String}, - }; - - private static readonly Dictionary ClrTypesToSwaggerScalarFormats = new() - { - {typeof(byte[]), OpenApiTypeFormat.Byte}, - {typeof(sbyte[]), OpenApiTypeFormat.Byte}, - {typeof(byte), OpenApiTypeFormat.Int}, - {typeof(sbyte), OpenApiTypeFormat.Int}, - {typeof(short), OpenApiTypeFormat.Int}, - {typeof(ushort), OpenApiTypeFormat.Int}, - {typeof(int), OpenApiTypeFormat.Int}, - {typeof(uint), OpenApiTypeFormat.Int}, - {typeof(long), OpenApiTypeFormat.Long}, - {typeof(ulong), OpenApiTypeFormat.Long}, - {typeof(float), OpenApiTypeFormat.Float}, - {typeof(double), OpenApiTypeFormat.Double}, - {typeof(decimal), OpenApiTypeFormat.Double}, - {typeof(DateTime), OpenApiTypeFormat.DateTime}, - {typeof(DateTimeOffset), OpenApiTypeFormat.DateTime}, - }; - - public ConcurrentDictionary Schemas { get; } = new(); - internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); - - public OpenApiSecurityScheme? SecurityDefinition { get; set; } - public OpenApiSecurityRequirement? SecurityRequirement { get; set; } - - public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } - public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } - - /// - /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI - /// - public HashSet ExcludeRequestTypes { get; set; } = []; - - public void AddBasicAuth() - { - SecurityDefinition = OpenApiSecurity.BasicAuthScheme; - SecurityRequirement = OpenApiSecurity.BasicAuth; - } - - public void AddJwtBearer() - { - SecurityDefinition = OpenApiSecurity.JwtBearerScheme; - SecurityRequirement = OpenApiSecurity.JwtBearer; - } - - public void AddApiKeys() - { - ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; - ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; - } - - public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) - { - if (ExcludeRequestTypes.Contains(operation.RequestType)) - return op; - //Console.WriteLine($"AddOperation {verb} {route} {operation.RequestType.Name}..."); - - // Response is handled by Endpoints Metadata - op.Summary = operation.RequestType.GetDescription(); - op.Description = operation.RequestType.FirstAttribute()?.Notes; - - var hasRequestBody = HttpUtils.HasRequestBody(verb); - if (!hasRequestBody) - { - var parameters = CreateParameters(operation.RequestType, route, verb); - op.Parameters.AddDistinctRange(parameters); - } - - var apiAttr = operation.RequestType.FirstAttribute(); - if (hasRequestBody) - { - var openApiType = CreateSchema(operation.RequestType, route, verb); - if (openApiType != null) - { - // Move path parameters from body - var inPaths = new List(); - foreach (var entry in openApiType.Properties) - { - var inPath = route.Contains("{" + entry.Key + "}", StringComparison.OrdinalIgnoreCase); - if (inPath) - { - var propNameUsed = route.Contains("{" + entry.Key + "}") - ? entry.Key - : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name - ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); - inPaths.Add(entry.Key); - OpenApiSchema? prop = entry.Value; - op.Parameters.Add(new OpenApiParameter - { - Name = propNameUsed, - In = ParameterLocation.Path, - Required = true, - Schema = prop, - Style = ParameterStyle.Simple, - Explode = true, - }); - } - } - - var formType = new OpenApiMediaType - { - Schema = new(openApiType), - }; - foreach (var propName in inPaths) - { - formType.Schema.Properties.Remove(propName); - } - - foreach (var entry in formType.Schema.Properties) - { - formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; - } - op.RequestBody = new() - { - Content = { - [MimeTypes.MultiPartFormData] = formType - } - }; - if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) - { - op.RequestBody.Content[MimeTypes.Json] = new OpenApiMediaType - { - Schema = new() - { - Reference = ToOpenApiReference(operation.RequestType), - } - }; - } - SchemaFilter?.Invoke(op, openApiType); - } - } - - if (operation.RequiresAuthentication) - { - if (SecurityRequirement != null) - op.Security.Add(SecurityRequirement); - } - if (operation.RequiresApiKey) - { - if (ApiKeySecurityDefinition != null) - op.Security.Add(ApiKeySecurityRequirement); - } - - var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); - if (userTags.Count > 0) - { - // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag - op.Tags.Clear(); - userTags.Each(tag => op.Tags.Add(new OpenApiTag { Name = tag })); - } - - OperationFilter?.Invoke(verb, op, operation); - - return op; - } - - internal static OpenApiReference ToOpenApiReference(Type type) => - new() { - Type = ReferenceType.Schema, - Id = GetSchemaDefinitionRef(type), - }; - - private static bool IsKeyValuePairType(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); - } - - private static bool IsSwaggerScalarType(Type type) - { - var lookupType = Nullable.GetUnderlyingType(type) ?? type; - - return ClrTypesToSwaggerScalarTypes.ContainsKey(lookupType) - || lookupType.IsEnum - || (lookupType.IsValueType && !IsKeyValuePairType(lookupType)); - } - - private static string GetSwaggerTypeName(Type type) - { - var lookupType = Nullable.GetUnderlyingType(type) ?? type; - - return ClrTypesToSwaggerScalarTypes.TryGetValue(lookupType, out var scalarType) - ? scalarType - : GetSchemaTypeName(lookupType); - } - - private static string GetSwaggerTypeFormat(Type type) - { - var lookupType = Nullable.GetUnderlyingType(type) ?? type; - return ClrTypesToSwaggerScalarFormats.GetValueOrDefault(lookupType); - } - - private OpenApiSchema? GetListSchema(Type schemaType) - { - if (!IsListType(schemaType)) - return null; - - var listItemType = GetListElementType(schemaType); - return new OpenApiSchema - { - Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.Array, - Items = new() - { - Type = listItemType != null && IsSwaggerScalarType(listItemType) - ? GetSwaggerTypeName(listItemType) - : null, - Reference = listItemType != null && !IsSwaggerScalarType(listItemType) - ? ToOpenApiReference(listItemType) - : null, - }, - }; - } - - private static bool IsDictionaryType(Type type) - { - if (!type.IsGenericType) return false; - var genericType = type.GetGenericTypeDefinition(); - return genericType == typeof(Dictionary<,>) - || genericType == typeof(IDictionary<,>) - || genericType == typeof(IReadOnlyDictionary<,>) - || genericType == typeof(SortedDictionary<,>); - } - - private OpenApiSchema? CreateDictionarySchema(Type schemaType) - { - if (!IsDictionaryType(schemaType)) - return null; - - var valueType = schemaType.GetGenericArguments()[1]; - - return new OpenApiSchema - { - Title = GetSchemaTypeName(schemaType), - Type = OpenApiType.Object, - Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - AdditionalProperties = GetOpenApiProperty(valueType) - }; - } - - private OpenApiSchema? GetKeyValuePairSchema(Type schemaType) - { - if (!IsKeyValuePairType(schemaType)) - return null; - - var keyType = schemaType.GetGenericArguments()[0]; - var valueType = schemaType.GetGenericArguments()[1]; - - return new OpenApiSchema - { - Type = OpenApiType.Object, - Title = GetSchemaTypeName(schemaType), - Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary - { - ["Key"] = GetOpenApiProperty(keyType), - ["Value"] = GetOpenApiProperty(valueType), - } - }; - } - - private static bool IsRequiredType(Type type) - { - return !type.IsNullableType() && type != typeof(string); - } - - public static string GetSchemaTypeName(Type schemaType) - { - if (schemaType.IsEnum) - return schemaType.Name; - - if ((!IsKeyValuePairType(schemaType) && schemaType.IsValueType) || schemaType.IsNullableType()) - return OpenApiType.String; - - if (!schemaType.IsGenericType) - return schemaType.Name; - - var typeName = schemaType.ToPrettyName(); - return typeName; - } - - private static string[]? GetEnumValues(ApiAllowableValuesAttribute? attr) - { - return attr?.Values?.ToArray(); - } - - private static Type? GetListElementType(Type type) - { - if (type.IsArray) - return type.GetElementType(); - if (!type.IsGenericType) - return null; - var genericType = type.GetGenericTypeDefinition(); - if (genericType == typeof(List<>) || genericType == typeof(IList<>) || genericType == typeof(IEnumerable<>)) - return type.GetGenericArguments()[0]; - return null; - } - - private static bool IsListType(Type type) - { - //Swagger2 specification has a special data format for type byte[] ('byte', 'binary' or 'file'), so it's not a list - if (type == typeof(byte[])) - return false; - - return GetListElementType(type) != null; - } - - private List CreateParameters(Type operationType, string route, string verb) - { - var hasDataContract = operationType.HasAttribute(); - - var properties = operationType.GetProperties(); - var paramAttrs = new Dictionary(); - var propertyTypes = new Dictionary(); - var allowableParams = new List(); - var defaultOperationParameters = new List(); - - var hasApiMembers = false; - - foreach (var property in properties) - { - if (property.HasAttribute()) - continue; - - var attr = hasDataContract - ? property.FirstAttribute() - : null; - - var propertyName = attr?.Name ?? property.Name; - - var apiMembers = property.AllAttributes(); - if (apiMembers.Length > 0) - hasApiMembers = true; - - paramAttrs[propertyName] = apiMembers; - propertyTypes[propertyName] = property.PropertyType; - var allowableValuesAttrs = property.AllAttributes(); - var allowableValuesAttr = allowableValuesAttrs.FirstOrDefault(); - allowableParams.AddRange(allowableValuesAttrs); - - if (hasDataContract && attr == null) - continue; - - var inPath = route.Contains("{" + propertyName + "}", StringComparison.OrdinalIgnoreCase); - var paramLocation = inPath - ? ParameterLocation.Path - : ParameterLocation.Query; - - var parameter = CreateParameter(property.PropertyType, - propertyName, - paramLocation, - enumValues: GetEnumValues(allowableValuesAttr)); - - defaultOperationParameters.Add(parameter); - } - - var methodOperationParameters = defaultOperationParameters; - if (hasApiMembers) - { - methodOperationParameters = []; - foreach (var key in paramAttrs.Keys) - { - var apiMembers = paramAttrs[key]; - foreach (var member in apiMembers) - { - if ((member.Verb == null || string.Compare(member.Verb, verb, StringComparison.OrdinalIgnoreCase) == 0) - && (member.Route == null || route.StartsWith(member.Route)) - && !string.Equals(member.ParameterType, "model") - && methodOperationParameters.All(x => x.Name != (member.Name ?? key))) - { - var allowableValuesAttr = allowableParams.FirstOrDefault(attr => attr.Name == (member.Name ?? key)); - var p = CreateParameter(propertyTypes[key], - member.Name ?? key, - GetParamLocation(member.GetParamType(operationType, member.Verb ?? verb)), - enumValues: GetEnumValues(allowableValuesAttr), - isApiMember:true); - // p.Type = member.DataType ?? p.Type; - // p.Format = member.Format ?? p.Format; - p.Required = p.In == ParameterLocation.Path || member.IsRequired; - p.Description = member.Description ?? p.Description; - - methodOperationParameters.Add(p); - } - } - } - } - - return methodOperationParameters; - } - - private OpenApiParameter CreateParameter(Type propType, string paramName, - ParameterLocation? paramLocation, - string[]? enumValues = null, - bool isApiMember = false) - { - if (propType.IsEnum) - { - return new OpenApiParameter - { - In = paramLocation, - Name = paramName, - Reference = ToOpenApiReference(propType), - Required = paramLocation == ParameterLocation.Path, - }; - } - - if (IsSwaggerScalarType(propType)) - { - return new OpenApiParameter - { - In = paramLocation, - Name = paramName, - Schema = new() - { - Type = GetSwaggerTypeName(propType), - Enum = enumValues?.Select(x => new OpenApiString(x)).Cast().ToList() ?? [], - Nullable = !IsRequiredType(propType), - Format = GetSwaggerTypeFormat(propType), - }, - Required = paramLocation == ParameterLocation.Path, - }; - } - - if (!isApiMember) - { - return new OpenApiParameter - { - In = paramLocation, - Name = paramName, - Schema = new() - { - Type = OpenApiType.String, - }, - Required = paramLocation == ParameterLocation.Path, - }; - } - - if (IsDictionaryType(propType)) - { - return new OpenApiParameter - { - In = paramLocation, - Name = paramName, - Schema = CreateDictionarySchema(propType) - }; - } - - if (IsListType(propType)) - { - return CreateArrayParameter(propType, paramName, paramLocation); - } - - OpenApiSchema openApiSchema; - - if (IsInlineSchema(propType)) - { - openApiSchema = CreateSchema(propType); - } - else - { - openApiSchema = new OpenApiSchema { - Reference = ToOpenApiReference(propType) - }; - } - - return new OpenApiParameter - { - In = paramLocation, - Name = paramName, - Schema = openApiSchema - }; - } - - private static ParameterLocation? GetParamLocation(string paramIn) - { - ParameterLocation? paramLocation = paramIn switch - { - "query" => ParameterLocation.Query, - "header" => ParameterLocation.Header, - "path" => ParameterLocation.Path, - "cookie" => ParameterLocation.Cookie, - _ => null, - }; - return paramLocation; - } - - private OpenApiParameter CreateArrayParameter(Type listType, - string paramName, - ParameterLocation? paramLocation) - { - var listItemType = GetListElementType(listType); - var parameter = new OpenApiParameter - { - In = paramLocation, - Schema = new() { - Type = OpenApiType.Array, - Items = new() - { - Type = listItemType != null && IsSwaggerScalarType(listItemType) - ? GetSwaggerTypeName(listItemType) - : null, - Reference = listItemType != null && !IsSwaggerScalarType(listItemType) - ? ToOpenApiReference(listItemType) - : null, - } - }, - Description = listType.GetDescription(), - Name = paramName, - Required = paramLocation == ParameterLocation.Path, - Style = ParameterStyle.Form, - Explode = true, - }; - return parameter; - } - - private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); - - private OpenApiSchema GetOpenApiProperty(PropertyInfo pi) - { - var schema = GetOpenApiProperty(pi.PropertyType); - schema.Nullable = pi.IsAssignableToNull(); - return schema; - } - - private OpenApiSchema GetOpenApiProperty(Type propertyType) - { - var schemaProp = new OpenApiSchema { - Nullable = propertyType.IsNullableType(), - }; - - propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; - - if (IsKeyValuePairType(propertyType)) - { - if (IsInlineSchema(propertyType)) - { - var schema = CreateSchema(propertyType); - if (schema != null) InlineSchema(schema, schemaProp); - } - else - { - schemaProp.Reference = ToOpenApiReference(propertyType); - } - } - else if (IsListType(propertyType)) - { - schemaProp.Type = OpenApiType.Array; - var listItemType = GetListElementType(propertyType); - if (listItemType == null) return schemaProp; - if (IsSwaggerScalarType(listItemType)) - { - schemaProp.Items = new OpenApiSchema { - Type = GetSwaggerTypeName(listItemType), - Format = GetSwaggerTypeFormat(listItemType), - }; - if (IsRequiredType(listItemType)) - { - schemaProp.Nullable = false; - } - } - else if (IsInlineSchema(listItemType)) - { - var schema = CreateSchema(listItemType); - if (schema != null) InlineSchema(schema, schemaProp); - } - else - { - schemaProp.Items = new OpenApiSchema - { - Reference = ToOpenApiReference(listItemType) - }; - } - } - else if (IsDictionaryType(propertyType)) - { - schemaProp = CreateDictionarySchema(propertyType); - } - else if (propertyType.IsEnum) - { - schemaProp.Reference = ToOpenApiReference(propertyType); - } - else if (IsSwaggerScalarType(propertyType)) - { - schemaProp.Type = GetSwaggerTypeName(propertyType); - schemaProp.Format = GetSwaggerTypeFormat(propertyType); - schemaProp.Nullable = !IsRequiredType(propertyType); - //schemaProp.Required = IsRequiredType(propertyType) ? true : (bool?)null; - } - else if (IsInlineSchema(propertyType)) - { - var schema = CreateSchema(propertyType); - if (schema != null) InlineSchema(schema, schemaProp); - } - else - { - //CreateSchema(propertyType, route, verb); - schemaProp.Reference = ToOpenApiReference(propertyType); - } - - return schemaProp; - } - - public static OpenApiSchema CreateEnumSchema(Type propertyType) - { - var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; - if (!enumType.IsEnum) - throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); - - var schema = new OpenApiSchema(); - if (enumType.IsNumericType()) - { - var underlyingType = Enum.GetUnderlyingType(enumType); - schema.Type = GetSwaggerTypeName(underlyingType); - schema.Format = GetSwaggerTypeFormat(underlyingType); - schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); - } - else - { - schema.Type = OpenApiType.String; - schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); - } - return schema; - } - - private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) - { - schemaProp.Title = schema?.Title ?? schemaProp.Title; - schemaProp.Type = schema?.Type ?? schemaProp.Type; - schemaProp.Format = schema?.Format ?? schemaProp.Format; - schemaProp.Description = schema?.Description ?? schemaProp.Description; - schemaProp.Maximum = schema?.Maximum ?? schemaProp.Maximum; - schemaProp.ExclusiveMaximum = schema?.ExclusiveMaximum ?? schemaProp.ExclusiveMaximum; - schemaProp.Minimum = schema?.Minimum ?? schemaProp.Minimum; - schemaProp.ExclusiveMinimum = schema?.ExclusiveMinimum ?? schemaProp.ExclusiveMinimum; - schemaProp.MaxLength = schema?.MaxLength ?? schemaProp.MaxLength; - schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; - schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; - schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; - schemaProp.Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); - schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; - schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; - schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; - schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; - schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; - schemaProp.Not = schema?.Not != null ? new(schema.Not) : null; - schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; - schemaProp.Items = schema?.Items != null ? new(schema.Items) : null; - schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; - schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; - schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; - schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; - schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; - schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; - schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; - schemaProp.AdditionalProperties = new(schema?.AdditionalProperties); - schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; - schemaProp.Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); - schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; - schemaProp.Nullable = schema?.Nullable ?? schemaProp.Nullable; - schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; - schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; - schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; - schemaProp.UnresolvedReference = schema?.UnresolvedReference ?? schemaProp.UnresolvedReference; - schemaProp.Reference = schema?.Reference != null ? new(schema.Reference) : null; - } - - private bool IsInlineSchema(Type schemaType) - { - return schemaType.Namespace != null && InlineSchemaTypesInNamespaces.Contains(schemaType.Namespace); - } - - List RequiredValidators { get; } = ["NotNull", "NotEmpty"]; - - public OpenApiSchema? CreateSchema(Type schemaType, string? route=null, string? verb=null, HashSet? allTypes = null) - { - if (schemaType.ExcludesFeature(Feature.Metadata) || schemaType.ExcludesFeature(Feature.ApiExplorer)) - return null; - - if (IsSwaggerScalarType(schemaType) && !schemaType.IsEnum) - return null; - - var schemaId = GetSchemaDefinitionRef(schemaType); - if (Schemas.TryGetValue(schemaId, out var schema)) - return schema; - - schema = CreateDictionarySchema(schemaType) - ?? GetKeyValuePairSchema(schemaType) - ?? GetListSchema(schemaType); - - bool parseProperties = false; - if (schema == null) - { - if (schemaType.IsEnum) - { - schema = CreateEnumSchema(schemaType); - } - else - { - schema = new OpenApiSchema - { - Type = OpenApiType.Object, - Title = GetSchemaTypeName(schemaType), - Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), - Properties = new OrderedDictionary() - }; - parseProperties = schemaType.IsUserType(); - } - - if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) - { - schema.AllOf.Add(new OpenApiSchema { Reference = ToOpenApiReference(schemaType.BaseType) }); - } - } - Schemas[schemaId] = schema; - - var properties = schemaType.GetProperties() - .Where(pi => !SwaggerUtils.IgnoreProperty(pi)) - .ToArray(); - - // Order schema properties by DataMember.Order if [DataContract] and [DataMember](s) defined - // Ordering defined by: http://msdn.microsoft.com/en-us/library/ms729813.aspx - var dataContractAttr = schemaType.FirstAttribute(); - if (dataContractAttr != null && properties.Any(prop => prop.IsDefined(typeof(DataMemberAttribute), true))) - { - var typeOrder = new List { schemaType }; - var baseType = schemaType.BaseType; - while (baseType != null) - { - typeOrder.Add(baseType); - baseType = baseType.BaseType; - } - - var propsWithDataMember = properties.Where(prop => prop.IsDefined(typeof(DataMemberAttribute), true)); - var propDataMemberAttrs = properties.ToDictionary(prop => prop, prop => prop.FirstAttribute()); - - properties = propsWithDataMember - .OrderBy(prop => propDataMemberAttrs[prop].Order) // Order by DataMember.Order - .ThenByDescending(prop => typeOrder.IndexOf(prop.DeclaringType)) // Then by BaseTypes First - .ThenBy(prop => // Then by [DataMember].Name / prop.Name - { - var name = propDataMemberAttrs[prop].Name; - return name.IsNullOrEmpty() ? prop.Name : name; - }).ToArray(); - } - - if (parseProperties) - { - foreach (var prop in properties) - { - if (prop.HasAttributeOf()) - continue; - - var apiMembers = prop - .AllAttributes() - .OrderByDescending(attr => attr.Route) - .ToList(); - var apiDoc = apiMembers - .Where(attr => string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(attr.Verb) || (verb ?? "").Equals(attr.Verb)) - .Where(attr => string.IsNullOrEmpty(route) || string.IsNullOrEmpty(attr.Route) || (route ?? "").StartsWith(attr.Route)) - .FirstOrDefault(attr => attr.ParameterType is "body" or "model"); - - if (apiMembers.Any(x => x.ExcludeInSchema)) - continue; - var schemaProperty = GetOpenApiProperty(prop); - var schemaPropertyName = GetSchemaPropertyName(prop); - - schemaProperty.Description = prop.GetDescription() ?? apiDoc?.Description; - - var propAttr = prop.FirstAttribute(); - var validateAttrs = prop.AllAttributes(); - - var isRequired = propAttr?.IsRequired == true - || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) - || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); - - if (propAttr != null) - { - if (propAttr.DataType != null) - schemaProperty.Type = propAttr.DataType; - - if (propAttr.Format != null) - schemaProperty.Format = propAttr.Format; - } - - if (isRequired) - { - schema.Required.Add(schemaPropertyName); - } - - var uploadTo = prop.FirstAttribute(); - if (uploadTo != null) - { - schemaProperty.Reference = null; - if (schemaProperty.Type != OpenApiType.Array) - { - schemaProperty.Type = "file"; - } - schemaProperty.Items = new OpenApiSchema - { - Type = OpenApiType.String, - Format = OpenApiTypeFormat.Binary, - }; - } - - schemaProperty.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); - - SchemaPropertyFilter?.Invoke(schemaProperty); - schema.Properties[schemaPropertyName] = schemaProperty; - } - } - return schema; - } - - private static string GetSchemaPropertyName(PropertyInfo prop) - { - var dataMemberAttr = prop.FirstAttribute(); - if (dataMemberAttr?.Name != null) - return dataMemberAttr.Name; - - return JsConfig.TextCase == TextCase.CamelCase - ? prop.Name.ToCamelCase() - : JsConfig.TextCase == TextCase.SnakeCase - ? prop.Name.ToLowercaseUnderscore() - : prop.Name; - } - - private static List GetNumericValues(Type propertyType, Type underlyingType) - { - var values = Enum.GetValues(propertyType) - .Map(x => $"{Convert.ChangeType(x, underlyingType)} ({x})"); - - return values; - } - - private OpenApiSchema? GetResponseSchema(IRestPath restPath, out string schemaDescription) - { - schemaDescription = string.Empty; - - // Given: class MyDto : IReturn. Determine the type X. - foreach (var i in restPath.RequestType.GetInterfaces()) - { - if (i == typeof(IReturnVoid)) - return GetSchemaForResponseType(typeof(void), out schemaDescription); - - if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>)) - { - var schemaType = i.GetGenericArguments()[0]; - return GetSchemaForResponseType(schemaType, out schemaDescription); - } - } - - return new OpenApiSchema { - Type = OpenApiType.Object, - }; - } - - private OpenApiSchema? GetSchemaForResponseType(Type schemaType, out string schemaDescription) - { - if (schemaType == typeof(IReturnVoid) || schemaType == typeof(void)) - { - schemaDescription = "No Content"; - return null; - } - - var schema = CreateDictionarySchema(schemaType) - ?? GetKeyValuePairSchema(schemaType) - ?? GetListSchema(schemaType) - ?? (IsSwaggerScalarType(schemaType) - ? new OpenApiSchema - { - Title = GetSchemaTypeName(schemaType), - Type = GetSwaggerTypeName(schemaType), - Format = GetSwaggerTypeFormat(schemaType) - } - : IsInlineSchema(schemaType) - ? CreateSchema(schemaType) - : new OpenApiSchema { Reference = ToOpenApiReference(schemaType) }); - - schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; - - return schema; - } - - private OrderedDictionary GetMethodResponseCodes(IRestPath restPath, IDictionary schemas, Type requestType) - { - var responses = new OrderedDictionary(); - - var responseSchema = GetResponseSchema(restPath, out string schemaDescription); - //schema is null when return type is IReturnVoid - var statusCode = responseSchema == null && HostConfig.Instance.Return204NoContentForEmptyResponse - ? ((int)HttpStatusCode.NoContent).ToString() - : ((int)HttpStatusCode.OK).ToString(); - - responses.Add(statusCode, new OpenApiResponse - { - Content = - { - [MimeTypes.Json] = new OpenApiMediaType - { - Schema = responseSchema, - } - }, - Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" - }); - - foreach (var attr in requestType.AllAttributes()) - { - string apiSchemaDescription = string.Empty; - - var response = new OpenApiResponse - { - Content = - { - [MimeTypes.Json] = new OpenApiMediaType - { - Schema = attr.ResponseType != null - ? GetSchemaForResponseType(attr.ResponseType, out apiSchemaDescription) - : responseSchema, - } - }, - Description = attr.Description ?? apiSchemaDescription - }; - - statusCode = attr.IsDefaultResponse ? "default" : attr.StatusCode.ToString(); - if (!responses.ContainsKey(statusCode)) - responses.Add(statusCode, response); - else - responses[statusCode] = response; - } - - return responses; - } - -} - -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs deleted file mode 100644 index b89ae5fe9f7..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiType.cs +++ /dev/null @@ -1,13 +0,0 @@ -#if !NET10_0_OR_GREATER -namespace ServiceStack.AspNetCore.OpenApi; - -public static class OpenApiType -{ - public const string Array = "array"; - public const string Boolean = "boolean"; - public const string Number = "number"; - public const string Integer = "integer"; - public const string String = "string"; - public const string Object = "object"; -} -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs deleted file mode 100644 index b91e50de778..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/OpenApiTypeFormat.cs +++ /dev/null @@ -1,17 +0,0 @@ -#if !NET10_0_OR_GREATER -namespace ServiceStack.AspNetCore.OpenApi; - -public static class OpenApiTypeFormat -{ - public const string Array = "int32"; - public const string Byte = "byte"; - public const string Binary = "binary"; - public const string Date = "date"; - public const string DateTime = "date-time"; - public const string Double = "double"; - public const string Float = "float"; - public const string Int = "int32"; - public const string Long = "int64"; - public const string Password = "password"; -} -#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs deleted file mode 100644 index dd91ba5ab6f..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackDocumentFilter.cs +++ /dev/null @@ -1,105 +0,0 @@ -#if !NET10_0_OR_GREATER -using System.Text.Json.Serialization; -using Microsoft.OpenApi.Models; -using ServiceStack.Host; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace ServiceStack.AspNetCore.OpenApi; - -// Last OpenApi Filter to run -public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFilter -{ - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - //Console.WriteLine(GetType().Name + "..."); - if (metadata.SecurityDefinition != null) - { - swaggerDoc.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; - } - - if (metadata.ApiKeySecurityDefinition != null) - { - swaggerDoc.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; - } - - var operations = HostContext.Metadata.OperationsMap.Values.ToList(); - var dtos = new HashSet(); - foreach (var op in operations) - { - if (!IsDtoTypeOrEnum(op.RequestType)) - continue; - - AddReferencedTypes(dtos, op.RequestType, IsDtoTypeOrEnum, includeBaseTypes:HttpUtils.HasRequestBody(op.Method)); - } - - var orderedDtos = dtos.OrderBy(x => x.Name); - foreach (var type in orderedDtos) - { - //Console.WriteLine("Type: " + type.ToPrettyName() + " ..."); - var schema = metadata.CreateSchema(type, allTypes: dtos); - if (schema != null) - { - swaggerDoc.Components.Schemas[OpenApiMetadata.GetSchemaTypeName(type)] = schema; - } - } - } - - public static void AddReferencedTypes(HashSet to, Type? type, Func include, bool includeBaseTypes) - { - if (type == null || to.Contains(type) || !include(type)) - return; - - to.Add(type); - - var baseType = type.BaseType; - if (includeBaseTypes && baseType != null && include(baseType) && !to.Contains(baseType)) - { - AddReferencedTypes(to, baseType, include, includeBaseTypes); - - var genericArgs = type.IsGenericType - ? type.GetGenericArguments() - : Type.EmptyTypes; - - foreach (var arg in genericArgs) - { - AddReferencedTypes(to, arg, include, includeBaseTypes); - } - } - - foreach (var pi in type.GetSerializableProperties()) - { - // Skip Obsolete properties - if (SwaggerUtils.IgnoreProperty(pi)) - continue; - - if (to.Contains(pi.PropertyType)) - continue; - - if (include(pi.PropertyType)) - AddReferencedTypes(to, pi.PropertyType, include, includeBaseTypes); - - var genericArgs = pi.PropertyType.IsGenericType - ? pi.PropertyType.GetGenericArguments() - : Type.EmptyTypes; - - if (genericArgs.Length > 0) - { - foreach (var arg in genericArgs) - { - AddReferencedTypes(to, arg, include, includeBaseTypes); - } - } - else if (pi.PropertyType.IsArray) - { - var elType = pi.PropertyType.HasElementType ? pi.PropertyType.GetElementType() : null; - AddReferencedTypes(to, elType, include, includeBaseTypes); - } - } - } - - public static bool IsDtoTypeOrEnum(Type? type) => type != null - && (ServiceMetadata.IsDtoType(type) || type.IsEnum) - && !type.IsGenericTypeDefinition - && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); -} -#endif \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs deleted file mode 100644 index 58fc51de7bb..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/ServiceStackOpenApiExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -#if !NET10_0_OR_GREATER -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Any; -using ServiceStack.AspNetCore.OpenApi; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace ServiceStack; - -public static class ServiceStackOpenApiExtensions -{ - public static void WithOpenApi(this ServiceStackOptions options) - { - if (!options.MapEndpointRouting) - throw new NotSupportedException("MapEndpointRouting must be enabled to use OpenApi"); - - options.RouteHandlerBuilders.Add((builder, operation, method, route) => - { - if (OpenApiMetadata.Instance.Ignore?.Invoke(operation) == true) - return; - builder.WithOpenApi(op => - { - OpenApiMetadata.Instance.AddOperation(op, operation, method, route); - return op; - }); - }); - } - - public static void AddSwagger(this ServiceStackServicesOptions options, Action? configure = null) - { - configure?.Invoke(OpenApiMetadata.Instance); - - options.Services!.AddSingleton(OpenApiMetadata.Instance); - options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); - options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); - - options.Services!.ConfigurePlugin(feature => { - feature.AddPluginLink("/swagger/index.html", "Swagger UI"); - }); - } - - public static void AddServiceStackSwagger(this IServiceCollection services, Action? configure = null) - { - configure?.Invoke(OpenApiMetadata.Instance); - - services.AddSingleton(OpenApiMetadata.Instance); - services.AddSingleton, ConfigureServiceStackSwagger>(); - services.AddSingleton, ConfigureServiceStackSwagger>(); - - services.ConfigurePlugin(feature => { - feature.AddPluginLink("/swagger/index.html", "Swagger UI"); - }); - } - - public static AuthenticationBuilder AddBasicAuth(this IServiceCollection services) - where TUser : IdentityUser, new() { - OpenApiMetadata.Instance.AddBasicAuth(); - return new AuthenticationBuilder(services).AddBasicAuth(); - } - - public static void AddJwtAuth(this IServiceCollection services) { - OpenApiMetadata.Instance.AddJwtBearer(); - } - - public static void AddBasicAuth(this SwaggerGenOptions options) => - options.AddSecurityDefinition(OpenApiSecurity.BasicAuthScheme.Scheme, OpenApiSecurity.BasicAuthScheme); - - public static void AddJwtAuth(this SwaggerGenOptions options) => - options.AddSecurityDefinition(OpenApiSecurity.JwtBearerScheme.Scheme, OpenApiSecurity.JwtBearerScheme); - - public static void AddApiKeys(this SwaggerGenOptions options) => - options.AddSecurityDefinition(OpenApiSecurity.ApiKeyScheme.Scheme, OpenApiSecurity.ApiKeyScheme); - - internal static List ToOpenApiEnums(this IEnumerable? enums) => - enums.Safe().Map(x => (IOpenApiAny)new OpenApiString(x)); -} -#endif diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs deleted file mode 100644 index 7815b2d554b..00000000000 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/net8/SwaggerUtils.cs +++ /dev/null @@ -1,18 +0,0 @@ -#if !NET10_0_OR_GREATER -using System.Reflection; -using System.Text.Json.Serialization; - -namespace ServiceStack.AspNetCore.OpenApi; - -public static class SwaggerUtils -{ - public static Func IgnoreProperty { get; set; } = DefaultIgnoreProperty; - - public static bool DefaultIgnoreProperty(PropertyInfo pi) - { - var propAttrs = pi.AllAttributes(); - return propAttrs.Any(x => x is ObsoleteAttribute or JsonIgnoreAttribute - or Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute); - } -} -#endif \ No newline at end of file From a9444caa33b4fcf56af36da5d224ae4a8afc864b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 10:10:18 +0800 Subject: [PATCH 018/140] fix build --- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index dfdcc9026bb..7d873ae1abf 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -25,8 +25,8 @@ - - + + From cd983e69c1687ee1c2fb30d7d3b221a23234a72b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 16:09:28 +0800 Subject: [PATCH 019/140] Add Min/Max imum prefixes to AutoQueryFeature --- ServiceStack/src/ServiceStack.Server/AutoQueryFeature.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.cs b/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.cs index 832367697ad..805f62ba0ea 100644 --- a/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.cs +++ b/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.cs @@ -97,6 +97,8 @@ public partial class AutoQueryFeature : IPlugin, IConfigureServices, IPostConfig {"Since%", SqlTemplate.GreaterThanOrEqual}, {"Start%", SqlTemplate.GreaterThanOrEqual}, {"%Higher%", SqlTemplate.GreaterThanOrEqual}, + {"Min%", SqlTemplate.GreaterThanOrEqual}, + {"Minimum%", SqlTemplate.GreaterThanOrEqual}, {">%", SqlTemplate.GreaterThanOrEqual}, {"%>", SqlTemplate.GreaterThan}, {"%!", SqlTemplate.NotEqual}, @@ -119,6 +121,8 @@ public partial class AutoQueryFeature : IPlugin, IConfigureServices, IPostConfig {"Stop%", SqlTemplate.LessThanOrEqual}, {"To%", SqlTemplate.LessThanOrEqual}, {"Until%", SqlTemplate.LessThanOrEqual}, + {"Max%", SqlTemplate.LessThanOrEqual}, + {"Maximum%", SqlTemplate.LessThanOrEqual}, {"%<", SqlTemplate.LessThanOrEqual}, {"<%", SqlTemplate.LessThan}, From 7020648f21a9f1d9d1b0ee17efe3cc9af39a6e43 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 21 Nov 2025 20:01:20 +0800 Subject: [PATCH 020/140] Add support for Microsoft.AspNetCore.OpenApi and Scalar.AspNetCore --- ServiceStack/build/build.proj | 12 +- .../ConfigureServiceStackOpenApi.cs | 43 + .../OpenApiMetadata.cs | 1100 ++ .../OpenApiType.cs | 24 + .../OpenApiTypeFormat.cs | 15 + .../OpenApiUtils.cs | 15 + .../README.md | 225 + ...eStack.AspNetCore.OpenApi.Microsoft.csproj | 30 + .../ServiceStackDocumentTransformer.cs | 215 + .../ServiceStackOpenApiExtensions.cs | 100 + .../ConfigureServiceStackSwagger.cs | 13 +- .../ServiceStackDocumentFilter.cs | 9 +- .../ServiceStackOpenApiExtensions.cs | 41 +- .../ConfigureServiceStackSwagger.cs | 35 + .../OpenApiMetadata.cs | 1100 ++ .../OpenApiType.cs | 24 + .../OpenApiTypeFormat.cs | 15 + .../ServiceStack.AspNetCore.OpenApi3.csproj | 23 + .../ServiceStackDocumentFilter.cs | 201 + .../ServiceStackOpenApiExtensions.cs | 73 + .../SwaggerUtils.cs | 16 + ServiceStack/src/ServiceStack.sln | 58 + ServiceStack/tests/AdhocNew/AdhocNew.csproj | 6 +- ServiceStack/tests/AdhocNew/swagger.json | 2788 ++++ .../OpenApi3Swashbuckle/Configure.AppHost.cs | 25 + .../OpenApi3Swashbuckle.csproj | 31 + .../tests/OpenApi3Swashbuckle/Program.cs | 29 + .../Properties/launchSettings.json | 38 + .../tests/OpenApi3Swashbuckle/README.md | 75 + .../ServiceInterface/MyServices.cs | 13 + .../OpenApi3Swashbuckle/ServiceModel/Hello.cs | 16 + .../tests/OpenApi3Swashbuckle/swagger.json | 12959 ++++++++++++++++ .../OpenApi3Swashbuckle/test-hidden-routes.sh | 55 + .../tests/OpenApiScalar/Configure.AppHost.cs | 24 + .../tests/OpenApiScalar/OpenApiScalar.csproj | 39 + ServiceStack/tests/OpenApiScalar/Program.cs | 25 + .../Properties/launchSettings.json | 38 + ServiceStack/tests/OpenApiScalar/README.md | 216 + .../ServiceInterface/MyServices.cs | 13 + .../tests/OpenApiScalar/ServiceModel/Hello.cs | 16 + 40 files changed, 19778 insertions(+), 15 deletions(-) create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ConfigureServiceStackOpenApi.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs create mode 100644 ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs create mode 100644 ServiceStack/tests/AdhocNew/swagger.json create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Program.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Properties/launchSettings.json create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/README.md create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/swagger.json create mode 100755 ServiceStack/tests/OpenApi3Swashbuckle/test-hidden-routes.sh create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs create mode 100644 ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj create mode 100644 ServiceStack/tests/OpenApiScalar/Program.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Properties/launchSettings.json create mode 100644 ServiceStack/tests/OpenApiScalar/README.md create mode 100644 ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs create mode 100644 ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs diff --git a/ServiceStack/build/build.proj b/ServiceStack/build/build.proj index e90e351864c..71192135a5d 100644 --- a/ServiceStack/build/build.proj +++ b/ServiceStack/build/build.proj @@ -139,11 +139,21 @@ Targets="Build;Pack" Properties="Configuration=$(Configuration)" /> - + + + + + + + , + IConfigureOptions +{ + public void Configure(OpenApiOptions options) + { + // Register document transformers + foreach (var transformerType in metadata.DocumentTransformerTypes) + { + // Create instance and register it + var instance = ActivatorUtilities.CreateInstance(serviceProvider, transformerType); + Console.WriteLine($"ConfigureServiceStackOpenApi.Configure called with {metadata.DocumentTransformerTypes.Count} transformers"); + if (instance is IOpenApiDocumentTransformer documentTransformer) + { + options.AddDocumentTransformer(documentTransformer); + } + } + + // Register schema transformers + foreach (var transformerType in metadata.SchemaTransformerTypes) + { + // Create instance and register it + var instance = ActivatorUtilities.CreateInstance(serviceProvider, transformerType); + if (instance is IOpenApiSchemaTransformer schemaTransformer) + { + options.AddSchemaTransformer(schemaTransformer); + } + } + } + + public void Configure(ServiceStackOptions options) + { + options.WithOpenApi(); + } +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs new file mode 100644 index 00000000000..1c448dffa10 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs @@ -0,0 +1,1100 @@ +using System.Collections.Concurrent; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.OpenApi; +using ServiceStack.Host; +using ServiceStack.NativeTypes; +using ServiceStack.Text; +using ServiceStack.Web; +using OpenApiReference = Microsoft.OpenApi.BaseOpenApiReference; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiSecurity +{ + public static OpenApiSecurityRequirement BasicAuth { get; } = new() + { + { + new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), + [] + } + }; + public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "HTTP Basic access authentication", + Type = SecuritySchemeType.Http, + Scheme = BasicAuthenticationHandler.Scheme, + }; + + public static OpenApiSecurityRequirement JwtBearer { get; } = new() + { + { + new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), + [] + } + }; + public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "JWT Bearer Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = JwtBearerDefaults.AuthenticationScheme, + }; + + public static OpenApiSecurityRequirement ApiKey { get; } = new() + { + { + new OpenApiSecuritySchemeReference("ApiKey"), + [] + } + }; + public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() + { + Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "ApiKey" + }; +} + +public class OpenApiMetadata +{ + public static OpenApiMetadata Instance { get; } = new(); + + public List DocumentTransformerTypes { get; set; } = [ + typeof(ServiceStackDocumentTransformer), + ]; + public List SchemaTransformerTypes { get; set; } = [ + ]; + + public Func? Ignore { get; set; } + + public Action? OperationFilter { get; set; } + public Action? SchemaFilter { get; set; } + public static Action? SchemaPropertyFilter { get; set; } + + private static readonly Dictionary ClrTypesToSwaggerScalarTypes = new() + { + {typeof(byte[]), OpenApiType.String}, + {typeof(sbyte[]), OpenApiType.String}, + {typeof(byte), OpenApiType.Integer}, + {typeof(sbyte), OpenApiType.Integer}, + {typeof(bool), OpenApiType.Boolean}, + {typeof(short), OpenApiType.Integer}, + {typeof(ushort), OpenApiType.Integer}, + {typeof(int), OpenApiType.Integer}, + {typeof(uint), OpenApiType.Integer}, + {typeof(long), OpenApiType.Integer}, + {typeof(ulong), OpenApiType.Integer}, + {typeof(float), OpenApiType.Number}, + {typeof(double), OpenApiType.Number}, + {typeof(decimal), OpenApiType.Number}, + {typeof(string), OpenApiType.String}, + {typeof(DateTime), OpenApiType.String}, + {typeof(DateTimeOffset), OpenApiType.String}, + }; + + private static readonly Dictionary ClrTypesToSwaggerScalarFormats = new() + { + {typeof(byte[]), OpenApiTypeFormat.Byte}, + {typeof(sbyte[]), OpenApiTypeFormat.Byte}, + {typeof(byte), OpenApiTypeFormat.Int}, + {typeof(sbyte), OpenApiTypeFormat.Int}, + {typeof(short), OpenApiTypeFormat.Int}, + {typeof(ushort), OpenApiTypeFormat.Int}, + {typeof(int), OpenApiTypeFormat.Int}, + {typeof(uint), OpenApiTypeFormat.Int}, + {typeof(long), OpenApiTypeFormat.Long}, + {typeof(ulong), OpenApiTypeFormat.Long}, + {typeof(float), OpenApiTypeFormat.Float}, + {typeof(double), OpenApiTypeFormat.Double}, + {typeof(decimal), OpenApiTypeFormat.Double}, + {typeof(DateTime), OpenApiTypeFormat.DateTime}, + {typeof(DateTimeOffset), OpenApiTypeFormat.DateTime}, + }; + + public ConcurrentDictionary Schemas { get; } = new(); + internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); + + public OpenApiSecurityScheme? SecurityDefinition { get; set; } + public OpenApiSecurityRequirement? SecurityRequirement { get; set; } + + public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } + public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } + + /// + /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI + /// + public HashSet ExcludeRequestTypes { get; set; } = []; + + public void AddBasicAuth() + { + SecurityDefinition = OpenApiSecurity.BasicAuthScheme; + SecurityRequirement = OpenApiSecurity.BasicAuth; + } + + public void AddJwtBearer() + { + SecurityDefinition = OpenApiSecurity.JwtBearerScheme; + SecurityRequirement = OpenApiSecurity.JwtBearer; + } + + public void AddApiKeys() + { + ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; + ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; + } + + public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) + { + if (ExcludeRequestTypes.Contains(operation.RequestType)) + return op; + //Console.WriteLine($"AddOperation {verb} {route} {operation.RequestType.Name}..."); + + // Response is handled by Endpoints Metadata + op.Summary = operation.RequestType.GetDescription(); + op.Description = operation.RequestType.FirstAttribute()?.Notes; + + var hasRequestBody = HttpUtils.HasRequestBody(verb); + if (!hasRequestBody) + { + var parameters = CreateParameters(operation.RequestType, route, verb); + if (parameters.Count > 0) + { + if (op.Parameters == null) + op.Parameters = new List(); + op.Parameters.AddDistinctRange(parameters); + } + } + + var apiAttr = operation.RequestType.FirstAttribute(); + if (hasRequestBody) + { + var openApiType = CreateSchema(operation.RequestType, route, verb); + if (openApiType != null) + { + // Move path parameters from body + var inPaths = new List(); + foreach (var entry in openApiType.Properties) + { + var inPath = route.Contains("{" + entry.Key + "}", StringComparison.OrdinalIgnoreCase); + if (inPath) + { + var propNameUsed = route.Contains("{" + entry.Key + "}") + ? entry.Key + : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name + ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); + inPaths.Add(entry.Key); + IOpenApiSchema prop = entry.Value; + if (op.Parameters == null) + op.Parameters = new List(); + op.Parameters.Add(new OpenApiParameter + { + Name = propNameUsed, + In = ParameterLocation.Path, + Required = true, + Schema = prop, + Style = ParameterStyle.Simple, + Explode = true, + }); + } + } + + var formSchema = openApiType.CreateShallowCopy(); + foreach (var propName in inPaths) + { + formSchema.Properties.Remove(propName); + } + + var formType = new OpenApiMediaType + { + Schema = formSchema, + }; + foreach (var entry in formSchema.Properties) + { + formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + } + var requestBody = new OpenApiRequestBody + { + }; + var content = requestBody.Content ??= new OrderedDictionary(); + content[MimeTypes.MultiPartFormData] = formType; + if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) + { + content[MimeTypes.Json] = new OpenApiMediaType + { + Schema = ToOpenApiSchemaReference(operation.RequestType) + }; + } + op.RequestBody = requestBody; + SchemaFilter?.Invoke(op, openApiType); + } + } + + if (operation.RequiresAuthentication) + { + if (SecurityRequirement != null) + { + op.Security ??= new List(); + op.Security.Add(SecurityRequirement); + } + } + if (operation.RequiresApiKey) + { + if (ApiKeySecurityDefinition != null) + { + op.Security ??= new List(); + op.Security.Add(ApiKeySecurityRequirement); + } + } + + var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); + if (userTags.Count > 0) + { + // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag + op.Tags ??= new HashSet(); + op.Tags.Clear(); + userTags.Each(tag => op.Tags.Add(new OpenApiTagReference(tag))); + } + + OperationFilter?.Invoke(verb, op, operation); + + return op; + } + + internal static IOpenApiSchema ToOpenApiSchemaReference(Type type) => + new OpenApiSchemaReference(GetSchemaDefinitionRef(type)); + + private static bool IsKeyValuePairType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + } + + private static bool IsSwaggerScalarType(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.ContainsKey(lookupType) + || lookupType.IsEnum + || (lookupType.IsValueType && !IsKeyValuePairType(lookupType)); + } + + private static string GetSwaggerTypeName(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.TryGetValue(lookupType, out var scalarType) + ? scalarType + : GetSchemaTypeName(lookupType); + } + + private static string GetSwaggerTypeFormat(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + return ClrTypesToSwaggerScalarFormats.GetValueOrDefault(lookupType); + } + + private OpenApiSchema? GetListSchema(Type schemaType) + { + if (!IsListType(schemaType)) + return null; + + var listItemType = GetListElementType(schemaType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items, + }; + } + + private static bool IsDictionaryType(Type type) + { + if (!type.IsGenericType) return false; + var genericType = type.GetGenericTypeDefinition(); + return genericType == typeof(Dictionary<,>) + || genericType == typeof(IDictionary<,>) + || genericType == typeof(IReadOnlyDictionary<,>) + || genericType == typeof(SortedDictionary<,>); + } + + private OpenApiSchema? CreateDictionarySchema(Type schemaType) + { + if (!IsDictionaryType(schemaType)) + return null; + + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + AdditionalProperties = GetOpenApiProperty(valueType) + }; + } + + private OpenApiSchema? GetKeyValuePairSchema(Type schemaType) + { + if (!IsKeyValuePairType(schemaType)) + return null; + + var keyType = schemaType.GetGenericArguments()[0]; + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary + { + ["Key"] = GetOpenApiProperty(keyType), + ["Value"] = GetOpenApiProperty(valueType), + } + }; + } + + private static bool IsRequiredType(Type type) + { + return !type.IsNullableType() && type != typeof(string); + } + + public static string GetSchemaTypeName(Type schemaType) + { + if (schemaType.IsEnum) + return schemaType.Name; + + if ((!IsKeyValuePairType(schemaType) && schemaType.IsValueType) || schemaType.IsNullableType()) + return OpenApiType.String; + + if (!schemaType.IsGenericType) + return schemaType.Name; + + var typeName = schemaType.ToPrettyName(); + return typeName; + } + + private static string[]? GetEnumValues(ApiAllowableValuesAttribute? attr) + { + return attr?.Values?.ToArray(); + } + + private static Type? GetListElementType(Type type) + { + if (type.IsArray) + return type.GetElementType(); + if (!type.IsGenericType) + return null; + var genericType = type.GetGenericTypeDefinition(); + if (genericType == typeof(List<>) || genericType == typeof(IList<>) || genericType == typeof(IEnumerable<>)) + return type.GetGenericArguments()[0]; + return null; + } + + private static bool IsListType(Type type) + { + //Swagger2 specification has a special data format for type byte[] ('byte', 'binary' or 'file'), so it's not a list + if (type == typeof(byte[])) + return false; + + return GetListElementType(type) != null; + } + + private List CreateParameters(Type operationType, string route, string verb) + { + var hasDataContract = operationType.HasAttribute(); + + var properties = operationType.GetProperties(); + var paramAttrs = new Dictionary(); + var propertyTypes = new Dictionary(); + var allowableParams = new List(); + var defaultOperationParameters = new List(); + + var hasApiMembers = false; + + foreach (var property in properties) + { + if (property.HasAttribute()) + continue; + + var attr = hasDataContract + ? property.FirstAttribute() + : null; + + var propertyName = attr?.Name ?? property.Name; + + var apiMembers = property.AllAttributes(); + if (apiMembers.Length > 0) + hasApiMembers = true; + + paramAttrs[propertyName] = apiMembers; + propertyTypes[propertyName] = property.PropertyType; + var allowableValuesAttrs = property.AllAttributes(); + var allowableValuesAttr = allowableValuesAttrs.FirstOrDefault(); + allowableParams.AddRange(allowableValuesAttrs); + + if (hasDataContract && attr == null) + continue; + + var inPath = route.Contains("{" + propertyName + "}", StringComparison.OrdinalIgnoreCase); + var paramLocation = inPath + ? ParameterLocation.Path + : ParameterLocation.Query; + + var parameter = CreateParameter(property.PropertyType, + propertyName, + paramLocation, + enumValues: GetEnumValues(allowableValuesAttr)); + + defaultOperationParameters.Add(parameter); + } + + var methodOperationParameters = defaultOperationParameters; + if (hasApiMembers) + { + methodOperationParameters = []; + foreach (var key in paramAttrs.Keys) + { + var apiMembers = paramAttrs[key]; + foreach (var member in apiMembers) + { + if ((member.Verb == null || string.Compare(member.Verb, verb, StringComparison.OrdinalIgnoreCase) == 0) + && (member.Route == null || route.StartsWith(member.Route)) + && !string.Equals(member.ParameterType, "model") + && methodOperationParameters.All(x => x.Name != (member.Name ?? key))) + { + var allowableValuesAttr = allowableParams.FirstOrDefault(attr => attr.Name == (member.Name ?? key)); + var p = CreateParameter(propertyTypes[key], + member.Name ?? key, + GetParamLocation(member.GetParamType(operationType, member.Verb ?? verb)), + enumValues: GetEnumValues(allowableValuesAttr), + isApiMember:true); + // p.Type = member.DataType ?? p.Type; + // p.Format = member.Format ?? p.Format; + p.Required = p.In == ParameterLocation.Path || member.IsRequired; + p.Description = member.Description ?? p.Description; + + methodOperationParameters.Add(p); + } + } + } + } + + return methodOperationParameters; + } + + private OpenApiParameter CreateParameter(Type propType, string paramName, + ParameterLocation? paramLocation, + string[]? enumValues = null, + bool isApiMember = false) + { + if (propType.IsEnum) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = CreateEnumSchema(propType), + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsSwaggerScalarType(propType)) + { + var schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propType)), + Format = GetSwaggerTypeFormat(propType), + }; + if (enumValues != null && enumValues.Length > 0) + { + schema.Enum = enumValues.Select(x => (JsonNode)System.Text.Json.Nodes.JsonValue.Create(x)).ToList(); + } + if (!IsRequiredType(propType)) + { + ApplyNullable(schema, true); + } + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = schema, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (!isApiMember) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + }, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsDictionaryType(propType)) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = CreateDictionarySchema(propType) + }; + } + + if (IsListType(propType)) + { + return CreateArrayParameter(propType, paramName, paramLocation); + } + + IOpenApiSchema openApiSchema; + + if (IsInlineSchema(propType)) + { + openApiSchema = CreateSchema(propType); + } + else + { + openApiSchema = ToOpenApiSchemaReference(propType); + } + + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = openApiSchema + }; + } + + private static ParameterLocation? GetParamLocation(string paramIn) + { + ParameterLocation? paramLocation = paramIn switch + { + "query" => ParameterLocation.Query, + "header" => ParameterLocation.Header, + "path" => ParameterLocation.Path, + "cookie" => ParameterLocation.Cookie, + _ => null, + }; + return paramLocation; + } + + private OpenApiParameter CreateArrayParameter(Type listType, + string paramName, + ParameterLocation? paramLocation) + { + var listItemType = GetListElementType(listType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + + var parameter = new OpenApiParameter + { + In = paramLocation, + Schema = new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items + }, + Description = listType.GetDescription(), + Name = paramName, + Required = paramLocation == ParameterLocation.Path, + Style = ParameterStyle.Form, + Explode = true, + }; + return parameter; + } + + private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); + + private IOpenApiSchema GetOpenApiProperty(PropertyInfo pi) + { + var schema = GetOpenApiProperty(pi.PropertyType); + if (schema is OpenApiSchema openApiSchema && pi.IsAssignableToNull()) + { + openApiSchema.Type = openApiSchema.Type.HasValue + ? openApiSchema.Type.Value | JsonSchemaType.Null + : JsonSchemaType.Null; + } + return schema; + } + + private IOpenApiSchema GetOpenApiProperty(Type propertyType) + { + var isNullable = propertyType.IsNullableType(); + propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (IsKeyValuePairType(propertyType)) + { + if (IsInlineSchema(propertyType)) + { + var schemaProp = new OpenApiSchema(); + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); + } + else + { + return ToOpenApiSchemaReference(propertyType); + } + } + else if (IsListType(propertyType)) + { + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array) + }; + var listItemType = GetListElementType(propertyType); + if (listItemType == null) return ApplyNullable(schemaProp, isNullable); + if (IsSwaggerScalarType(listItemType)) + { + schemaProp.Items = new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)), + Format = GetSwaggerTypeFormat(listItemType), + }; + } + else if (IsInlineSchema(listItemType)) + { + var schema = CreateSchema(listItemType); + if (schema != null) InlineSchema(schema, schemaProp); + } + else + { + schemaProp.Items = ToOpenApiSchemaReference(listItemType); + } + return ApplyNullable(schemaProp, isNullable); + } + else if (IsDictionaryType(propertyType)) + { + var schemaProp = CreateDictionarySchema(propertyType); + return ApplyNullable(schemaProp, isNullable); + } + else if (propertyType.IsEnum) + { + return ToOpenApiSchemaReference(propertyType); + } + else if (IsSwaggerScalarType(propertyType)) + { + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propertyType)), + Format = GetSwaggerTypeFormat(propertyType), + }; + var nullable = isNullable || !IsRequiredType(propertyType); + return ApplyNullable(schemaProp, nullable); + } + else if (IsInlineSchema(propertyType)) + { + var schemaProp = new OpenApiSchema(); + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); + } + else + { + //CreateSchema(propertyType, route, verb); + return ToOpenApiSchemaReference(propertyType); + } + } + + private static IOpenApiSchema ApplyNullable(OpenApiSchema schema, bool isNullable) + { + if (isNullable && schema.Type.HasValue) + { + schema.Type = schema.Type.Value | JsonSchemaType.Null; + } + return schema; + } + + public static OpenApiSchema CreateEnumSchema(Type propertyType) + { + var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (!enumType.IsEnum) + throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); + + var schema = new OpenApiSchema(); + if (enumType.IsNumericType()) + { + var underlyingType = Enum.GetUnderlyingType(enumType); + schema.Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(underlyingType)); + schema.Format = GetSwaggerTypeFormat(underlyingType); + schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); + } + else + { + schema.Type = OpenApiType.ToJsonSchemaType(OpenApiType.String); + schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); + } + return schema; + } + + private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) + { + schemaProp.Title = schema?.Title ?? schemaProp.Title; + schemaProp.Type = schema?.Type ?? schemaProp.Type; + schemaProp.Format = schema?.Format ?? schemaProp.Format; + schemaProp.Description = schema?.Description ?? schemaProp.Description; + schemaProp.Maximum = schema?.Maximum ?? schemaProp.Maximum; + schemaProp.ExclusiveMaximum = schema?.ExclusiveMaximum ?? schemaProp.ExclusiveMaximum; + schemaProp.Minimum = schema?.Minimum ?? schemaProp.Minimum; + schemaProp.ExclusiveMinimum = schema?.ExclusiveMinimum ?? schemaProp.ExclusiveMinimum; + schemaProp.MaxLength = schema?.MaxLength ?? schemaProp.MaxLength; + schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; + schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; + schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; + schemaProp.Default = schema?.Default?.DeepClone(); + schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; + schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; + schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + schemaProp.Not = schema?.Not?.CreateShallowCopy(); + schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; + schemaProp.Items = schema?.Items?.CreateShallowCopy(); + schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; + schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; + schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; + schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; + schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; + schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; + schemaProp.AdditionalProperties = schema?.AdditionalProperties?.CreateShallowCopy(); + schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; + // Note: In Microsoft.OpenApi 3.x, Example property handling may vary by implementation + // The concrete OpenApiSchema class should support Example assignment + // if (schema?.Example is JsonNode exampleNode) + // schemaProp.Example = exampleNode.DeepClone(); + schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; + schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; + schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; + schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; + } + + private bool IsInlineSchema(Type schemaType) + { + return schemaType.Namespace != null && InlineSchemaTypesInNamespaces.Contains(schemaType.Namespace); + } + + List RequiredValidators { get; } = ["NotNull", "NotEmpty"]; + + public OpenApiSchema? CreateSchema(Type schemaType, string? route=null, string? verb=null, HashSet? allTypes = null) + { + if (schemaType.ExcludesFeature(Feature.Metadata) || schemaType.ExcludesFeature(Feature.ApiExplorer)) + return null; + + if (IsSwaggerScalarType(schemaType) && !schemaType.IsEnum) + return null; + + var schemaId = GetSchemaDefinitionRef(schemaType); + if (Schemas.TryGetValue(schemaId, out var schema)) + return schema; + + schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType); + + bool parseProperties = false; + if (schema == null) + { + if (schemaType.IsEnum) + { + schema = CreateEnumSchema(schemaType); + } + else + { + schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary() + }; + parseProperties = schemaType.IsUserType(); + } + + if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) + { + schema.AllOf ??= new List(); + schema.AllOf.Add(ToOpenApiSchemaReference(schemaType.BaseType)); + } + } + Schemas[schemaId] = schema; + + var properties = schemaType.GetProperties() + .Where(pi => !OpenApiUtils.IgnoreProperty(pi)) + .ToArray(); + + // Order schema properties by DataMember.Order if [DataContract] and [DataMember](s) defined + // Ordering defined by: http://msdn.microsoft.com/en-us/library/ms729813.aspx + var dataContractAttr = schemaType.FirstAttribute(); + if (dataContractAttr != null && properties.Any(prop => prop.IsDefined(typeof(DataMemberAttribute), true))) + { + var typeOrder = new List { schemaType }; + var baseType = schemaType.BaseType; + while (baseType != null) + { + typeOrder.Add(baseType); + baseType = baseType.BaseType; + } + + var propsWithDataMember = properties.Where(prop => prop.IsDefined(typeof(DataMemberAttribute), true)); + var propDataMemberAttrs = properties.ToDictionary(prop => prop, prop => prop.FirstAttribute()); + + properties = propsWithDataMember + .OrderBy(prop => propDataMemberAttrs[prop].Order) // Order by DataMember.Order + .ThenByDescending(prop => typeOrder.IndexOf(prop.DeclaringType)) // Then by BaseTypes First + .ThenBy(prop => // Then by [DataMember].Name / prop.Name + { + var name = propDataMemberAttrs[prop].Name; + return name.IsNullOrEmpty() ? prop.Name : name; + }).ToArray(); + } + + if (parseProperties) + { + foreach (var prop in properties) + { + if (prop.HasAttributeOf()) + continue; + + var apiMembers = prop + .AllAttributes() + .OrderByDescending(attr => attr.Route) + .ToList(); + var apiDoc = apiMembers + .Where(attr => string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(attr.Verb) || (verb ?? "").Equals(attr.Verb)) + .Where(attr => string.IsNullOrEmpty(route) || string.IsNullOrEmpty(attr.Route) || (route ?? "").StartsWith(attr.Route)) + .FirstOrDefault(attr => attr.ParameterType is "body" or "model"); + + if (apiMembers.Any(x => x.ExcludeInSchema)) + continue; + var schemaProperty = GetOpenApiProperty(prop); + var schemaPropertyName = GetSchemaPropertyName(prop); + + if (schemaProperty is OpenApiSchema openApiSchema) + { + openApiSchema.Description = prop.GetDescription() ?? apiDoc?.Description; + + var propAttr = prop.FirstAttribute(); + var validateAttrs = prop.AllAttributes(); + + var isRequired = propAttr?.IsRequired == true + || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) + || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); + + if (propAttr != null) + { + if (propAttr.DataType != null) + openApiSchema.Type = OpenApiType.ToJsonSchemaType(propAttr.DataType); + + if (propAttr.Format != null) + openApiSchema.Format = propAttr.Format; + } + + if (isRequired) + { + schema.Required ??= new HashSet(); + schema.Required.Add(schemaPropertyName); + } + + var uploadTo = prop.FirstAttribute(); + if (uploadTo != null) + { + if (openApiSchema.Type != OpenApiType.ToJsonSchemaType(OpenApiType.Array)) + { + openApiSchema.Type = JsonSchemaType.String; // "file" type doesn't exist in JsonSchemaType + } + openApiSchema.Items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + Format = OpenApiTypeFormat.Binary, + }; + } + + openApiSchema.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); + + SchemaPropertyFilter?.Invoke(openApiSchema); + } + schema.Properties[schemaPropertyName] = schemaProperty; + } + } + return schema; + } + + private static string GetSchemaPropertyName(PropertyInfo prop) + { + var dataMemberAttr = prop.FirstAttribute(); + if (dataMemberAttr?.Name != null) + return dataMemberAttr.Name; + + return JsConfig.TextCase == TextCase.CamelCase + ? prop.Name.ToCamelCase() + : JsConfig.TextCase == TextCase.SnakeCase + ? prop.Name.ToLowercaseUnderscore() + : prop.Name; + } + + private static List GetNumericValues(Type propertyType, Type underlyingType) + { + var values = Enum.GetValues(propertyType) + .Map(x => $"{Convert.ChangeType(x, underlyingType)} ({x})"); + + return values; + } + + private OpenApiSchema? GetResponseSchema(IRestPath restPath, out string schemaDescription) + { + schemaDescription = string.Empty; + + // Given: class MyDto : IReturn. Determine the type X. + foreach (var i in restPath.RequestType.GetInterfaces()) + { + if (i == typeof(IReturnVoid)) + return GetSchemaForResponseType(typeof(void), out schemaDescription); + + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>)) + { + var schemaType = i.GetGenericArguments()[0]; + return GetSchemaForResponseType(schemaType, out schemaDescription); + } + } + + return new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + }; + } + + private OpenApiSchema? GetSchemaForResponseType(Type schemaType, out string schemaDescription) + { + if (schemaType == typeof(IReturnVoid) || schemaType == typeof(void)) + { + schemaDescription = "No Content"; + return null; + } + + OpenApiSchema? schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType); + + if (schema == null) + { + if (IsSwaggerScalarType(schemaType)) + { + schema = new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(schemaType)), + Format = GetSwaggerTypeFormat(schemaType) + }; + } + else if (IsInlineSchema(schemaType)) + { + schema = CreateSchema(schemaType); + } + else + { + // For references, we need to return a schema that references the type + // In v3.0, we can't use OpenApiSchema with Reference property + // Instead, we should use OpenApiSchemaReference, but since the return type is OpenApiSchema?, + // we'll create a schema with AllOf containing the reference + schema = new OpenApiSchema + { + AllOf = new List { ToOpenApiSchemaReference(schemaType) } + }; + } + } + + schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; + + return schema; + } + + internal OrderedDictionary GetMethodResponseCodes(IRestPath restPath, IDictionary schemas, Type requestType) + { + var responses = new OrderedDictionary(); + + var responseSchema = GetResponseSchema(restPath, out string schemaDescription); + //schema is null when return type is IReturnVoid + var statusCode = responseSchema == null && HostConfig.Instance.Return204NoContentForEmptyResponse + ? ((int)HttpStatusCode.NoContent).ToString() + : ((int)HttpStatusCode.OK).ToString(); + + var response = new OpenApiResponse + { + Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" + }; + var content = response.Content ??= new OrderedDictionary(); + content[MimeTypes.Json] = new OpenApiMediaType + { + Schema = responseSchema, + }; + responses.Add(statusCode, response); + + foreach (var attr in requestType.AllAttributes()) + { + string apiSchemaDescription = string.Empty; + + var apiResponse = new OpenApiResponse + { + Description = attr.Description ?? apiSchemaDescription + }; + var apiContent = apiResponse.Content ??= new OrderedDictionary(); + apiContent[MimeTypes.Json] = new OpenApiMediaType + { + Schema = attr.ResponseType != null + ? GetSchemaForResponseType(attr.ResponseType, out apiSchemaDescription) + : responseSchema, + }; + + statusCode = attr.IsDefaultResponse ? "default" : attr.StatusCode.ToString(); + if (!responses.ContainsKey(statusCode)) + responses.Add(statusCode, apiResponse); + else + responses[statusCode] = apiResponse; + } + + return responses; + } + +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs new file mode 100644 index 00000000000..17178581051 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs @@ -0,0 +1,24 @@ +using Microsoft.OpenApi; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiType +{ + public const string Array = "array"; + public const string Boolean = "boolean"; + public const string Number = "number"; + public const string Integer = "integer"; + public const string String = "string"; + public const string Object = "object"; + + public static JsonSchemaType ToJsonSchemaType(string type) => type switch + { + Array => JsonSchemaType.Array, + Boolean => JsonSchemaType.Boolean, + Number => JsonSchemaType.Number, + Integer => JsonSchemaType.Integer, + String => JsonSchemaType.String, + Object => JsonSchemaType.Object, + _ => JsonSchemaType.String + }; +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs new file mode 100644 index 00000000000..3ebd7db4e97 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs @@ -0,0 +1,15 @@ +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiTypeFormat +{ + public const string Array = "int32"; + public const string Byte = "byte"; + public const string Binary = "binary"; + public const string Date = "date"; + public const string DateTime = "date-time"; + public const string Double = "double"; + public const string Float = "float"; + public const string Int = "int32"; + public const string Long = "int64"; + public const string Password = "password"; +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs new file mode 100644 index 00000000000..5db17806ca2 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Text.Json.Serialization; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiUtils +{ + public static Func IgnoreProperty { get; set; } = DefaultIgnoreProperty; + + public static bool DefaultIgnoreProperty(PropertyInfo pi) + { + var propAttrs = pi.AllAttributes(); + return propAttrs.Any(x => x is ObsoleteAttribute or JsonIgnoreAttribute); + } +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md new file mode 100644 index 00000000000..607c816cf58 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md @@ -0,0 +1,225 @@ +# ServiceStack.AspNetCore.OpenApi.Microsoft + +ServiceStack integration for **Microsoft.AspNetCore.OpenApi** - the native OpenAPI document generation library in ASP.NET Core. + +This package enables ServiceStack services to be documented using Microsoft's built-in OpenAPI support, providing seamless integration with modern OpenAPI UI tools like Scalar, Swagger UI, and others. + +## Features + +- ✅ Native integration with `Microsoft.AspNetCore.OpenApi` (ASP.NET Core's built-in OpenAPI support) +- ✅ Automatic OpenAPI 3.1 document generation from ServiceStack services +- ✅ Support for multiple OpenAPI documents +- ✅ Compatible with Scalar UI, Swagger UI, and other OpenAPI viewers +- ✅ Full support for ServiceStack DTOs, routes, and metadata +- ✅ Security scheme support (Basic Auth, API Key, Bearer tokens) + +## Installation + +```bash +dotnet add package ServiceStack.AspNetCore.OpenApi.Microsoft +``` + +For Scalar UI support: +```bash +dotnet add package Scalar.AspNetCore +``` + +## Quick Start + +### Basic Configuration + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Add Microsoft OpenAPI support +builder.Services.AddOpenApi(); + +// Add ServiceStack OpenAPI integration +builder.Services.AddServiceStackOpenApi(); + +// Add ServiceStack +builder.Services.AddServiceStack(typeof(MyServices).Assembly); + +var app = builder.Build(); + +// Configure ServiceStack +app.UseServiceStack(new AppHost()); + +// Map OpenAPI endpoints (in Development) +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.Run(); +``` + +### With Scalar UI + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddOpenApi(); +builder.Services.AddServiceStackOpenApi(); +builder.Services.AddServiceStack(typeof(MyServices).Assembly); + +var app = builder.Build(); + +app.UseServiceStack(new AppHost()); + +if (app.Environment.IsDevelopment()) +{ + // Map OpenAPI document endpoint + app.MapOpenApi(); + + // Map Scalar UI endpoint + app.MapScalarApiReference(); +} + +app.Run(); +``` + +## Available Endpoints + +After configuration, the following endpoints are available: + +### OpenAPI Document Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/openapi/v1.json` | OpenAPI 3.1 document in JSON format (default document) | +| `/openapi/{documentName}.json` | Named OpenAPI documents | + +### UI Endpoints + +#### Scalar UI (when using `Scalar.AspNetCore`) + +| Endpoint | Description | +|----------|-------------| +| `/scalar/v1` | Scalar API reference UI for the default document | +| `/scalar/{documentName}` | Scalar UI for named documents | + +#### Swagger UI (when using `Swashbuckle.AspNetCore`) + +| Endpoint | Description | +|----------|-------------| +| `/swagger/v1/swagger.json` | Swagger document endpoint | +| `/swagger` | Swagger UI | + +## Advanced Configuration + +### Multiple OpenAPI Documents + +You can configure multiple OpenAPI documents for different API versions or groups: + +```csharp +builder.Services.AddOpenApi("v1"); +builder.Services.AddOpenApi("v2"); + +// Configure ServiceStack for each document +builder.Services.AddServiceStackOpenApi("v1"); +builder.Services.AddServiceStackOpenApi("v2"); +``` + +### Custom Document Configuration + +```csharp +builder.Services.AddServiceStackOpenApi(documentName: "v1", configure: metadata => +{ + // Configure metadata + metadata.Title = "My API"; + metadata.Version = "1.0.0"; + metadata.Description = "My ServiceStack API"; + + // Add security definitions + metadata.AddBasicAuth(); + metadata.AddApiKeyAuth(); + metadata.AddBearerAuth(); +}); +``` + +### Security Schemes + +Add authentication schemes to your OpenAPI document: + +```csharp +builder.Services.AddServiceStackOpenApi(configure: metadata => +{ + // Basic Authentication + metadata.AddBasicAuth(); + + // API Key Authentication + metadata.AddApiKeyAuth(); + + // Bearer Token Authentication + metadata.AddBearerAuth(); +}); +``` + +## Scalar UI Configuration + +Scalar provides a modern, interactive API documentation interface. + +### Basic Scalar Setup + +```csharp +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.MapScalarApiReference(); +} +``` + +### Custom Scalar Options + +```csharp +app.MapScalarApiReference(options => +{ + options + .WithTitle("My API Documentation") + .WithTheme(ScalarTheme.Purple) + .WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient); +}); +``` + +## Example Project + +See the `ServiceStack/tests/OpenApiScalar` project for a complete working example. + +## Differences from Swashbuckle + +This package uses **Microsoft.AspNetCore.OpenApi** instead of **Swashbuckle.AspNetCore**: + +| Feature | Swashbuckle | Microsoft.AspNetCore.OpenApi | +|---------|-------------|------------------------------| +| OpenAPI Version | 3.0 | 3.1 | +| Integration | Third-party | Native ASP.NET Core | +| Performance | Good | Better (native) | +| Document Filters | `IDocumentFilter` | `IOpenApiDocumentTransformer` | +| Schema Filters | `ISchemaFilter` | `IOpenApiSchemaTransformer` | + +## Troubleshooting + +### OpenAPI document shows empty paths + +Make sure you call `AddServiceStackOpenApi()` with the correct document name that matches your `AddOpenApi()` call: + +```csharp +// These must match +builder.Services.AddOpenApi("v1"); +builder.Services.AddServiceStackOpenApi("v1"); +``` + +### ServiceStack operations not appearing + +Ensure `UseServiceStack()` is called before `MapOpenApi()`: + +```csharp +app.UseServiceStack(new AppHost()); // Must be before MapOpenApi +app.MapOpenApi(); +``` + +## License + +See the main ServiceStack license. + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj new file mode 100644 index 00000000000..528099891ce --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + enable + ServiceStack.AspNetCore.OpenApi + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs new file mode 100644 index 00000000000..e9130f1c3ff --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs @@ -0,0 +1,215 @@ +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi; +using ServiceStack.Host; +using ServiceStack.Web; + +namespace ServiceStack.AspNetCore.OpenApi; + +// ServiceStack Document Transformer for Microsoft.AspNetCore.OpenApi +public class ServiceStackDocumentTransformer(OpenApiMetadata metadata) : IOpenApiDocumentTransformer +{ + public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) + { + // Check if ServiceStack has been initialized + if (HostContext.AppHost == null) + { + // ServiceStack not yet initialized, skip for now + return Task.CompletedTask; + } + + if (metadata.SecurityDefinition != null) + { + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes ??= new Dictionary(); + document.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; + } + + if (metadata.ApiKeySecurityDefinition != null) + { + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes ??= new Dictionary(); + document.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; + } + + // Ensure we have a Paths collection to populate + document.Paths ??= new OpenApiPaths(); + + var restPathMap = HostContext.ServiceController.RestPathMap; + + // Build schemas for all DTOs used by ServiceStack operations + var operations = HostContext.Metadata.OperationsMap.Values.ToList(); + var dtos = new HashSet(); + foreach (var op in operations) + { + if (IsDtoTypeOrEnum(op.RequestType)) + { + AddReferencedTypes(dtos, op.RequestType, IsDtoTypeOrEnum, includeBaseTypes: HttpUtils.HasRequestBody(op.Method)); + } + + if (op.ResponseType != null && IsDtoTypeOrEnum(op.ResponseType)) + { + // Ensure response DTOs like HelloResponse are also added to components/schemas + AddReferencedTypes(dtos, op.ResponseType, IsDtoTypeOrEnum, includeBaseTypes: true); + } + } + + var orderedDtos = dtos.OrderBy(x => x.Name); + foreach (var type in orderedDtos) + { + var schema = metadata.CreateSchema(type, allTypes: dtos); + if (schema != null) + { + document.Components ??= new OpenApiComponents(); + document.Components.Schemas ??= new Dictionary(); + document.Components.Schemas[OpenApiMetadata.GetSchemaTypeName(type)] = schema; + } + } + + // Populate operations/paths from ServiceStack metadata + foreach (var restPathList in restPathMap.Values) + { + foreach (var restPath in restPathList) + { + var verbs = new List(); + verbs.AddRange(restPath.AllowsAllVerbs + ? new[] { HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete } + : restPath.Verbs); + + var routePath = restPath.Path.Replace("*", string.Empty); + var requestType = restPath.RequestType; + if (!HostContext.Metadata.OperationsMap.TryGetValue(requestType, out var opMeta)) + continue; + + if (metadata.Ignore?.Invoke(opMeta) == true) + continue; + + // Skip operations that exclude metadata or API explorer features + if (requestType.ExcludesFeature(Feature.Metadata) || requestType.ExcludesFeature(Feature.ApiExplorer)) + continue; + + // Swashbuckle expects route templates without leading ~/ and with {id} style params + var swaggerPath = routePath.StartsWith("~/") + ? routePath.Substring(1) + : routePath; + + if (!document.Paths.TryGetValue(swaggerPath, out var pathItem)) + { + pathItem = new OpenApiPathItem(); + document.Paths[swaggerPath] = pathItem; + } + + // Apply each verb for this RestPath + foreach (var verb in verbs) + { + var openApiOp = new OpenApiOperation + { + OperationId = $"{requestType.Name}{verb}{swaggerPath.Replace("/", "_").Replace("{", "_").Replace("}", string.Empty)}", + }; + + openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath); + + // Responses + var responses = GetResponses(metadata, restPath, requestType); + foreach (var entry in responses) + { + openApiOp.Responses[entry.Key] = entry.Value; + } + + // Tags from [Tag] attributes if any + var userTags = requestType.AllAttributes().Map(x => x.Name); + foreach (var tag in userTags) + { + openApiOp.Tags ??= new HashSet(); + openApiOp.Tags.Add(new OpenApiTagReference(tag)); + } + + // Map verb to HttpMethod and add operation to path + HttpMethod? httpMethod = verb switch + { + HttpMethods.Get => HttpMethod.Get, + HttpMethods.Post => HttpMethod.Post, + HttpMethods.Put => HttpMethod.Put, + HttpMethods.Delete => HttpMethod.Delete, + HttpMethods.Patch => HttpMethod.Patch, + HttpMethods.Head => HttpMethod.Head, + HttpMethods.Options => HttpMethod.Options, + _ => null + }; + + if (httpMethod != null && pathItem is OpenApiPathItem concretePathItem) + { + concretePathItem.AddOperation(httpMethod, openApiOp); + } + } + } + } + + return Task.CompletedTask; + } + + private static OrderedDictionary GetResponses(OpenApiMetadata metadata, IRestPath restPath, Type requestType) + { + // OpenApiMetadata already knows how to build response schemas using the v3 models + var schemas = metadata.Schemas.ToDictionary(x => x.Key, x => x.Value); + return metadata.GetMethodResponseCodes(restPath, schemas, requestType); + } + + public static void AddReferencedTypes(HashSet to, Type? type, Func include, bool includeBaseTypes) + { + if (type == null || to.Contains(type) || !include(type)) + return; + + to.Add(type); + + var baseType = type.BaseType; + if (includeBaseTypes && baseType != null && include(baseType) && !to.Contains(baseType)) + { + AddReferencedTypes(to, baseType, include, includeBaseTypes); + + var genericArgs = type.IsGenericType + ? type.GetGenericArguments() + : Type.EmptyTypes; + + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + + foreach (var pi in type.GetSerializableProperties()) + { + // Skip Obsolete properties + if (OpenApiUtils.IgnoreProperty(pi)) + continue; + + if (to.Contains(pi.PropertyType)) + continue; + + if (include(pi.PropertyType)) + AddReferencedTypes(to, pi.PropertyType, include, includeBaseTypes); + + var genericArgs = pi.PropertyType.IsGenericType + ? pi.PropertyType.GetGenericArguments() + : Type.EmptyTypes; + + if (genericArgs.Length > 0) + { + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + else if (pi.PropertyType.IsArray) + { + var elType = pi.PropertyType.HasElementType ? pi.PropertyType.GetElementType() : null; + AddReferencedTypes(to, elType, include, includeBaseTypes); + } + } + } + + public static bool IsDtoTypeOrEnum(Type? type) => type != null + && (ServiceMetadata.IsDtoType(type) || type.IsEnum) + && !type.IsGenericTypeDefinition + && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs new file mode 100644 index 00000000000..31e73b0985f --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs @@ -0,0 +1,100 @@ +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using ServiceStack.AspNetCore.OpenApi; + +namespace ServiceStack; + +public static class ServiceStackOpenApiExtensions +{ + public static void WithOpenApi(this ServiceStackOptions options) + { + // ServiceStack operations are wired into the OpenAPI document via + // ServiceStackDocumentTransformer + OpenApiMetadata using Microsoft.AspNetCore.OpenApi, + // based on ServiceStack's own metadata. + // + // This hook is kept for symmetry with other OpenApi packages and as + // a future extension point if additional ServiceStackOptions-based + // configuration is needed, but it's currently a no-op. + } + + public static void AddOpenApi(this ServiceStackServicesOptions options, Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + options.Services!.AddSingleton(OpenApiMetadata.Instance); + options.Services!.AddSingleton, ConfigureServiceStackOpenApi>(); + options.Services!.AddSingleton, ConfigureServiceStackOpenApi>(); + + options.Services!.ConfigurePlugin(feature => { + feature.AddPluginLink("/openapi/v1.json", "OpenAPI"); + }); + } + + public static void AddServiceStackOpenApi(this IServiceCollection services, string documentName = "v1", Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + services.AddSingleton(OpenApiMetadata.Instance); + + // Register the transformer types as services so they can be DI-activated + foreach (var transformerType in OpenApiMetadata.Instance.DocumentTransformerTypes) + { + services.AddTransient(transformerType); + } + foreach (var transformerType in OpenApiMetadata.Instance.SchemaTransformerTypes) + { + services.AddTransient(transformerType); + } + + // Configure OpenApiOptions for the specific document name + // Use PostConfigure to ensure this runs after AddOpenApi() + services.PostConfigure(documentName, options => + { + // Register document transformers using DI activation + foreach (var transformerType in OpenApiMetadata.Instance.DocumentTransformerTypes) + { + // Use reflection to call AddDocumentTransformer() + var method = typeof(OpenApiOptions).GetMethod(nameof(OpenApiOptions.AddDocumentTransformer), + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, + null, Type.EmptyTypes, null); + var genericMethod = method!.MakeGenericMethod(transformerType); + genericMethod.Invoke(options, null); + } + + // Register schema transformers using DI activation + foreach (var transformerType in OpenApiMetadata.Instance.SchemaTransformerTypes) + { + // Use reflection to call AddSchemaTransformer() + var method = typeof(OpenApiOptions).GetMethod(nameof(OpenApiOptions.AddSchemaTransformer), + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, + null, Type.EmptyTypes, null); + var genericMethod = method!.MakeGenericMethod(transformerType); + genericMethod.Invoke(options, null); + } + }); + + services.AddSingleton, ConfigureServiceStackOpenApi>(); + + services.ConfigurePlugin(feature => { + feature.AddPluginLink($"/openapi/{documentName}.json", "OpenAPI"); + }); + } + + public static AuthenticationBuilder AddBasicAuth(this IServiceCollection services) + where TUser : IdentityUser, new() { + OpenApiMetadata.Instance.AddBasicAuth(); + return new AuthenticationBuilder(services).AddBasicAuth(); + } + + public static void AddJwtAuth(this IServiceCollection services) { + OpenApiMetadata.Instance.AddJwtBearer(); + } + + internal static List ToOpenApiEnums(this IEnumerable? enums) => + enums.Safe().Map(x => (JsonNode)JsonValue.Create(x)); +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs index cb9c2a5cfec..02e5742ce9a 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ConfigureServiceStackSwagger.cs @@ -4,10 +4,17 @@ namespace ServiceStack; -public class ConfigureServiceStackSwagger(OpenApiMetadata metadata) : - IConfigureOptions, +public class ConfigureServiceStackSwagger : + IConfigureOptions, IConfigureOptions { + private readonly OpenApiMetadata metadata; + + public ConfigureServiceStackSwagger(OpenApiMetadata metadata) + { + this.metadata = metadata; + } + public void Configure(SwaggerGenOptions options) { foreach (var filterType in metadata.DocumentFilterTypes) @@ -15,7 +22,7 @@ public void Configure(SwaggerGenOptions options) options.DocumentFilterDescriptors.Add(new FilterDescriptor { Type = filterType, - Arguments = [], + Arguments = [metadata], }); } foreach (var filterType in metadata.SchemaFilterTypes) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs index 54897407164..6e55b10039e 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs @@ -6,8 +6,15 @@ namespace ServiceStack.AspNetCore.OpenApi; // Last OpenApi Filter to run -public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFilter +public class ServiceStackDocumentFilter : IDocumentFilter { + private readonly OpenApiMetadata metadata; + + public ServiceStackDocumentFilter(OpenApiMetadata metadata) + { + this.metadata = metadata; + } + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { //Console.WriteLine(GetType().Name + "..."); diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs index 68295e34b2c..99f350fe6fd 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs @@ -43,15 +43,40 @@ public static void AddSwagger(this ServiceStackServicesOptions options, Action? configure = null) { - configure?.Invoke(OpenApiMetadata.Instance); - - services.AddSingleton(OpenApiMetadata.Instance); - services.AddSingleton, ConfigureServiceStackSwagger>(); - services.AddSingleton, ConfigureServiceStackSwagger>(); + try + { + configure?.Invoke(OpenApiMetadata.Instance); + services.AddSingleton(OpenApiMetadata.Instance); + services.AddSingleton, ConfigureServiceStackSwagger>(); + services.AddSingleton, ConfigureServiceStackSwagger>(); - services.ConfigurePlugin(feature => { - feature.AddPluginLink("/swagger/index.html", "Swagger UI"); - }); + services.ConfigurePlugin(feature => { + feature.AddPluginLink("/swagger/index.html", "Swagger UI"); + }); + } + catch (TypeInitializationException e) + { + Console.WriteLine( + """ + + + Possible package version conflict detected. + If using .NET 10, only these major versions of the following packages should be used: + + + + + To use the latest versions of these packages switch to: + + + Which instead uses: + + + + + """); + throw; + } } public static AuthenticationBuilder AddBasicAuth(this IServiceCollection services) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs new file mode 100644 index 00000000000..cb9c2a5cfec --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Options; +using ServiceStack.AspNetCore.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack; + +public class ConfigureServiceStackSwagger(OpenApiMetadata metadata) : + IConfigureOptions, + IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + foreach (var filterType in metadata.DocumentFilterTypes) + { + options.DocumentFilterDescriptors.Add(new FilterDescriptor + { + Type = filterType, + Arguments = [], + }); + } + foreach (var filterType in metadata.SchemaFilterTypes) + { + options.SchemaFilterDescriptors.Add(new FilterDescriptor + { + Type = filterType, + Arguments = [], + }); + } + } + + public void Configure(ServiceStackOptions options) + { + options.WithOpenApi(); + } +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs new file mode 100644 index 00000000000..5eab50eeecc --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs @@ -0,0 +1,1100 @@ +using System.Collections.Concurrent; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.OpenApi; +using ServiceStack.Host; +using ServiceStack.NativeTypes; +using ServiceStack.Text; +using ServiceStack.Web; +using OpenApiReference = Microsoft.OpenApi.BaseOpenApiReference; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiSecurity +{ + public static OpenApiSecurityRequirement BasicAuth { get; } = new() + { + { + new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), + [] + } + }; + public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "HTTP Basic access authentication", + Type = SecuritySchemeType.Http, + Scheme = BasicAuthenticationHandler.Scheme, + }; + + public static OpenApiSecurityRequirement JwtBearer { get; } = new() + { + { + new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), + [] + } + }; + public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() + { + In = ParameterLocation.Header, + Name = "Authorization", + Description = "JWT Bearer Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = JwtBearerDefaults.AuthenticationScheme, + }; + + public static OpenApiSecurityRequirement ApiKey { get; } = new() + { + { + new OpenApiSecuritySchemeReference("ApiKey"), + [] + } + }; + public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() + { + Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "ApiKey" + }; +} + +public class OpenApiMetadata +{ + public static OpenApiMetadata Instance { get; } = new(); + + public List DocumentFilterTypes { get; set; } = [ + typeof(ServiceStackDocumentFilter), + ]; + public List SchemaFilterTypes { get; set; } = [ + ]; + + public Func? Ignore { get; set; } + + public Action? OperationFilter { get; set; } + public Action? SchemaFilter { get; set; } + public static Action? SchemaPropertyFilter { get; set; } + + private static readonly Dictionary ClrTypesToSwaggerScalarTypes = new() + { + {typeof(byte[]), OpenApiType.String}, + {typeof(sbyte[]), OpenApiType.String}, + {typeof(byte), OpenApiType.Integer}, + {typeof(sbyte), OpenApiType.Integer}, + {typeof(bool), OpenApiType.Boolean}, + {typeof(short), OpenApiType.Integer}, + {typeof(ushort), OpenApiType.Integer}, + {typeof(int), OpenApiType.Integer}, + {typeof(uint), OpenApiType.Integer}, + {typeof(long), OpenApiType.Integer}, + {typeof(ulong), OpenApiType.Integer}, + {typeof(float), OpenApiType.Number}, + {typeof(double), OpenApiType.Number}, + {typeof(decimal), OpenApiType.Number}, + {typeof(string), OpenApiType.String}, + {typeof(DateTime), OpenApiType.String}, + {typeof(DateTimeOffset), OpenApiType.String}, + }; + + private static readonly Dictionary ClrTypesToSwaggerScalarFormats = new() + { + {typeof(byte[]), OpenApiTypeFormat.Byte}, + {typeof(sbyte[]), OpenApiTypeFormat.Byte}, + {typeof(byte), OpenApiTypeFormat.Int}, + {typeof(sbyte), OpenApiTypeFormat.Int}, + {typeof(short), OpenApiTypeFormat.Int}, + {typeof(ushort), OpenApiTypeFormat.Int}, + {typeof(int), OpenApiTypeFormat.Int}, + {typeof(uint), OpenApiTypeFormat.Int}, + {typeof(long), OpenApiTypeFormat.Long}, + {typeof(ulong), OpenApiTypeFormat.Long}, + {typeof(float), OpenApiTypeFormat.Float}, + {typeof(double), OpenApiTypeFormat.Double}, + {typeof(decimal), OpenApiTypeFormat.Double}, + {typeof(DateTime), OpenApiTypeFormat.DateTime}, + {typeof(DateTimeOffset), OpenApiTypeFormat.DateTime}, + }; + + public ConcurrentDictionary Schemas { get; } = new(); + internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); + + public OpenApiSecurityScheme? SecurityDefinition { get; set; } + public OpenApiSecurityRequirement? SecurityRequirement { get; set; } + + public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } + public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } + + /// + /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI + /// + public HashSet ExcludeRequestTypes { get; set; } = []; + + public void AddBasicAuth() + { + SecurityDefinition = OpenApiSecurity.BasicAuthScheme; + SecurityRequirement = OpenApiSecurity.BasicAuth; + } + + public void AddJwtBearer() + { + SecurityDefinition = OpenApiSecurity.JwtBearerScheme; + SecurityRequirement = OpenApiSecurity.JwtBearer; + } + + public void AddApiKeys() + { + ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; + ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; + } + + public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) + { + if (ExcludeRequestTypes.Contains(operation.RequestType)) + return op; + //Console.WriteLine($"AddOperation {verb} {route} {operation.RequestType.Name}..."); + + // Response is handled by Endpoints Metadata + op.Summary = operation.RequestType.GetDescription(); + op.Description = operation.RequestType.FirstAttribute()?.Notes; + + var hasRequestBody = HttpUtils.HasRequestBody(verb); + if (!hasRequestBody) + { + var parameters = CreateParameters(operation.RequestType, route, verb); + if (parameters.Count > 0) + { + if (op.Parameters == null) + op.Parameters = new List(); + op.Parameters.AddDistinctRange(parameters); + } + } + + var apiAttr = operation.RequestType.FirstAttribute(); + if (hasRequestBody) + { + var openApiType = CreateSchema(operation.RequestType, route, verb); + if (openApiType != null) + { + // Move path parameters from body + var inPaths = new List(); + foreach (var entry in openApiType.Properties) + { + var inPath = route.Contains("{" + entry.Key + "}", StringComparison.OrdinalIgnoreCase); + if (inPath) + { + var propNameUsed = route.Contains("{" + entry.Key + "}") + ? entry.Key + : TypeProperties.Get(operation.RequestType).GetPublicProperty(entry.Key)?.Name + ?? throw new ArgumentException($"Could not find property '{entry.Key}' for route '{route}' in Request {operation.RequestType.Name}"); + inPaths.Add(entry.Key); + IOpenApiSchema prop = entry.Value; + if (op.Parameters == null) + op.Parameters = new List(); + op.Parameters.Add(new OpenApiParameter + { + Name = propNameUsed, + In = ParameterLocation.Path, + Required = true, + Schema = prop, + Style = ParameterStyle.Simple, + Explode = true, + }); + } + } + + var formSchema = openApiType.CreateShallowCopy(); + foreach (var propName in inPaths) + { + formSchema.Properties.Remove(propName); + } + + var formType = new OpenApiMediaType + { + Schema = formSchema, + }; + foreach (var entry in formSchema.Properties) + { + formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + } + var requestBody = new OpenApiRequestBody + { + }; + var content = requestBody.Content ??= new OrderedDictionary(); + content[MimeTypes.MultiPartFormData] = formType; + if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) + { + content[MimeTypes.Json] = new OpenApiMediaType + { + Schema = ToOpenApiSchemaReference(operation.RequestType) + }; + } + op.RequestBody = requestBody; + SchemaFilter?.Invoke(op, openApiType); + } + } + + if (operation.RequiresAuthentication) + { + if (SecurityRequirement != null) + { + op.Security ??= new List(); + op.Security.Add(SecurityRequirement); + } + } + if (operation.RequiresApiKey) + { + if (ApiKeySecurityDefinition != null) + { + op.Security ??= new List(); + op.Security.Add(ApiKeySecurityRequirement); + } + } + + var userTags = operation.RequestType.AllAttributes().Map(x => x.Name); + if (userTags.Count > 0) + { + // Clear the endpoint out of the first (ServiceName) tag, so the API only appears once under its custom tag + op.Tags ??= new HashSet(); + op.Tags.Clear(); + userTags.Each(tag => op.Tags.Add(new OpenApiTagReference(tag))); + } + + OperationFilter?.Invoke(verb, op, operation); + + return op; + } + + internal static IOpenApiSchema ToOpenApiSchemaReference(Type type) => + new OpenApiSchemaReference(GetSchemaDefinitionRef(type)); + + private static bool IsKeyValuePairType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + } + + private static bool IsSwaggerScalarType(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.ContainsKey(lookupType) + || lookupType.IsEnum + || (lookupType.IsValueType && !IsKeyValuePairType(lookupType)); + } + + private static string GetSwaggerTypeName(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + + return ClrTypesToSwaggerScalarTypes.TryGetValue(lookupType, out var scalarType) + ? scalarType + : GetSchemaTypeName(lookupType); + } + + private static string GetSwaggerTypeFormat(Type type) + { + var lookupType = Nullable.GetUnderlyingType(type) ?? type; + return ClrTypesToSwaggerScalarFormats.GetValueOrDefault(lookupType); + } + + private OpenApiSchema? GetListSchema(Type schemaType) + { + if (!IsListType(schemaType)) + return null; + + var listItemType = GetListElementType(schemaType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items, + }; + } + + private static bool IsDictionaryType(Type type) + { + if (!type.IsGenericType) return false; + var genericType = type.GetGenericTypeDefinition(); + return genericType == typeof(Dictionary<,>) + || genericType == typeof(IDictionary<,>) + || genericType == typeof(IReadOnlyDictionary<,>) + || genericType == typeof(SortedDictionary<,>); + } + + private OpenApiSchema? CreateDictionarySchema(Type schemaType) + { + if (!IsDictionaryType(schemaType)) + return null; + + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + AdditionalProperties = GetOpenApiProperty(valueType) + }; + } + + private OpenApiSchema? GetKeyValuePairSchema(Type schemaType) + { + if (!IsKeyValuePairType(schemaType)) + return null; + + var keyType = schemaType.GetGenericArguments()[0]; + var valueType = schemaType.GetGenericArguments()[1]; + + return new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary + { + ["Key"] = GetOpenApiProperty(keyType), + ["Value"] = GetOpenApiProperty(valueType), + } + }; + } + + private static bool IsRequiredType(Type type) + { + return !type.IsNullableType() && type != typeof(string); + } + + public static string GetSchemaTypeName(Type schemaType) + { + if (schemaType.IsEnum) + return schemaType.Name; + + if ((!IsKeyValuePairType(schemaType) && schemaType.IsValueType) || schemaType.IsNullableType()) + return OpenApiType.String; + + if (!schemaType.IsGenericType) + return schemaType.Name; + + var typeName = schemaType.ToPrettyName(); + return typeName; + } + + private static string[]? GetEnumValues(ApiAllowableValuesAttribute? attr) + { + return attr?.Values?.ToArray(); + } + + private static Type? GetListElementType(Type type) + { + if (type.IsArray) + return type.GetElementType(); + if (!type.IsGenericType) + return null; + var genericType = type.GetGenericTypeDefinition(); + if (genericType == typeof(List<>) || genericType == typeof(IList<>) || genericType == typeof(IEnumerable<>)) + return type.GetGenericArguments()[0]; + return null; + } + + private static bool IsListType(Type type) + { + //Swagger2 specification has a special data format for type byte[] ('byte', 'binary' or 'file'), so it's not a list + if (type == typeof(byte[])) + return false; + + return GetListElementType(type) != null; + } + + private List CreateParameters(Type operationType, string route, string verb) + { + var hasDataContract = operationType.HasAttribute(); + + var properties = operationType.GetProperties(); + var paramAttrs = new Dictionary(); + var propertyTypes = new Dictionary(); + var allowableParams = new List(); + var defaultOperationParameters = new List(); + + var hasApiMembers = false; + + foreach (var property in properties) + { + if (property.HasAttribute()) + continue; + + var attr = hasDataContract + ? property.FirstAttribute() + : null; + + var propertyName = attr?.Name ?? property.Name; + + var apiMembers = property.AllAttributes(); + if (apiMembers.Length > 0) + hasApiMembers = true; + + paramAttrs[propertyName] = apiMembers; + propertyTypes[propertyName] = property.PropertyType; + var allowableValuesAttrs = property.AllAttributes(); + var allowableValuesAttr = allowableValuesAttrs.FirstOrDefault(); + allowableParams.AddRange(allowableValuesAttrs); + + if (hasDataContract && attr == null) + continue; + + var inPath = route.Contains("{" + propertyName + "}", StringComparison.OrdinalIgnoreCase); + var paramLocation = inPath + ? ParameterLocation.Path + : ParameterLocation.Query; + + var parameter = CreateParameter(property.PropertyType, + propertyName, + paramLocation, + enumValues: GetEnumValues(allowableValuesAttr)); + + defaultOperationParameters.Add(parameter); + } + + var methodOperationParameters = defaultOperationParameters; + if (hasApiMembers) + { + methodOperationParameters = []; + foreach (var key in paramAttrs.Keys) + { + var apiMembers = paramAttrs[key]; + foreach (var member in apiMembers) + { + if ((member.Verb == null || string.Compare(member.Verb, verb, StringComparison.OrdinalIgnoreCase) == 0) + && (member.Route == null || route.StartsWith(member.Route)) + && !string.Equals(member.ParameterType, "model") + && methodOperationParameters.All(x => x.Name != (member.Name ?? key))) + { + var allowableValuesAttr = allowableParams.FirstOrDefault(attr => attr.Name == (member.Name ?? key)); + var p = CreateParameter(propertyTypes[key], + member.Name ?? key, + GetParamLocation(member.GetParamType(operationType, member.Verb ?? verb)), + enumValues: GetEnumValues(allowableValuesAttr), + isApiMember:true); + // p.Type = member.DataType ?? p.Type; + // p.Format = member.Format ?? p.Format; + p.Required = p.In == ParameterLocation.Path || member.IsRequired; + p.Description = member.Description ?? p.Description; + + methodOperationParameters.Add(p); + } + } + } + } + + return methodOperationParameters; + } + + private OpenApiParameter CreateParameter(Type propType, string paramName, + ParameterLocation? paramLocation, + string[]? enumValues = null, + bool isApiMember = false) + { + if (propType.IsEnum) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = CreateEnumSchema(propType), + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsSwaggerScalarType(propType)) + { + var schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propType)), + Format = GetSwaggerTypeFormat(propType), + }; + if (enumValues != null && enumValues.Length > 0) + { + schema.Enum = enumValues.Select(x => (JsonNode)System.Text.Json.Nodes.JsonValue.Create(x)).ToList(); + } + if (!IsRequiredType(propType)) + { + ApplyNullable(schema, true); + } + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = schema, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (!isApiMember) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + }, + Required = paramLocation == ParameterLocation.Path, + }; + } + + if (IsDictionaryType(propType)) + { + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = CreateDictionarySchema(propType) + }; + } + + if (IsListType(propType)) + { + return CreateArrayParameter(propType, paramName, paramLocation); + } + + IOpenApiSchema openApiSchema; + + if (IsInlineSchema(propType)) + { + openApiSchema = CreateSchema(propType); + } + else + { + openApiSchema = ToOpenApiSchemaReference(propType); + } + + return new OpenApiParameter + { + In = paramLocation, + Name = paramName, + Schema = openApiSchema + }; + } + + private static ParameterLocation? GetParamLocation(string paramIn) + { + ParameterLocation? paramLocation = paramIn switch + { + "query" => ParameterLocation.Query, + "header" => ParameterLocation.Header, + "path" => ParameterLocation.Path, + "cookie" => ParameterLocation.Cookie, + _ => null, + }; + return paramLocation; + } + + private OpenApiParameter CreateArrayParameter(Type listType, + string paramName, + ParameterLocation? paramLocation) + { + var listItemType = GetListElementType(listType); + IOpenApiSchema? items = null; + if (listItemType != null) + { + if (IsSwaggerScalarType(listItemType)) + { + items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)) + }; + } + else + { + items = ToOpenApiSchemaReference(listItemType); + } + } + + var parameter = new OpenApiParameter + { + In = paramLocation, + Schema = new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array), + Items = items + }, + Description = listType.GetDescription(), + Name = paramName, + Required = paramLocation == ParameterLocation.Path, + Style = ParameterStyle.Form, + Explode = true, + }; + return parameter; + } + + private static string GetSchemaDefinitionRef(Type schemaType) => GetSchemaTypeName(schemaType); + + private IOpenApiSchema GetOpenApiProperty(PropertyInfo pi) + { + var schema = GetOpenApiProperty(pi.PropertyType); + if (schema is OpenApiSchema openApiSchema && pi.IsAssignableToNull()) + { + openApiSchema.Type = openApiSchema.Type.HasValue + ? openApiSchema.Type.Value | JsonSchemaType.Null + : JsonSchemaType.Null; + } + return schema; + } + + private IOpenApiSchema GetOpenApiProperty(Type propertyType) + { + var isNullable = propertyType.IsNullableType(); + propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (IsKeyValuePairType(propertyType)) + { + if (IsInlineSchema(propertyType)) + { + var schemaProp = new OpenApiSchema(); + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); + } + else + { + return ToOpenApiSchemaReference(propertyType); + } + } + else if (IsListType(propertyType)) + { + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Array) + }; + var listItemType = GetListElementType(propertyType); + if (listItemType == null) return ApplyNullable(schemaProp, isNullable); + if (IsSwaggerScalarType(listItemType)) + { + schemaProp.Items = new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(listItemType)), + Format = GetSwaggerTypeFormat(listItemType), + }; + } + else if (IsInlineSchema(listItemType)) + { + var schema = CreateSchema(listItemType); + if (schema != null) InlineSchema(schema, schemaProp); + } + else + { + schemaProp.Items = ToOpenApiSchemaReference(listItemType); + } + return ApplyNullable(schemaProp, isNullable); + } + else if (IsDictionaryType(propertyType)) + { + var schemaProp = CreateDictionarySchema(propertyType); + return ApplyNullable(schemaProp, isNullable); + } + else if (propertyType.IsEnum) + { + return ToOpenApiSchemaReference(propertyType); + } + else if (IsSwaggerScalarType(propertyType)) + { + var schemaProp = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(propertyType)), + Format = GetSwaggerTypeFormat(propertyType), + }; + var nullable = isNullable || !IsRequiredType(propertyType); + return ApplyNullable(schemaProp, nullable); + } + else if (IsInlineSchema(propertyType)) + { + var schemaProp = new OpenApiSchema(); + var schema = CreateSchema(propertyType); + if (schema != null) InlineSchema(schema, schemaProp); + return ApplyNullable(schemaProp, isNullable); + } + else + { + //CreateSchema(propertyType, route, verb); + return ToOpenApiSchemaReference(propertyType); + } + } + + private static IOpenApiSchema ApplyNullable(OpenApiSchema schema, bool isNullable) + { + if (isNullable && schema.Type.HasValue) + { + schema.Type = schema.Type.Value | JsonSchemaType.Null; + } + return schema; + } + + public static OpenApiSchema CreateEnumSchema(Type propertyType) + { + var enumType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (!enumType.IsEnum) + throw new ArgumentException(propertyType.Name + " is not an enum", nameof(propertyType)); + + var schema = new OpenApiSchema(); + if (enumType.IsNumericType()) + { + var underlyingType = Enum.GetUnderlyingType(enumType); + schema.Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(underlyingType)); + schema.Format = GetSwaggerTypeFormat(underlyingType); + schema.Enum = GetNumericValues(enumType, underlyingType).ToOpenApiEnums(); + } + else + { + schema.Type = OpenApiType.ToJsonSchemaType(OpenApiType.String); + schema.Enum = Enum.GetNames(enumType).ToOpenApiEnums(); + } + return schema; + } + + private static void InlineSchema(OpenApiSchema schemaProp, OpenApiSchema schema) + { + schemaProp.Title = schema?.Title ?? schemaProp.Title; + schemaProp.Type = schema?.Type ?? schemaProp.Type; + schemaProp.Format = schema?.Format ?? schemaProp.Format; + schemaProp.Description = schema?.Description ?? schemaProp.Description; + schemaProp.Maximum = schema?.Maximum ?? schemaProp.Maximum; + schemaProp.ExclusiveMaximum = schema?.ExclusiveMaximum ?? schemaProp.ExclusiveMaximum; + schemaProp.Minimum = schema?.Minimum ?? schemaProp.Minimum; + schemaProp.ExclusiveMinimum = schema?.ExclusiveMinimum ?? schemaProp.ExclusiveMinimum; + schemaProp.MaxLength = schema?.MaxLength ?? schemaProp.MaxLength; + schemaProp.MinLength = schema?.MinLength ?? schemaProp.MinLength; + schemaProp.Pattern = schema?.Pattern ?? schemaProp.Pattern; + schemaProp.MultipleOf = schema?.MultipleOf ?? schemaProp.MultipleOf; + schemaProp.Default = schema?.Default?.DeepClone(); + schemaProp.ReadOnly = schema?.ReadOnly ?? schemaProp.ReadOnly; + schemaProp.WriteOnly = schema?.WriteOnly ?? schemaProp.WriteOnly; + schemaProp.AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + schemaProp.OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + schemaProp.AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + schemaProp.Not = schema?.Not?.CreateShallowCopy(); + schemaProp.Required = schema?.Required != null ? new HashSet(schema.Required) : null; + schemaProp.Items = schema?.Items?.CreateShallowCopy(); + schemaProp.MaxItems = schema?.MaxItems ?? schemaProp.MaxItems; + schemaProp.MinItems = schema?.MinItems ?? schemaProp.MinItems; + schemaProp.UniqueItems = schema?.UniqueItems ?? schemaProp.UniqueItems; + schemaProp.Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + schemaProp.MaxProperties = schema?.MaxProperties ?? schemaProp.MaxProperties; + schemaProp.MinProperties = schema?.MinProperties ?? schemaProp.MinProperties; + schemaProp.AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? schemaProp.AdditionalPropertiesAllowed; + schemaProp.AdditionalProperties = schema?.AdditionalProperties?.CreateShallowCopy(); + schemaProp.Discriminator = schema?.Discriminator != null ? new(schema.Discriminator) : null; + // In Microsoft.OpenApi 3.x, Example is read-only for some implementations; preserve existing example if available + // but avoid assigning directly when it's not supported. + if (schema?.Example is JsonNode exampleNode) + schemaProp.Example = exampleNode.DeepClone(); + schemaProp.Enum = schema?.Enum != null ? new List(schema.Enum) : null; + schemaProp.ExternalDocs = schema?.ExternalDocs != null ? new(schema.ExternalDocs) : null; + schemaProp.Deprecated = schema?.Deprecated ?? schemaProp.Deprecated; + schemaProp.Xml = schema?.Xml != null ? new(schema.Xml) : null; + } + + private bool IsInlineSchema(Type schemaType) + { + return schemaType.Namespace != null && InlineSchemaTypesInNamespaces.Contains(schemaType.Namespace); + } + + List RequiredValidators { get; } = ["NotNull", "NotEmpty"]; + + public OpenApiSchema? CreateSchema(Type schemaType, string? route=null, string? verb=null, HashSet? allTypes = null) + { + if (schemaType.ExcludesFeature(Feature.Metadata) || schemaType.ExcludesFeature(Feature.ApiExplorer)) + return null; + + if (IsSwaggerScalarType(schemaType) && !schemaType.IsEnum) + return null; + + var schemaId = GetSchemaDefinitionRef(schemaType); + if (Schemas.TryGetValue(schemaId, out var schema)) + return schema; + + schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType); + + bool parseProperties = false; + if (schema == null) + { + if (schemaType.IsEnum) + { + schema = CreateEnumSchema(schemaType); + } + else + { + schema = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + Title = GetSchemaTypeName(schemaType), + Description = schemaType.GetDescription() ?? GetSchemaTypeName(schemaType), + Properties = new OrderedDictionary() + }; + parseProperties = schemaType.IsUserType(); + } + + if (allTypes != null && schemaType.BaseType != null && allTypes.Contains(schemaType.BaseType)) + { + schema.AllOf ??= new List(); + schema.AllOf.Add(ToOpenApiSchemaReference(schemaType.BaseType)); + } + } + Schemas[schemaId] = schema; + + var properties = schemaType.GetProperties() + .Where(pi => !SwaggerUtils.IgnoreProperty(pi)) + .ToArray(); + + // Order schema properties by DataMember.Order if [DataContract] and [DataMember](s) defined + // Ordering defined by: http://msdn.microsoft.com/en-us/library/ms729813.aspx + var dataContractAttr = schemaType.FirstAttribute(); + if (dataContractAttr != null && properties.Any(prop => prop.IsDefined(typeof(DataMemberAttribute), true))) + { + var typeOrder = new List { schemaType }; + var baseType = schemaType.BaseType; + while (baseType != null) + { + typeOrder.Add(baseType); + baseType = baseType.BaseType; + } + + var propsWithDataMember = properties.Where(prop => prop.IsDefined(typeof(DataMemberAttribute), true)); + var propDataMemberAttrs = properties.ToDictionary(prop => prop, prop => prop.FirstAttribute()); + + properties = propsWithDataMember + .OrderBy(prop => propDataMemberAttrs[prop].Order) // Order by DataMember.Order + .ThenByDescending(prop => typeOrder.IndexOf(prop.DeclaringType)) // Then by BaseTypes First + .ThenBy(prop => // Then by [DataMember].Name / prop.Name + { + var name = propDataMemberAttrs[prop].Name; + return name.IsNullOrEmpty() ? prop.Name : name; + }).ToArray(); + } + + if (parseProperties) + { + foreach (var prop in properties) + { + if (prop.HasAttributeOf()) + continue; + + var apiMembers = prop + .AllAttributes() + .OrderByDescending(attr => attr.Route) + .ToList(); + var apiDoc = apiMembers + .Where(attr => string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(attr.Verb) || (verb ?? "").Equals(attr.Verb)) + .Where(attr => string.IsNullOrEmpty(route) || string.IsNullOrEmpty(attr.Route) || (route ?? "").StartsWith(attr.Route)) + .FirstOrDefault(attr => attr.ParameterType is "body" or "model"); + + if (apiMembers.Any(x => x.ExcludeInSchema)) + continue; + var schemaProperty = GetOpenApiProperty(prop); + var schemaPropertyName = GetSchemaPropertyName(prop); + + if (schemaProperty is OpenApiSchema openApiSchema) + { + openApiSchema.Description = prop.GetDescription() ?? apiDoc?.Description; + + var propAttr = prop.FirstAttribute(); + var validateAttrs = prop.AllAttributes(); + + var isRequired = propAttr?.IsRequired == true + || validateAttrs.Any(x => RequiredValidators.Contains(x.Validator)) + || (prop.PropertyType.IsNumericType() && validateAttrs.Any(attr => attr.Validator?.StartsWith("GreaterThan") == true)); + + if (propAttr != null) + { + if (propAttr.DataType != null) + openApiSchema.Type = OpenApiType.ToJsonSchemaType(propAttr.DataType); + + if (propAttr.Format != null) + openApiSchema.Format = propAttr.Format; + } + + if (isRequired) + { + schema.Required ??= new HashSet(); + schema.Required.Add(schemaPropertyName); + } + + var uploadTo = prop.FirstAttribute(); + if (uploadTo != null) + { + if (openApiSchema.Type != OpenApiType.ToJsonSchemaType(OpenApiType.Array)) + { + openApiSchema.Type = JsonSchemaType.String; // "file" type doesn't exist in JsonSchemaType + } + openApiSchema.Items = new OpenApiSchema + { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.String), + Format = OpenApiTypeFormat.Binary, + }; + } + + openApiSchema.Enum = GetEnumValues(prop.FirstAttribute()).ToOpenApiEnums(); + + SchemaPropertyFilter?.Invoke(openApiSchema); + } + schema.Properties[schemaPropertyName] = schemaProperty; + } + } + return schema; + } + + private static string GetSchemaPropertyName(PropertyInfo prop) + { + var dataMemberAttr = prop.FirstAttribute(); + if (dataMemberAttr?.Name != null) + return dataMemberAttr.Name; + + return JsConfig.TextCase == TextCase.CamelCase + ? prop.Name.ToCamelCase() + : JsConfig.TextCase == TextCase.SnakeCase + ? prop.Name.ToLowercaseUnderscore() + : prop.Name; + } + + private static List GetNumericValues(Type propertyType, Type underlyingType) + { + var values = Enum.GetValues(propertyType) + .Map(x => $"{Convert.ChangeType(x, underlyingType)} ({x})"); + + return values; + } + + private OpenApiSchema? GetResponseSchema(IRestPath restPath, out string schemaDescription) + { + schemaDescription = string.Empty; + + // Given: class MyDto : IReturn. Determine the type X. + foreach (var i in restPath.RequestType.GetInterfaces()) + { + if (i == typeof(IReturnVoid)) + return GetSchemaForResponseType(typeof(void), out schemaDescription); + + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>)) + { + var schemaType = i.GetGenericArguments()[0]; + return GetSchemaForResponseType(schemaType, out schemaDescription); + } + } + + return new OpenApiSchema { + Type = OpenApiType.ToJsonSchemaType(OpenApiType.Object), + }; + } + + private OpenApiSchema? GetSchemaForResponseType(Type schemaType, out string schemaDescription) + { + if (schemaType == typeof(IReturnVoid) || schemaType == typeof(void)) + { + schemaDescription = "No Content"; + return null; + } + + OpenApiSchema? schema = CreateDictionarySchema(schemaType) + ?? GetKeyValuePairSchema(schemaType) + ?? GetListSchema(schemaType); + + if (schema == null) + { + if (IsSwaggerScalarType(schemaType)) + { + schema = new OpenApiSchema + { + Title = GetSchemaTypeName(schemaType), + Type = OpenApiType.ToJsonSchemaType(GetSwaggerTypeName(schemaType)), + Format = GetSwaggerTypeFormat(schemaType) + }; + } + else if (IsInlineSchema(schemaType)) + { + schema = CreateSchema(schemaType); + } + else + { + // For references, we need to return a schema that references the type + // In v3.0, we can't use OpenApiSchema with Reference property + // Instead, we should use OpenApiSchemaReference, but since the return type is OpenApiSchema?, + // we'll create a schema with AllOf containing the reference + schema = new OpenApiSchema + { + AllOf = new List { ToOpenApiSchemaReference(schemaType) } + }; + } + } + + schemaDescription = schema?.Description ?? schemaType.GetDescription() ?? string.Empty; + + return schema; + } + + internal OrderedDictionary GetMethodResponseCodes(IRestPath restPath, IDictionary schemas, Type requestType) + { + var responses = new OrderedDictionary(); + + var responseSchema = GetResponseSchema(restPath, out string schemaDescription); + //schema is null when return type is IReturnVoid + var statusCode = responseSchema == null && HostConfig.Instance.Return204NoContentForEmptyResponse + ? ((int)HttpStatusCode.NoContent).ToString() + : ((int)HttpStatusCode.OK).ToString(); + + var response = new OpenApiResponse + { + Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" + }; + var content = response.Content ??= new OrderedDictionary(); + content[MimeTypes.Json] = new OpenApiMediaType + { + Schema = responseSchema, + }; + responses.Add(statusCode, response); + + foreach (var attr in requestType.AllAttributes()) + { + string apiSchemaDescription = string.Empty; + + var apiResponse = new OpenApiResponse + { + Description = attr.Description ?? apiSchemaDescription + }; + var apiContent = apiResponse.Content ??= new OrderedDictionary(); + apiContent[MimeTypes.Json] = new OpenApiMediaType + { + Schema = attr.ResponseType != null + ? GetSchemaForResponseType(attr.ResponseType, out apiSchemaDescription) + : responseSchema, + }; + + statusCode = attr.IsDefaultResponse ? "default" : attr.StatusCode.ToString(); + if (!responses.ContainsKey(statusCode)) + responses.Add(statusCode, apiResponse); + else + responses[statusCode] = apiResponse; + } + + return responses; + } + +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs new file mode 100644 index 00000000000..17178581051 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs @@ -0,0 +1,24 @@ +using Microsoft.OpenApi; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiType +{ + public const string Array = "array"; + public const string Boolean = "boolean"; + public const string Number = "number"; + public const string Integer = "integer"; + public const string String = "string"; + public const string Object = "object"; + + public static JsonSchemaType ToJsonSchemaType(string type) => type switch + { + Array => JsonSchemaType.Array, + Boolean => JsonSchemaType.Boolean, + Number => JsonSchemaType.Number, + Integer => JsonSchemaType.Integer, + String => JsonSchemaType.String, + Object => JsonSchemaType.Object, + _ => JsonSchemaType.String + }; +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs new file mode 100644 index 00000000000..3ebd7db4e97 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs @@ -0,0 +1,15 @@ +namespace ServiceStack.AspNetCore.OpenApi; + +public static class OpenApiTypeFormat +{ + public const string Array = "int32"; + public const string Byte = "byte"; + public const string Binary = "binary"; + public const string Date = "date"; + public const string DateTime = "date-time"; + public const string Double = "double"; + public const string Float = "float"; + public const string Int = "int32"; + public const string Long = "int64"; + public const string Password = "password"; +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj new file mode 100644 index 00000000000..240d117fdb2 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + ServiceStack.AspNetCore.OpenApi + + + + + + + + + + + + + + + + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs new file mode 100644 index 00000000000..2dd8ce06ae3 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs @@ -0,0 +1,201 @@ +using System.Text.Json.Serialization; +using Microsoft.OpenApi; +using ServiceStack.Host; +using ServiceStack.Web; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack.AspNetCore.OpenApi; + +// Last OpenApi Filter to run +public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFilter +{ + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + //Console.WriteLine(GetType().Name + "..."); + if (metadata.SecurityDefinition != null) + { + swaggerDoc.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; + } + + if (metadata.ApiKeySecurityDefinition != null) + { + swaggerDoc.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; + } + + // Ensure we have a Paths collection to populate + swaggerDoc.Paths ??= new OpenApiPaths(); + + var restPathMap = HostContext.ServiceController.RestPathMap; + + // Build schemas for all DTOs used by ServiceStack operations + var operations = HostContext.Metadata.OperationsMap.Values.ToList(); + var dtos = new HashSet(); + foreach (var op in operations) + { + if (IsDtoTypeOrEnum(op.RequestType)) + { + AddReferencedTypes(dtos, op.RequestType, IsDtoTypeOrEnum, includeBaseTypes: HttpUtils.HasRequestBody(op.Method)); + } + + if (op.ResponseType != null && IsDtoTypeOrEnum(op.ResponseType)) + { + // Ensure response DTOs like HelloResponse are also added to components/schemas + AddReferencedTypes(dtos, op.ResponseType, IsDtoTypeOrEnum, includeBaseTypes: true); + } + } + + var orderedDtos = dtos.OrderBy(x => x.Name); + foreach (var type in orderedDtos) + { + var schema = metadata.CreateSchema(type, allTypes: dtos); + if (schema != null) + { + swaggerDoc.Components.Schemas[OpenApiMetadata.GetSchemaTypeName(type)] = schema; + } + } + + // Populate operations/paths from ServiceStack metadata + foreach (var restPathList in restPathMap.Values) + { + foreach (var restPath in restPathList) + { + var verbs = new List(); + verbs.AddRange(restPath.AllowsAllVerbs + ? new[] { HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete } + : restPath.Verbs); + + var routePath = restPath.Path.Replace("*", string.Empty); + var requestType = restPath.RequestType; + if (!HostContext.Metadata.OperationsMap.TryGetValue(requestType, out var opMeta)) + continue; + + if (metadata.Ignore?.Invoke(opMeta) == true) + continue; + + // Skip operations that exclude metadata or API explorer features + if (requestType.ExcludesFeature(Feature.Metadata) || requestType.ExcludesFeature(Feature.ApiExplorer)) + continue; + + // Swashbuckle expects route templates without leading ~/ and with {id} style params + var swaggerPath = routePath.StartsWith("~/") + ? routePath.Substring(1) + : routePath; + + if (!swaggerDoc.Paths.TryGetValue(swaggerPath, out var pathItem)) + { + pathItem = new OpenApiPathItem(); + swaggerDoc.Paths[swaggerPath] = pathItem; + } + + // Apply each verb for this RestPath + foreach (var verb in verbs) + { + var openApiOp = new OpenApiOperation + { + OperationId = $"{requestType.Name}{verb}{swaggerPath.Replace("/", "_").Replace("{", "_").Replace("}", string.Empty)}", + }; + + openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath); + + // Responses + var responses = GetResponses(metadata, restPath, requestType); + foreach (var entry in responses) + { + openApiOp.Responses[entry.Key] = entry.Value; + } + + // Tags from [Tag] attributes if any + var userTags = requestType.AllAttributes().Map(x => x.Name); + foreach (var tag in userTags) + { + openApiOp.Tags ??= new HashSet(); + openApiOp.Tags.Add(new OpenApiTagReference(tag)); + } + + // Map verb to HttpMethod and add operation to path + HttpMethod? httpMethod = verb switch + { + HttpMethods.Get => HttpMethod.Get, + HttpMethods.Post => HttpMethod.Post, + HttpMethods.Put => HttpMethod.Put, + HttpMethods.Delete => HttpMethod.Delete, + HttpMethods.Patch => HttpMethod.Patch, + HttpMethods.Head => HttpMethod.Head, + HttpMethods.Options => HttpMethod.Options, + _ => null + }; + + if (httpMethod != null && pathItem is OpenApiPathItem concretePathItem) + { + concretePathItem.AddOperation(httpMethod, openApiOp); + } + } + } + } + } + + private static OrderedDictionary GetResponses(OpenApiMetadata metadata, IRestPath restPath, Type requestType) + { + // OpenApiMetadata already knows how to build response schemas using the v3 models + var schemas = metadata.Schemas.ToDictionary(x => x.Key, x => x.Value); + return metadata.GetMethodResponseCodes(restPath, schemas, requestType); + } + + public static void AddReferencedTypes(HashSet to, Type? type, Func include, bool includeBaseTypes) + { + if (type == null || to.Contains(type) || !include(type)) + return; + + to.Add(type); + + var baseType = type.BaseType; + if (includeBaseTypes && baseType != null && include(baseType) && !to.Contains(baseType)) + { + AddReferencedTypes(to, baseType, include, includeBaseTypes); + + var genericArgs = type.IsGenericType + ? type.GetGenericArguments() + : Type.EmptyTypes; + + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + + foreach (var pi in type.GetSerializableProperties()) + { + // Skip Obsolete properties + if (SwaggerUtils.IgnoreProperty(pi)) + continue; + + if (to.Contains(pi.PropertyType)) + continue; + + if (include(pi.PropertyType)) + AddReferencedTypes(to, pi.PropertyType, include, includeBaseTypes); + + var genericArgs = pi.PropertyType.IsGenericType + ? pi.PropertyType.GetGenericArguments() + : Type.EmptyTypes; + + if (genericArgs.Length > 0) + { + foreach (var arg in genericArgs) + { + AddReferencedTypes(to, arg, include, includeBaseTypes); + } + } + else if (pi.PropertyType.IsArray) + { + var elType = pi.PropertyType.HasElementType ? pi.PropertyType.GetElementType() : null; + AddReferencedTypes(to, elType, include, includeBaseTypes); + } + } + } + + public static bool IsDtoTypeOrEnum(Type? type) => type != null + && (ServiceMetadata.IsDtoType(type) || type.IsEnum) + && !type.IsGenericTypeDefinition + && !(type.ExcludesFeature(Feature.Metadata) || type.ExcludesFeature(Feature.ApiExplorer)); +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs new file mode 100644 index 00000000000..7728284e552 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs @@ -0,0 +1,73 @@ +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using ServiceStack.AspNetCore.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace ServiceStack; + +public static class ServiceStackOpenApiExtensions +{ + public static void WithOpenApi(this ServiceStackOptions options) + { + // In the OpenApi3 package we no longer require ASP.NET Core Endpoint Routing + // for OpenAPI document generation. ServiceStack operations are wired into + // the OpenAPI document via ServiceStackDocumentFilter + OpenApiMetadata + // using Swashbuckle, based on ServiceStack's own metadata. + // + // This hook is kept for symmetry with the OpenApi (v1/v2) package and as + // a future extension point if additional ServiceStackOptions-based + // configuration is needed, but it's currently a no-op. + } + + public static void AddSwagger(this ServiceStackServicesOptions options, Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + options.Services!.AddSingleton(OpenApiMetadata.Instance); + options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); + options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); + + options.Services!.ConfigurePlugin(feature => { + feature.AddPluginLink("/swagger/index.html", "Swagger UI"); + }); + } + + public static void AddServiceStackSwagger(this IServiceCollection services, Action? configure = null) + { + configure?.Invoke(OpenApiMetadata.Instance); + + services.AddSingleton(OpenApiMetadata.Instance); + services.AddSingleton, ConfigureServiceStackSwagger>(); + services.AddSingleton, ConfigureServiceStackSwagger>(); + + services.ConfigurePlugin(feature => { + feature.AddPluginLink("/swagger/index.html", "Swagger UI"); + }); + } + + public static AuthenticationBuilder AddBasicAuth(this IServiceCollection services) + where TUser : IdentityUser, new() { + OpenApiMetadata.Instance.AddBasicAuth(); + return new AuthenticationBuilder(services).AddBasicAuth(); + } + + public static void AddJwtAuth(this IServiceCollection services) { + OpenApiMetadata.Instance.AddJwtBearer(); + } + + public static void AddBasicAuth(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.BasicAuthScheme.Scheme, OpenApiSecurity.BasicAuthScheme); + + public static void AddJwtAuth(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.JwtBearerScheme.Scheme, OpenApiSecurity.JwtBearerScheme); + + public static void AddApiKeys(this SwaggerGenOptions options) => + options.AddSecurityDefinition(OpenApiSecurity.ApiKeyScheme.Scheme, OpenApiSecurity.ApiKeyScheme); + + internal static List ToOpenApiEnums(this IEnumerable? enums) => + enums.Safe().Map(x => (JsonNode)JsonValue.Create(x)); +} diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs new file mode 100644 index 00000000000..817d24a0454 --- /dev/null +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Text.Json.Serialization; + +namespace ServiceStack.AspNetCore.OpenApi; + +public static class SwaggerUtils +{ + public static Func IgnoreProperty { get; set; } = DefaultIgnoreProperty; + + public static bool DefaultIgnoreProperty(PropertyInfo pi) + { + var propAttrs = pi.AllAttributes(); + return propAttrs.Any(x => x is ObsoleteAttribute or JsonIgnoreAttribute + or Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute); + } +} diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index a29e2a03efa..1f384d3bb47 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -133,6 +133,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdhocNew", "..\tests\AdhocN EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Desktop", "ServiceStack.Desktop\ServiceStack.Desktop.csproj", "{6FFE8429-51F7-45C5-9402-FBA0FC216B85}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.AspNetCore.OpenApi3", "ServiceStack.AspNetCore.OpenApi3\ServiceStack.AspNetCore.OpenApi3.csproj", "{D712182C-B0C3-4797-A576-3AF725F13660}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi3Swashbuckle", "..\tests\OpenApi3Swashbuckle\OpenApi3Swashbuckle.csproj", "{ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiScalar", "..\tests\OpenApiScalar\OpenApiScalar.csproj", "{D0024195-711E-4B32-8F39-40D34BFDB57B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.AspNetCore.OpenApi.Microsoft", "ServiceStack.AspNetCore.OpenApi.Microsoft\ServiceStack.AspNetCore.OpenApi.Microsoft.csproj", "{03EDA213-6C39-4DB9-BF03-DEB67BDF3286}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -809,6 +817,54 @@ Global {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|Mixed Platforms.Build.0 = Release|Any CPU {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|x86.ActiveCfg = Release|Any CPU {6FFE8429-51F7-45C5-9402-FBA0FC216B85}.Release|x86.Build.0 = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|x86.ActiveCfg = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Debug|x86.Build.0 = Debug|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|Any CPU.Build.0 = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|x86.ActiveCfg = Release|Any CPU + {D712182C-B0C3-4797-A576-3AF725F13660}.Release|x86.Build.0 = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Debug|x86.Build.0 = Debug|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|Any CPU.Build.0 = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|x86.ActiveCfg = Release|Any CPU + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}.Release|x86.Build.0 = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|x86.ActiveCfg = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Debug|x86.Build.0 = Debug|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|Any CPU.Build.0 = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|x86.ActiveCfg = Release|Any CPU + {D0024195-711E-4B32-8F39-40D34BFDB57B}.Release|x86.Build.0 = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|x86.ActiveCfg = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Debug|x86.Build.0 = Debug|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|Any CPU.Build.0 = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|x86.ActiveCfg = Release|Any CPU + {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -848,6 +904,8 @@ Global {D3C4AB4F-7C68-447D-B143-088E29AD1576} = {515043B6-1FFD-4CBF-8916-23B82C67CF8C} {86A8E669-E998-4ADD-83EE-89DF91C7A470} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} {0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} + {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} + {D0024195-711E-4B32-8F39-40D34BFDB57B} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55441DE1-559F-4064-BB8F-907A9E898A6C} diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index 7d873ae1abf..ba768e59f06 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -25,8 +25,10 @@ - - + + + + diff --git a/ServiceStack/tests/AdhocNew/swagger.json b/ServiceStack/tests/AdhocNew/swagger.json new file mode 100644 index 00000000000..48284c4f88a --- /dev/null +++ b/ServiceStack/tests/AdhocNew/swagger.json @@ -0,0 +1,2788 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "AdhocNew", + "version": "1.0" + }, + "paths": { + "/hello/{Name}": { + "get": { + "tags": [ + "AdhocNew" + ], + "parameters": [ + { + "name": "Name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloResponse" + } + } + } + } + } + } + }, + "/v1/chat/completions": { + "post": { + "tags": [ + "AI" + ], + "summary": "Chat Completions API (OpenAI-Compatible)", + "description": "The industry-standard, message-based interface for interfacing with Large Language Models.", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "ChatCompletion", + "type": "object", + "properties": { + "audio": { + "$ref": "#/components/schemas/AiChatAudio" + }, + "enable_thinking": { + "type": "boolean", + "description": "Whether to enable thinking mode for some Qwen models and providers.", + "nullable": true + }, + "frequency_penalty": { + "type": "number", + "description": "Number between `-2.0` and `2.0`. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + "format": "double", + "nullable": true + }, + "logit_bias": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + }, + "description": "Modify the likelihood of specified tokens appearing in the completion.", + "nullable": true + }, + "logprobs": { + "type": "boolean", + "description": "Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the content of message.", + "nullable": true + }, + "max_completion_tokens": { + "type": "integer", + "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.", + "format": "int32", + "nullable": true + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AiMessage" + }, + "description": "The messages to generate chat completions for." + }, + "metadata": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", + "nullable": true + }, + "modalities": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output types that you would like the model to generate. Most models are capable of generating text, which is the default:", + "nullable": true + }, + "model": { + "type": "string", + "description": "ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API" + }, + "n": { + "type": "integer", + "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", + "format": "int32", + "nullable": true + }, + "parallel_tool_calls": { + "type": "boolean", + "description": "Whether to enable parallel function calling during tool use.", + "nullable": true + }, + "presence_penalty": { + "type": "number", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", + "format": "double", + "nullable": true + }, + "prompt_cache_key": { + "type": "string", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.", + "nullable": true + }, + "reasoning_effort": { + "type": "string", + "description": "Constrains effort on reasoning for reasoning models. Currently supported values are minimal, low, medium, and high (none, default). Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.", + "nullable": true + }, + "response_format": { + "$ref": "#/components/schemas/AiResponseFormat" + }, + "safety_identifier": { + "type": "string", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.", + "nullable": true + }, + "seed": { + "type": "integer", + "description": "This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend.", + "format": "int32", + "nullable": true + }, + "service_tier": { + "type": "string", + "description": "Specifies the processing type used for serving the request.", + "nullable": true + }, + "stop": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Up to 4 sequences where the API will stop generating further tokens.", + "nullable": true + }, + "store": { + "type": "boolean", + "description": "Whether or not to store the output of this chat completion request for use in our model distillation or evals products.", + "nullable": true + }, + "stream": { + "type": "boolean", + "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a `data: [DONE]` message.", + "nullable": true + }, + "temperature": { + "type": "number", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.", + "format": "double", + "nullable": true + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tool" + }, + "description": "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.", + "nullable": true + }, + "top_logprobs": { + "type": "integer", + "description": "An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. logprobs must be set to true if this parameter is used.", + "format": "int32", + "nullable": true + }, + "top_p": { + "type": "number", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.", + "format": "double", + "nullable": true + }, + "verbosity": { + "type": "string", + "description": "Constrains the verbosity of the model's response. Lower values will result in more concise responses, while higher values will result in more verbose responses. Currently supported values are low, medium, and high.", + "nullable": true + } + }, + "description": "Chat Completions API (OpenAI-Compatible)" + }, + "encoding": { + "audio": { + "style": "form", + "explode": false + }, + "enable_thinking": { + "style": "form", + "explode": false + }, + "frequency_penalty": { + "style": "form", + "explode": false + }, + "logit_bias": { + "style": "form", + "explode": false + }, + "logprobs": { + "style": "form", + "explode": false + }, + "max_completion_tokens": { + "style": "form", + "explode": false + }, + "messages": { + "style": "form", + "explode": false + }, + "metadata": { + "style": "form", + "explode": false + }, + "modalities": { + "style": "form", + "explode": false + }, + "model": { + "style": "form", + "explode": false + }, + "n": { + "style": "form", + "explode": false + }, + "parallel_tool_calls": { + "style": "form", + "explode": false + }, + "presence_penalty": { + "style": "form", + "explode": false + }, + "prompt_cache_key": { + "style": "form", + "explode": false + }, + "reasoning_effort": { + "style": "form", + "explode": false + }, + "response_format": { + "style": "form", + "explode": false + }, + "safety_identifier": { + "style": "form", + "explode": false + }, + "seed": { + "style": "form", + "explode": false + }, + "service_tier": { + "style": "form", + "explode": false + }, + "stop": { + "style": "form", + "explode": false + }, + "store": { + "style": "form", + "explode": false + }, + "stream": { + "style": "form", + "explode": false + }, + "temperature": { + "style": "form", + "explode": false + }, + "tools": { + "style": "form", + "explode": false + }, + "top_logprobs": { + "style": "form", + "explode": false + }, + "top_p": { + "style": "form", + "explode": false + }, + "verbosity": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCompletion" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatResponse" + } + } + } + } + } + } + }, + "/auth/{provider}": { + "post": { + "tags": [ + "auth" + ], + "summary": "Sign In", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "style": "simple", + "explode": true, + "schema": { + "type": "string", + "description": "AuthProvider, e.g. credentials" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "Authenticate", + "type": "object", + "properties": { + "userName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "rememberMe": { + "type": "boolean", + "nullable": true + }, + "accessToken": { + "type": "string" + }, + "accessTokenSecret": { + "type": "string" + }, + "returnUrl": { + "type": "string" + }, + "errorView": { + "type": "string" + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + "description": "Sign In" + }, + "encoding": { + "userName": { + "style": "form", + "explode": false + }, + "password": { + "style": "form", + "explode": false + }, + "rememberMe": { + "style": "form", + "explode": false + }, + "accessToken": { + "style": "form", + "explode": false + }, + "accessTokenSecret": { + "style": "form", + "explode": false + }, + "returnUrl": { + "style": "form", + "explode": false + }, + "errorView": { + "style": "form", + "explode": false + }, + "meta": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Authenticate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticateResponse" + } + } + } + } + } + } + }, + "/auth": { + "get": { + "tags": [ + "auth" + ], + "summary": "Sign In", + "parameters": [ + { + "name": "provider", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "UserName", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Password", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "RememberMe", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AccessToken", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AccessTokenSecret", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "ReturnUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "ErrorView", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Meta", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticateResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "auth" + ], + "summary": "Sign In", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "Authenticate", + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "AuthProvider, e.g. credentials" + }, + "userName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "rememberMe": { + "type": "boolean", + "nullable": true + }, + "accessToken": { + "type": "string" + }, + "accessTokenSecret": { + "type": "string" + }, + "returnUrl": { + "type": "string" + }, + "errorView": { + "type": "string" + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + "description": "Sign In" + }, + "encoding": { + "provider": { + "style": "form", + "explode": false + }, + "userName": { + "style": "form", + "explode": false + }, + "password": { + "style": "form", + "explode": false + }, + "rememberMe": { + "style": "form", + "explode": false + }, + "accessToken": { + "style": "form", + "explode": false + }, + "accessTokenSecret": { + "style": "form", + "explode": false + }, + "returnUrl": { + "style": "form", + "explode": false + }, + "errorView": { + "style": "form", + "explode": false + }, + "meta": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Authenticate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticateResponse" + } + } + } + } + } + } + }, + "/bookings": { + "get": { + "tags": [ + "bookings" + ], + "summary": "Find Bookings", + "description": "Find out how to quickly create a C# Bookings App from Scratch", + "parameters": [ + { + "name": "Id", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "Skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "Take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "OrderBy", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "OrderByDesc", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Include", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Fields", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Meta", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BookingQueryResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "bookings" + ], + "summary": "Create a new Booking", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "CreateBooking", + "required": [ + "name", + "roomNumber", + "cost" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name this Booking is for" + }, + "roomType": { + "$ref": "#/components/schemas/RoomType" + }, + "roomNumber": { + "type": "integer", + "format": "int32" + }, + "cost": { + "type": "number", + "format": "double" + }, + "bookingStartDate": { + "type": "string", + "format": "date-time" + }, + "bookingEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "notes": { + "type": "string", + "nullable": true + }, + "couponId": { + "type": "string", + "nullable": true + } + }, + "description": "Create a new Booking" + }, + "encoding": { + "name": { + "style": "form", + "explode": false + }, + "roomType": { + "style": "form", + "explode": false + }, + "roomNumber": { + "style": "form", + "explode": false + }, + "cost": { + "style": "form", + "explode": false + }, + "bookingStartDate": { + "style": "form", + "explode": false + }, + "bookingEndDate": { + "style": "form", + "explode": false + }, + "notes": { + "style": "form", + "explode": false + }, + "couponId": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBooking" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdResponse" + } + } + } + } + }, + "security": [ + { + "basic": [ ] + } + ] + } + }, + "/bookings/{Id}": { + "get": { + "tags": [ + "bookings" + ], + "summary": "Find Bookings", + "description": "Find out how to quickly create a C# Bookings App from Scratch", + "parameters": [ + { + "name": "Id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "Skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "Take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "OrderBy", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "OrderByDesc", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Include", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Fields", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Meta", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BookingQueryResponse" + } + } + } + } + } + } + }, + "/coupons": { + "get": { + "tags": [ + "bookings" + ], + "summary": "Find Coupons", + "parameters": [ + { + "name": "Id", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "Take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "OrderBy", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "OrderByDesc", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Include", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Fields", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Meta", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouponQueryResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "bookings" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "CreateCoupon", + "required": [ + "id", + "description", + "discount", + "expiryDate" + ], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "discount": { + "type": "integer", + "format": "int32" + }, + "expiryDate": { + "type": "string", + "format": "date-time" + } + }, + "description": "CreateCoupon" + }, + "encoding": { + "id": { + "style": "form", + "explode": false + }, + "description": { + "style": "form", + "explode": false + }, + "discount": { + "style": "form", + "explode": false + }, + "expiryDate": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCoupon" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdResponse" + } + } + } + } + }, + "security": [ + { + "basic": [ ] + } + ] + } + }, + "/booking/{Id}": { + "patch": { + "tags": [ + "bookings" + ], + "summary": "Update an existing Booking", + "description": "Find out how to quickly create a C# Bookings App from Scratch", + "parameters": [ + { + "name": "Id", + "in": "path", + "required": true, + "style": "simple", + "explode": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "UpdateBooking", + "required": [ + "roomNumber", + "cost" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "roomType": { + "$ref": "#/components/schemas/RoomType" + }, + "roomNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "cost": { + "type": "number", + "format": "double", + "nullable": true + }, + "bookingStartDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "bookingEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "notes": { + "type": "string", + "nullable": true + }, + "couponId": { + "type": "string", + "nullable": true + }, + "cancelled": { + "type": "boolean", + "nullable": true + } + }, + "description": "Update an existing Booking" + }, + "encoding": { + "name": { + "style": "form", + "explode": false + }, + "roomType": { + "style": "form", + "explode": false + }, + "roomNumber": { + "style": "form", + "explode": false + }, + "cost": { + "style": "form", + "explode": false + }, + "bookingStartDate": { + "style": "form", + "explode": false + }, + "bookingEndDate": { + "style": "form", + "explode": false + }, + "notes": { + "style": "form", + "explode": false + }, + "couponId": { + "style": "form", + "explode": false + }, + "cancelled": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateBooking" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdResponse" + } + } + } + } + }, + "security": [ + { + "basic": [ ] + } + ] + }, + "delete": { + "tags": [ + "bookings" + ], + "summary": "Delete a Booking", + "parameters": [ + { + "name": "Id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "basic": [ ] + } + ] + } + }, + "/coupons/{Id}": { + "patch": { + "tags": [ + "bookings" + ], + "parameters": [ + { + "name": "Id", + "in": "path", + "required": true, + "style": "simple", + "explode": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "UpdateCoupon", + "required": [ + "description", + "discount", + "expiryDate" + ], + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true + }, + "discount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "expiryDate": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "description": "UpdateCoupon" + }, + "encoding": { + "description": { + "style": "form", + "explode": false + }, + "discount": { + "style": "form", + "explode": false + }, + "expiryDate": { + "style": "form", + "explode": false + } + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCoupon" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdResponse" + } + } + } + } + }, + "security": [ + { + "basic": [ ] + } + ] + }, + "delete": { + "tags": [ + "bookings" + ], + "summary": "Delete a Coupon", + "parameters": [ + { + "name": "Id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "basic": [ ] + } + ] + } + } + }, + "components": { + "schemas": { + "AdminData": { + "title": "AdminData", + "type": "object", + "description": "AdminData" + }, + "AiChatAudio": { + "title": "AiChatAudio", + "type": "object", + "properties": { + "format": { + "type": "string", + "description": "Specifies the output audio format. Must be one of wav, mp3, flac, opus, or pcm16." + }, + "voice": { + "type": "string", + "description": "The voice the model uses to respond. Supported voices are alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, and shimmer." + } + }, + "description": "Parameters for audio output. Required when audio output is requested with modalities: [audio]" + }, + "AiCompletionUsage": { + "type": "object", + "properties": { + "acceptedPredictionTokens": { + "type": "integer", + "description": "When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.\n\n", + "format": "int32" + }, + "audioTokens": { + "type": "integer", + "description": "Audio input tokens generated by the model.", + "format": "int32" + }, + "reasoningTokens": { + "type": "integer", + "description": "Tokens generated by the model for reasoning.", + "format": "int32" + }, + "rejectedPredictionTokens": { + "type": "integer", + "description": "When using Predicted Outputs, the number of tokens in the prediction that did not appear in the completion.", + "format": "int32" + } + }, + "additionalProperties": false + }, + "AiContent": { + "title": "AiContent", + "type": "object", + "description": "AiContent" + }, + "AiMessage": { + "title": "AiMessage", + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AiContent" + }, + "description": "The contents of the message.", + "nullable": true + }, + "name": { + "type": "string", + "description": "An optional name for the participant. Provides the model information to differentiate between participants of the same role.", + "nullable": true + }, + "role": { + "type": "string", + "description": "The role of the author of this message. Valid values are `system`, `user`, `assistant` and `tool`." + }, + "tool_call_id": { + "type": "string", + "description": "Tool call that this message is responding to.", + "nullable": true + }, + "tool_calls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolCall" + }, + "description": "The tool calls generated by the model, such as function calls.", + "nullable": true + } + }, + "description": "A list of messages comprising the conversation so far." + }, + "AiPromptUsage": { + "type": "object", + "properties": { + "acceptedPredictionTokens": { + "type": "integer", + "description": "When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.\n\n", + "format": "int32" + }, + "audioTokens": { + "type": "integer", + "description": "Audio input tokens present in the prompt.", + "format": "int32" + }, + "cachedTokens": { + "type": "integer", + "description": "Cached tokens present in the prompt.", + "format": "int32" + } + }, + "additionalProperties": false + }, + "AiResponseFormat": { + "title": "AiResponseFormat", + "type": "object", + "properties": { + "response_format": { + "$ref": "#/components/schemas/ResponseFormat" + } + }, + "description": "AiResponseFormat" + }, + "AiUsage": { + "type": "object", + "properties": { + "completionTokens": { + "type": "integer", + "description": "Number of tokens in the generated completion.", + "format": "int32" + }, + "promptTokens": { + "type": "integer", + "description": "Number of tokens in the prompt.", + "format": "int32" + }, + "totalTokens": { + "type": "integer", + "description": "Total number of tokens used in the request (prompt + completion).", + "format": "int32" + }, + "completionTokensDetails": { + "$ref": "#/components/schemas/AiCompletionUsage" + }, + "promptTokensDetails": { + "$ref": "#/components/schemas/AiPromptUsage" + } + }, + "additionalProperties": false + }, + "Authenticate": { + "title": "Authenticate", + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "AuthProvider, e.g. credentials" + }, + "userName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "rememberMe": { + "type": "boolean", + "nullable": true + }, + "accessToken": { + "type": "string" + }, + "accessTokenSecret": { + "type": "string" + }, + "returnUrl": { + "type": "string" + }, + "errorView": { + "type": "string" + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + "description": "Sign In" + }, + "AuthenticateResponse": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "nullable": true + }, + "sessionId": { + "type": "string", + "nullable": true + }, + "userName": { + "type": "string", + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "referrerUrl": { + "type": "string", + "nullable": true + }, + "bearerToken": { + "type": "string", + "nullable": true + }, + "refreshToken": { + "type": "string", + "nullable": true + }, + "refreshTokenExpiry": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "profileUrl": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "authProvider": { + "type": "string", + "nullable": true + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "Booking": { + "type": "object", + "properties": { + "createdDate": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string", + "nullable": true + }, + "modifiedDate": { + "type": "string", + "format": "date-time" + }, + "modifiedBy": { + "type": "string", + "nullable": true + }, + "deletedDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "roomType": { + "$ref": "#/components/schemas/RoomType" + }, + "roomNumber": { + "type": "integer", + "format": "int32" + }, + "bookingStartDate": { + "type": "string", + "format": "date-time" + }, + "bookingEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cost": { + "type": "number", + "format": "double" + }, + "couponId": { + "type": "string", + "nullable": true + }, + "discount": { + "$ref": "#/components/schemas/Coupon" + }, + "notes": { + "type": "string", + "nullable": true + }, + "cancelled": { + "type": "boolean", + "nullable": true + }, + "employee": { + "$ref": "#/components/schemas/User" + } + }, + "additionalProperties": false + }, + "BookingQueryResponse": { + "type": "object", + "properties": { + "offset": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Booking" + }, + "nullable": true + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "additionalProperties": false + }, + "ChatCompletion": { + "title": "ChatCompletion", + "type": "object", + "properties": { + "audio": { + "$ref": "#/components/schemas/AiChatAudio" + }, + "enable_thinking": { + "type": "boolean", + "description": "Whether to enable thinking mode for some Qwen models and providers.", + "nullable": true + }, + "frequency_penalty": { + "type": "number", + "description": "Number between `-2.0` and `2.0`. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + "format": "double", + "nullable": true + }, + "logit_bias": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + }, + "description": "Modify the likelihood of specified tokens appearing in the completion.", + "nullable": true + }, + "logprobs": { + "type": "boolean", + "description": "Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the content of message.", + "nullable": true + }, + "max_completion_tokens": { + "type": "integer", + "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.", + "format": "int32", + "nullable": true + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AiMessage" + }, + "description": "The messages to generate chat completions for." + }, + "metadata": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", + "nullable": true + }, + "modalities": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output types that you would like the model to generate. Most models are capable of generating text, which is the default:", + "nullable": true + }, + "model": { + "type": "string", + "description": "ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API" + }, + "n": { + "type": "integer", + "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", + "format": "int32", + "nullable": true + }, + "parallel_tool_calls": { + "type": "boolean", + "description": "Whether to enable parallel function calling during tool use.", + "nullable": true + }, + "presence_penalty": { + "type": "number", + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", + "format": "double", + "nullable": true + }, + "prompt_cache_key": { + "type": "string", + "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.", + "nullable": true + }, + "reasoning_effort": { + "type": "string", + "description": "Constrains effort on reasoning for reasoning models. Currently supported values are minimal, low, medium, and high (none, default). Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.", + "nullable": true + }, + "response_format": { + "$ref": "#/components/schemas/AiResponseFormat" + }, + "safety_identifier": { + "type": "string", + "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.", + "nullable": true + }, + "seed": { + "type": "integer", + "description": "This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend.", + "format": "int32", + "nullable": true + }, + "service_tier": { + "type": "string", + "description": "Specifies the processing type used for serving the request.", + "nullable": true + }, + "stop": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Up to 4 sequences where the API will stop generating further tokens.", + "nullable": true + }, + "store": { + "type": "boolean", + "description": "Whether or not to store the output of this chat completion request for use in our model distillation or evals products.", + "nullable": true + }, + "stream": { + "type": "boolean", + "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a `data: [DONE]` message.", + "nullable": true + }, + "temperature": { + "type": "number", + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.", + "format": "double", + "nullable": true + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tool" + }, + "description": "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.", + "nullable": true + }, + "top_logprobs": { + "type": "integer", + "description": "An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. logprobs must be set to true if this parameter is used.", + "format": "int32", + "nullable": true + }, + "top_p": { + "type": "number", + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.", + "format": "double", + "nullable": true + }, + "verbosity": { + "type": "string", + "description": "Constrains the verbosity of the model's response. Lower values will result in more concise responses, while higher values will result in more verbose responses. Currently supported values are low, medium, and high.", + "nullable": true + } + }, + "description": "Chat Completions API (OpenAI-Compatible)" + }, + "ChatResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for the chat completion.", + "nullable": true + }, + "choices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Choice" + }, + "description": "A list of chat completion choices. Can be more than one if n is greater than 1.", + "nullable": true + }, + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) of when the chat completion was created.", + "format": "int64" + }, + "model": { + "type": "string", + "description": "The model used for the chat completion.", + "nullable": true + }, + "systemFingerprint": { + "type": "string", + "description": "This fingerprint represents the backend configuration that the model runs with.", + "nullable": true + }, + "object": { + "type": "string", + "description": "The object type, which is always chat.completion.", + "nullable": true + }, + "serviceTier": { + "type": "string", + "description": "Specifies the processing type used for serving the request.", + "nullable": true + }, + "usage": { + "$ref": "#/components/schemas/AiUsage" + }, + "provider": { + "type": "string", + "description": "The provider used for the chat completion.", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", + "nullable": true + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "additionalProperties": false + }, + "Choice": { + "type": "object", + "properties": { + "finishReason": { + "type": "string", + "description": "The reason the model stopped generating tokens. This will be stop if the model hit a natural stop point or a provided stop sequence, length if the maximum number of tokens specified in the request was reached, content_filter if content was omitted due to a flag from our content filters, tool_calls if the model called a tool", + "nullable": true + }, + "index": { + "type": "integer", + "description": "The index of the choice in the list of choices.", + "format": "int32" + }, + "message": { + "$ref": "#/components/schemas/ChoiceMessage" + } + }, + "additionalProperties": false + }, + "ChoiceAnnotation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of the URL citation. Always url_citation.", + "nullable": true + }, + "urlCitation": { + "$ref": "#/components/schemas/UrlCitation" + } + }, + "additionalProperties": false + }, + "ChoiceAudio": { + "type": "object", + "properties": { + "data": { + "type": "string", + "description": "Base64 encoded audio bytes generated by the model, in the format specified in the request.", + "nullable": true + }, + "expiresAt": { + "type": "integer", + "description": "The Unix timestamp (in seconds) for when this audio response will no longer be accessible on the server for use in multi-turn conversations.", + "format": "int32" + }, + "id": { + "type": "string", + "description": "Unique identifier for this audio response.", + "nullable": true + }, + "transcript": { + "type": "string", + "description": "Transcript of the audio generated by the model.", + "nullable": true + } + }, + "additionalProperties": false + }, + "ChoiceMessage": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The contents of the message.", + "nullable": true + }, + "refusal": { + "type": "string", + "description": "The refusal message generated by the model.", + "nullable": true + }, + "reasoning": { + "type": "string", + "description": "The reasoning process used by the model.", + "nullable": true + }, + "role": { + "type": "string", + "description": "The role of the author of this message.", + "nullable": true + }, + "annotations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChoiceAnnotation" + }, + "description": "Annotations for the message, when applicable, as when using the web search tool.", + "nullable": true + }, + "audio": { + "$ref": "#/components/schemas/ChoiceAudio" + }, + "toolCalls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolCall" + }, + "description": "The tool calls generated by the model, such as function calls.", + "nullable": true + } + }, + "additionalProperties": false + }, + "Coupon": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "discount": { + "type": "integer", + "format": "int32" + }, + "expiryDate": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "CouponQueryResponse": { + "type": "object", + "properties": { + "offset": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Coupon" + }, + "nullable": true + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "additionalProperties": false + }, + "CreateBooking": { + "title": "CreateBooking", + "required": [ + "name", + "roomNumber", + "cost" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name this Booking is for" + }, + "roomType": { + "$ref": "#/components/schemas/RoomType" + }, + "roomNumber": { + "type": "integer", + "format": "int32" + }, + "cost": { + "type": "number", + "format": "double" + }, + "bookingStartDate": { + "type": "string", + "format": "date-time" + }, + "bookingEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "notes": { + "type": "string", + "nullable": true + }, + "couponId": { + "type": "string", + "nullable": true + } + }, + "description": "Create a new Booking" + }, + "CreateCoupon": { + "title": "CreateCoupon", + "required": [ + "id", + "description", + "discount", + "expiryDate" + ], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "discount": { + "type": "integer", + "format": "int32" + }, + "expiryDate": { + "type": "string", + "format": "date-time" + } + }, + "description": "CreateCoupon" + }, + "DeleteBooking": { + "title": "DeleteBooking", + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + } + }, + "description": "Delete a Booking" + }, + "DeleteCoupon": { + "title": "DeleteCoupon", + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "description": "Delete a Coupon" + }, + "Hello": { + "title": "Hello", + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + } + }, + "description": "Hello" + }, + "HelloResponse": { + "required": [ + "result" + ], + "type": "object", + "properties": { + "result": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "IdResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "additionalProperties": false + }, + "QueryBookings": { + "title": "QueryBookings", + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "skip": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "take": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "orderBy": { + "type": "string", + "nullable": true + }, + "orderByDesc": { + "type": "string", + "nullable": true + }, + "include": { + "type": "string", + "nullable": true + }, + "fields": { + "type": "string", + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "Find Bookings" + }, + "QueryCoupons": { + "title": "QueryCoupons", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "skip": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "take": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "orderBy": { + "type": "string", + "nullable": true + }, + "orderByDesc": { + "type": "string", + "nullable": true + }, + "include": { + "type": "string", + "nullable": true + }, + "fields": { + "type": "string", + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "Find Coupons" + }, + "QueryUsers": { + "title": "QueryUsers", + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "skip": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "take": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "orderBy": { + "type": "string", + "nullable": true + }, + "orderByDesc": { + "type": "string", + "nullable": true + }, + "include": { + "type": "string", + "nullable": true + }, + "fields": { + "type": "string", + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "QueryUsers" + }, + "QueueCheckUrls": { + "title": "QueueCheckUrls", + "type": "object", + "properties": { + "urls": { + "type": "string" + } + }, + "description": "QueueCheckUrls" + }, + "ResponseError": { + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "nullable": true + }, + "fieldName": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string", + "nullable": true + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "ResponseFormat": { + "enum": [ + "Text", + "JsonObject" + ], + "type": "string" + }, + "ResponseStatus": { + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string", + "nullable": true + }, + "stackTrace": { + "type": "string", + "nullable": true + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResponseError" + }, + "nullable": true + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "RoomType": { + "enum": [ + "Single", + "Double", + "Queen", + "Twin", + "Suite" + ], + "type": "string" + }, + "SearchRootSummary": { + "title": "SearchRootSummary", + "type": "object", + "properties": { + "skip": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "take": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "orderBy": { + "type": "string", + "nullable": true + }, + "orderByDesc": { + "type": "string", + "nullable": true + }, + "include": { + "type": "string", + "nullable": true + }, + "fields": { + "type": "string", + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "SearchRootSummary" + }, + "TestNullable": { + "title": "TestNullable", + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredId": { + "type": "integer", + "format": "int32" + }, + "ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "nullable": true + }, + "requiredIds": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "names": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "requiredNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "roomNumbers": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "nullable": true + }, + "requiredRoomNumbers": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "description": "TestNullable" + }, + "Tool": { + "title": "Tool", + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/ToolType" + } + }, + "description": "Tool" + }, + "ToolCall": { + "title": "ToolCall", + "type": "object", + "properties": { + "function": { + "type": "string", + "description": "The function that the model called." + }, + "id": { + "type": "string", + "description": "The ID of the tool call." + }, + "type": { + "type": "string", + "description": "The type of the tool. Currently, only `function` is supported." + } + }, + "description": "The tool calls generated by the model, such as function calls." + }, + "ToolType": { + "enum": [ + "Function" + ], + "type": "string" + }, + "UpdateBooking": { + "title": "UpdateBooking", + "required": [ + "roomNumber", + "cost" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "roomType": { + "$ref": "#/components/schemas/RoomType" + }, + "roomNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "cost": { + "type": "number", + "format": "double", + "nullable": true + }, + "bookingStartDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "bookingEndDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "notes": { + "type": "string", + "nullable": true + }, + "couponId": { + "type": "string", + "nullable": true + }, + "cancelled": { + "type": "boolean", + "nullable": true + } + }, + "description": "Update an existing Booking" + }, + "UpdateCoupon": { + "title": "UpdateCoupon", + "required": [ + "description", + "discount", + "expiryDate" + ], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "discount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "expiryDate": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "description": "UpdateCoupon" + }, + "UrlCitation": { + "type": "object", + "properties": { + "endIndex": { + "type": "integer", + "description": "The index of the last character of the URL citation in the message.", + "format": "int32" + }, + "startIndex": { + "type": "integer", + "description": "The index of the first character of the URL citation in the message.", + "format": "int32" + }, + "title": { + "type": "string", + "description": "The title of the web resource.", + "nullable": true + }, + "url": { + "type": "string", + "description": "The URL of the web resource.", + "nullable": true + } + }, + "additionalProperties": false + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "userName": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "string", + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "profileUrl": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "basic": { + "type": "http", + "description": "HTTP Basic access authentication", + "scheme": "basic" + } + } + } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs new file mode 100644 index 00000000000..43bd2c7164e --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs @@ -0,0 +1,25 @@ +using ServiceStack; +using OpenApi3Swashbuckle.ServiceInterface; + +[assembly: HostingStartup(typeof(OpenApi3Swashbuckle.AppHost))] + +namespace OpenApi3Swashbuckle; + +public class AppHost() : AppHostBase("OpenApi3Swashbuckle", typeof(MyServices).Assembly), IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => + { + // Additional ASP.NET Core services for tests can go here + }); + + public override void Configure() + { + // ServiceStack configuration for tests can go here + SetConfig(new HostConfig + { + DebugMode = true + }); + } +} + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj new file mode 100644 index 00000000000..6fa0bbaa678 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + enable + enable + OpenApi3Swashbuckle + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs new file mode 100644 index 00000000000..5b5044a7d8d --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs @@ -0,0 +1,29 @@ +using OpenApi3Swashbuckle.ServiceInterface; +using ServiceStack; + +var builder = WebApplication.CreateBuilder(args); +var services = builder.Services; + +services.AddServiceStack(typeof(MyServices).Assembly); + +if (builder.Environment.IsDevelopment()) +{ + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddServiceStackSwagger(); +} + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} +app.UseHttpsRedirection(); + + +app.UseServiceStack(new AppHost()); + +app.Run(); + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Properties/launchSettings.json b/ServiceStack/tests/OpenApi3Swashbuckle/Properties/launchSettings.json new file mode 100644 index 00000000000..44bc1913fcb --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36760", + "sslPort": 44388 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/README.md b/ServiceStack/tests/OpenApi3Swashbuckle/README.md new file mode 100644 index 00000000000..6f6d61f4a19 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/README.md @@ -0,0 +1,75 @@ +# OpenApi3Sample + +A minimal **.NET 10** ServiceStack host showing how to integrate the `ServiceStack.AspNetCore.OpenApi3` package with **Swashbuckle** and **Microsoft.OpenApi 3.x**. + +This sample lives at: + +- `ServiceStack/tests/OpenApi3Sample` (repo root) +- `tests/OpenApi3Sample` (inside the `ServiceStack` solution folder) + +## What it demonstrates + +- A minimal ServiceStack AppHost running on ASP.NET Core 10 +- Integration of **ServiceStack.AspNetCore.OpenApi3** with **Swashbuckle.AspNetCore** +- Automatic generation of an **OpenAPI 3** document for ServiceStack services +- A simple `Hello` service exposed via ServiceStack and surfaced in Swagger + +Key files: + +- `OpenApi3Sample.csproj` – web project targeting `net10.0`, references: + - `ServiceStack.AspNetCore.OpenApi3` + - core ServiceStack projects and `Swashbuckle.AspNetCore` +- `Program.cs` – minimal ASP.NET Core bootstrap: + - Adds ServiceStack (`AddServiceStack`) + - Registers Swagger + ServiceStack OpenAPI (`AddSwaggerGen`, `AddServiceStackSwagger`) + - Enables Swagger middleware in Development +- `Configure.AppHost.cs` – standard ServiceStack `AppHost` using `HostingStartup` +- `ServiceModel/Hello.cs` – `Hello` request/response DTOs +- `ServiceInterface/MyServices.cs` – `Hello` service implementation + +## Build and run + +From the **ServiceStack solution folder** (where `ServiceStack.sln` lives): + +```bash +cd ServiceStack +dotnet build tests/OpenApi3Sample/OpenApi3Sample.csproj -f net10.0 +``` + +To run the sample: + +```bash +cd ServiceStack +dotnet run --project tests/OpenApi3Sample/OpenApi3Sample.csproj +``` + +By default this sample assumes Kestrel is listening on: + +- `https://localhost:5001` (HTTPS) + +## OpenAPI & Swagger endpoints + +Once the app is running (Development environment): + +- **Swagger UI (Swashbuckle):** + - `https://localhost:5001/swagger` +- **Raw OpenAPI 3 JSON document:** + - `https://localhost:5001/swagger/v1/swagger.json` + +These are produced by **Swashbuckle** using the **ServiceStack.AspNetCore.OpenApi3** metadata pipeline, backed by **Microsoft.OpenApi 3.x**. + +## ServiceStack endpoints to try + +ServiceStack metadata & UI: + +- Metadata: `https://localhost:5001/metadata` + +`Hello` service examples: + +- Query string: + - `https://localhost:5001/json/reply/Hello?Name=World` +- Route-based: + - `https://localhost:5001/hello/World` + +You should see the `Hello` operation documented in Swagger UI and present under the `/hello` path in the OpenAPI JSON. + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs new file mode 100644 index 00000000000..56308699bba --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs @@ -0,0 +1,13 @@ +using OpenApi3Swashbuckle.ServiceModel; +using ServiceStack; + +namespace OpenApi3Swashbuckle.ServiceInterface; + +public class MyServices : Service +{ + public object Any(Hello request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; +} + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs new file mode 100644 index 00000000000..822deaddcbf --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs @@ -0,0 +1,16 @@ +using ServiceStack; + +namespace OpenApi3Swashbuckle.ServiceModel; + +[Route("/hello", "GET")] +[Route("/hello/{Name}", "GET")] +public class Hello : IReturn +{ + public string? Name { get; set; } +} + +public class HelloResponse +{ + public string? Result { get; set; } +} + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/swagger.json b/ServiceStack/tests/OpenApi3Swashbuckle/swagger.json new file mode 100644 index 00000000000..8217675d885 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/swagger.json @@ -0,0 +1,12959 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "OpenApi3Sample", + "version": "1.0" + }, + "paths": { + "/hello": { + "get": { + "operationId": "HelloGET_hello", + "parameters": [ + { + "name": "Name", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/HelloResponse" + } + ] + } + } + } + } + } + } + }, + "/hello/{Name}": { + "get": { + "operationId": "HelloGET_hello__Name", + "parameters": [ + { + "name": "Name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/HelloResponse" + } + ] + } + } + } + } + } + } + }, + "/types": { + "get": { + "operationId": "TypeLinksGET_types", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Dictionary", + "content": { + "application/json": { + "schema": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Dictionary" + } + } + } + } + } + }, + "post": { + "operationId": "TypeLinksPOST_types", + "responses": { + "200": { + "description": "Dictionary", + "content": { + "application/json": { + "schema": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Dictionary" + } + } + } + } + } + }, + "put": { + "operationId": "TypeLinksPUT_types", + "responses": { + "200": { + "description": "Dictionary", + "content": { + "application/json": { + "schema": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Dictionary" + } + } + } + } + } + }, + "delete": { + "operationId": "TypeLinksDELETE_types", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Dictionary", + "content": { + "application/json": { + "schema": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Dictionary" + } + } + } + } + } + } + }, + "/types/metadata": { + "get": { + "operationId": "TypesMetadataGET_types_metadata", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MetadataTypes" + } + ] + } + } + } + } + } + }, + "post": { + "operationId": "TypesMetadataPOST_types_metadata", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MetadataTypes" + } + ] + } + } + } + } + } + }, + "put": { + "operationId": "TypesMetadataPUT_types_metadata", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MetadataTypes" + } + ] + } + } + } + } + } + }, + "delete": { + "operationId": "TypesMetadataDELETE_types_metadata", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MetadataTypes" + } + ] + } + } + } + } + } + } + }, + "/types/csharp": { + "get": { + "operationId": "TypesCSharpGET_types_csharp", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesCSharpPOST_types_csharp", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesCSharpPUT_types_csharp", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesCSharpDELETE_types_csharp", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/fsharp": { + "get": { + "operationId": "TypesFSharpGET_types_fsharp", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesFSharpPOST_types_fsharp", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesFSharpPUT_types_fsharp", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesFSharpDELETE_types_fsharp", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/vbnet": { + "get": { + "operationId": "TypesVbNetGET_types_vbnet", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesVbNetPOST_types_vbnet", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesVbNetPUT_types_vbnet", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesVbNetDELETE_types_vbnet", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/typescript": { + "get": { + "operationId": "TypesTypeScriptGET_types_typescript", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesTypeScriptPOST_types_typescript", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesTypeScriptPUT_types_typescript", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesTypeScriptDELETE_types_typescript", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/typescript.d": { + "get": { + "operationId": "TypesTypeScriptDefinitionGET_types_typescript.d", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesTypeScriptDefinitionPOST_types_typescript.d", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesTypeScriptDefinitionPUT_types_typescript.d", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesTypeScriptDefinitionDELETE_types_typescript.d", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/js": { + "get": { + "operationId": "TypesCommonJsGET_types_js", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesCommonJsPOST_types_js", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesCommonJsPUT_types_js", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesCommonJsDELETE_types_js", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/mjs": { + "get": { + "operationId": "TypesMjsGET_types_mjs", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesMjsPOST_types_mjs", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesMjsPUT_types_mjs", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesMjsDELETE_types_mjs", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/php": { + "get": { + "operationId": "TypesPhpGET_types_php", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesPhpPOST_types_php", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesPhpPUT_types_php", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesPhpDELETE_types_php", + "parameters": [ + { + "name": "Cache", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "Vfx", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/python": { + "get": { + "operationId": "TypesPythonGET_types_python", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesPythonPOST_types_python", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesPythonPUT_types_python", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesPythonDELETE_types_python", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/ruby": { + "get": { + "operationId": "TypesRubyGET_types_ruby", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesRubyPOST_types_ruby", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesRubyPUT_types_ruby", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesRubyDELETE_types_ruby", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/go": { + "get": { + "operationId": "TypesGoGET_types_go", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesGoPOST_types_go", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesGoPUT_types_go", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesGoDELETE_types_go", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/rust": { + "get": { + "operationId": "TypesRustGET_types_rust", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesRustPOST_types_rust", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesRustPUT_types_rust", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesRustDELETE_types_rust", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/zig": { + "get": { + "operationId": "TypesZigGET_types_zig", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesZigPOST_types_zig", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesZigPUT_types_zig", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesZigDELETE_types_zig", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/dart": { + "get": { + "operationId": "TypesDartGET_types_dart", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesDartPOST_types_dart", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesDartPUT_types_dart", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesDartDELETE_types_dart", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/swift": { + "get": { + "operationId": "TypesSwiftGET_types_swift", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesSwiftPOST_types_swift", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesSwiftPUT_types_swift", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesSwiftDELETE_types_swift", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/java": { + "get": { + "operationId": "TypesJavaGET_types_java", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesJavaPOST_types_java", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesJavaPUT_types_java", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesJavaDELETE_types_java", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/types/kotlin": { + "get": { + "operationId": "TypesKotlinGET_types_kotlin", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "TypesKotlinPOST_types_kotlin", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "put": { + "operationId": "TypesKotlinPUT_types_kotlin", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + }, + "delete": { + "operationId": "TypesKotlinDELETE_types_kotlin", + "parameters": [ + { + "name": "BaseUrl", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "MakePartial", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeVirtual", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeInternal", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddReturnMarker", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDescriptionAsComments", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDocAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDataContractAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakeDataContractsExtensible", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddIndexesToDataMembers", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddGeneratedCodeAttributes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "InitializeCollections", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddImplicitVersion", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + { + "name": "AddResponseStatus", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddServiceStackTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddModelExtensions", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddPropertyAccessors", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeGenericBaseTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "SettersReturnThis", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddNullableAnnotations", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "MakePropertiesOptional", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportAsTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExportValueTypes", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "ExcludeNamespace", + "in": "query", + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "AddDefaultXmlNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "GlobalNamespace", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "BaseClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "Package", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClass", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "DataClassJson", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "AddNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultNamespaces", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DefaultImports", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExcludeTypes", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ExportTags", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "TreatTypesAsStrings", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "title": "String", + "type": "string" + } + } + } + } + } + } + }, + "/metadata/app": { + "get": { + "operationId": "MetadataAppGET_metadata_app", + "parameters": [ + { + "name": "View", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AppMetadata" + } + ] + } + } + } + } + } + }, + "post": { + "operationId": "MetadataAppPOST_metadata_app", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AppMetadata" + } + ] + } + } + } + } + } + }, + "put": { + "operationId": "MetadataAppPUT_metadata_app", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AppMetadata" + } + ] + } + } + } + } + } + }, + "delete": { + "operationId": "MetadataAppDELETE_metadata_app", + "parameters": [ + { + "name": "View", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "IncludeTypes", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AppMetadata" + } + ] + } + } + } + } + } + } + }, + "/metadata/nav": { + "get": { + "operationId": "GetNavItemsGET_metadata_nav", + "parameters": [ + { + "name": "Name", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GetNavItemsResponse" + } + ] + } + } + } + } + } + } + }, + "/metadata/nav/{Name}": { + "get": { + "operationId": "GetNavItemsGET_metadata_nav__Name", + "parameters": [ + { + "name": "Name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GetNavItemsResponse" + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AdminDashboardResponse": { + "title": "AdminDashboardResponse", + "type": "object", + "properties": { + "serverStats": { + "$ref": "#/components/schemas/ServerStats" + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "description": "AdminDashboardResponse" + }, + "GetNavItemsResponse": { + "title": "GetNavItemsResponse", + "type": "object", + "properties": { + "baseUrl": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NavItem" + } + }, + "navItemsMap": { + "title": "Dictionary>", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NavItem" + } + } + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus" + } + }, + "description": "GetNavItemsResponse" + }, + "Hello": { + "title": "Hello", + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + } + }, + "description": "Hello" + }, + "HelloResponse": { + "title": "HelloResponse", + "type": "object", + "properties": { + "result": { + "type": "string", + "nullable": true + } + }, + "description": "HelloResponse" + }, + "NavItem": { + "title": "NavItem", + "type": "object", + "properties": { + "label": { + "type": "string", + "nullable": true + }, + "href": { + "type": "string", + "nullable": true + }, + "exact": { + "type": "boolean", + "nullable": true + }, + "id": { + "type": "string", + "nullable": true + }, + "className": { + "type": "string", + "nullable": true + }, + "iconClass": { + "type": "string", + "nullable": true + }, + "iconSrc": { + "type": "string", + "nullable": true + }, + "show": { + "type": "string", + "nullable": true + }, + "hide": { + "type": "string", + "nullable": true + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NavItem" + }, + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "NavItem" + }, + "ResponseError": { + "title": "ResponseError", + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "nullable": true + }, + "fieldName": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string", + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "ResponseError" + }, + "ResponseStatus": { + "title": "ResponseStatus", + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string", + "nullable": true + }, + "stackTrace": { + "type": "string", + "nullable": true + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResponseError" + }, + "nullable": true + }, + "meta": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + } + }, + "description": "ResponseStatus" + }, + "ServerStats": { + "title": "ServerStats", + "type": "object", + "properties": { + "redis": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "serverEvents": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + }, + "mqDescription": { + "type": "string", + "nullable": true + }, + "mqWorkers": { + "title": "Dictionary", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + }, + "description": "ServerStats" + } + } + } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/test-hidden-routes.sh b/ServiceStack/tests/OpenApi3Swashbuckle/test-hidden-routes.sh new file mode 100755 index 00000000000..a3d49fcbd1b --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/test-hidden-routes.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Start the server in the background +cd "$(dirname "$0")" +dotnet run --no-build --urls "http://localhost:5556" > /tmp/openapi3-server.log 2>&1 & +SERVER_PID=$! + +# Wait for server to start +echo "Waiting for server to start..." +sleep 5 + +# Fetch swagger.json +echo "Fetching swagger.json..." +curl -s http://localhost:5556/swagger/v1/swagger.json > /tmp/swagger-test.json + +# Check if /types routes are present +echo "" +echo "Checking for hidden routes..." +if grep -q '"/types"' /tmp/swagger-test.json; then + echo "❌ FAIL: /types route is still present in swagger.json" + RESULT=1 +else + echo "✅ PASS: /types route is NOT present in swagger.json" + RESULT=0 +fi + +if grep -q '"/types/metadata"' /tmp/swagger-test.json; then + echo "❌ FAIL: /types/metadata route is still present in swagger.json" + RESULT=1 +else + echo "✅ PASS: /types/metadata route is NOT present in swagger.json" +fi + +if grep -q '"/types/csharp"' /tmp/swagger-test.json; then + echo "❌ FAIL: /types/csharp route is still present in swagger.json" + RESULT=1 +else + echo "✅ PASS: /types/csharp route is NOT present in swagger.json" +fi + +# Check that /hello route is still present (should not be filtered) +if grep -q '"/hello"' /tmp/swagger-test.json; then + echo "✅ PASS: /hello route is present in swagger.json (as expected)" +else + echo "❌ FAIL: /hello route is missing from swagger.json" + RESULT=1 +fi + +# Kill the server +echo "" +echo "Stopping server..." +kill $SERVER_PID 2>/dev/null + +exit $RESULT + diff --git a/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs b/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs new file mode 100644 index 00000000000..cf30da7e42b --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs @@ -0,0 +1,24 @@ +using ServiceStack; +using OpenApiScalar.ServiceInterface; + +[assembly: HostingStartup(typeof(OpenApiScalar.AppHost))] + +namespace OpenApiScalar; + +public class AppHost() : AppHostBase("OpenApiScalar", typeof(MyServices).Assembly), IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => + { + // Additional ASP.NET Core services for tests can go here + }); + + public override void Configure() + { + // ServiceStack configuration for tests can go here + SetConfig(new HostConfig + { + DebugMode = true + }); + } +} diff --git a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj new file mode 100644 index 00000000000..845ec22e60d --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + enable + enable + OpenApiScalar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ServiceStack/tests/OpenApiScalar/Program.cs b/ServiceStack/tests/OpenApiScalar/Program.cs new file mode 100644 index 00000000000..78053902cbc --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Program.cs @@ -0,0 +1,25 @@ +using OpenApiScalar; +using OpenApiScalar.ServiceInterface; +using Scalar.AspNetCore; +using ServiceStack; + +var builder = WebApplication.CreateBuilder(args); +var services = builder.Services; + +services.AddOpenApi(); +services.AddServiceStackOpenApi(); +services.AddServiceStack(typeof(MyServices).Assembly); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseServiceStack(new AppHost()); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.MapScalarApiReference(); +} + +app.Run(); diff --git a/ServiceStack/tests/OpenApiScalar/Properties/launchSettings.json b/ServiceStack/tests/OpenApiScalar/Properties/launchSettings.json new file mode 100644 index 00000000000..44bc1913fcb --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36760", + "sslPort": 44388 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ServiceStack/tests/OpenApiScalar/README.md b/ServiceStack/tests/OpenApiScalar/README.md new file mode 100644 index 00000000000..142f2bca4f6 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/README.md @@ -0,0 +1,216 @@ +# OpenApiScalar Demo + +This demo project showcases **ServiceStack** integration with **Microsoft.AspNetCore.OpenApi** and **Scalar UI**. + +## What's Included + +- ✅ ServiceStack services with OpenAPI documentation +- ✅ Microsoft.AspNetCore.OpenApi for native OpenAPI 3.1 generation +- ✅ Scalar UI for modern, interactive API documentation +- ✅ Example services demonstrating various ServiceStack features + +## Running the Demo + +```bash +cd ServiceStack/tests/OpenApiScalar +dotnet run +``` + +The application will start on: +- **HTTPS**: https://localhost:5001 +- **HTTP**: http://localhost:5000 + +## Available Endpoints + +### API Documentation + +| Endpoint | Description | +|----------|-------------| +| https://localhost:5001/scalar/v1 | **Scalar UI** - Modern interactive API documentation | +| https://localhost:5001/openapi/v1.json | **OpenAPI Document** - Raw OpenAPI 3.1 JSON | + +### ServiceStack Endpoints + +| Endpoint | Description | +|----------|-------------| +| https://localhost:5001/metadata | ServiceStack metadata page | +| https://localhost:5001/hello | Example Hello service | +| https://localhost:5001/hello/{Name} | Hello service with route parameter | + +## Project Structure + +``` +OpenApiScalar/ +├── Program.cs # Application entry point and configuration +├── Configure.AppHost.cs # ServiceStack AppHost configuration +├── ServiceInterface/ # Service implementations +│ └── MyServices.cs +└── ServiceModel/ # DTOs and request/response models + └── Hello.cs +``` + +## Configuration Breakdown + +### Program.cs + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// 1. Add Microsoft's native OpenAPI support +builder.Services.AddOpenApi(); + +// 2. Add ServiceStack OpenAPI integration +builder.Services.AddServiceStackOpenApi(); + +// 3. Add ServiceStack services +builder.Services.AddServiceStack(typeof(MyServices).Assembly); + +var app = builder.Build(); + +// 4. Initialize ServiceStack +app.UseServiceStack(new AppHost()); + +// 5. Map OpenAPI endpoints (Development only) +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); // Maps /openapi/v1.json + app.MapScalarApiReference(); // Maps /scalar/v1 +} + +app.Run(); +``` + +### Key Points + +1. **AddOpenApi()** - Registers Microsoft's OpenAPI services (default document name: "v1") +2. **AddServiceStackOpenApi()** - Integrates ServiceStack operations into the OpenAPI document +3. **AddServiceStack()** - Registers ServiceStack services +4. **UseServiceStack()** - Initializes ServiceStack middleware (must be before MapOpenApi) +5. **MapOpenApi()** - Exposes the OpenAPI JSON document +6. **MapScalarApiReference()** - Exposes the Scalar UI + +## Customizing the Configuration + +### Custom Document Name + +```csharp +// Use a custom document name +builder.Services.AddOpenApi("api"); +builder.Services.AddServiceStackOpenApi("api"); + +// Access at /openapi/api.json and /scalar/api +``` + +### Multiple API Versions + +```csharp +// Configure multiple versions +builder.Services.AddOpenApi("v1"); +builder.Services.AddOpenApi("v2"); + +builder.Services.AddServiceStackOpenApi("v1"); +builder.Services.AddServiceStackOpenApi("v2"); + +// Access at: +// - /openapi/v1.json and /scalar/v1 +// - /openapi/v2.json and /scalar/v2 +``` + +### Custom Metadata + +```csharp +builder.Services.AddServiceStackOpenApi(configure: metadata => +{ + metadata.Title = "My Custom API"; + metadata.Version = "2.0.0"; + metadata.Description = "A comprehensive API for my application"; + + // Add authentication schemes + metadata.AddBasicAuth(); + metadata.AddApiKeyAuth(); + metadata.AddBearerAuth(); +}); +``` + +### Scalar UI Customization + +```csharp +app.MapScalarApiReference(options => +{ + options + .WithTitle("My API Documentation") + .WithTheme(ScalarTheme.Purple) + .WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient) + .WithPreferredScheme("https"); +}); +``` + +## Testing the API + +### Using Scalar UI + +1. Navigate to https://localhost:5001/scalar/v1 +2. Browse the available endpoints +3. Try out the API directly from the UI +4. View request/response examples +5. Generate code snippets in various languages + +### Using curl + +```bash +# Get the OpenAPI document +curl -k https://localhost:5001/openapi/v1.json + +# Call the Hello service +curl -k "https://localhost:5001/hello?Name=World" + +# Call with route parameter +curl -k https://localhost:5001/hello/World +``` + +### Using HTTP files (VS Code REST Client) + +```http +### Get OpenAPI document +GET https://localhost:5001/openapi/v1.json + +### Call Hello service +GET https://localhost:5001/hello?Name=World + +### Call with route parameter +GET https://localhost:5001/hello/World +``` + +## Learn More + +- [ServiceStack Documentation](https://docs.servicestack.net/) +- [Microsoft.AspNetCore.OpenApi](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi) +- [Scalar Documentation](https://github.com/scalar/scalar) +- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) + +## Troubleshooting + +### Port already in use + +If ports 5000/5001 are already in use, modify `Properties/launchSettings.json`: + +```json +{ + "applicationUrl": "https://localhost:7001;http://localhost:7000" +} +``` + +### HTTPS certificate issues + +Trust the development certificate: + +```bash +dotnet dev-certs https --trust +``` + +### OpenAPI document is empty + +Ensure the order of middleware registration: +1. `UseServiceStack()` must be called before `MapOpenApi()` +2. Document names must match between `AddOpenApi()` and `AddServiceStackOpenApi()` + diff --git a/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs b/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs new file mode 100644 index 00000000000..2684db0b575 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs @@ -0,0 +1,13 @@ +using OpenApiScalar.ServiceModel; +using ServiceStack; + +namespace OpenApiScalar.ServiceInterface; + +public class MyServices : Service +{ + public object Any(Hello request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; +} + diff --git a/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs b/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs new file mode 100644 index 00000000000..67fcde34326 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs @@ -0,0 +1,16 @@ +using ServiceStack; + +namespace OpenApiScalar.ServiceModel; + +[Route("/hello", "GET")] +[Route("/hello/{Name}", "GET")] +public class Hello : IReturn +{ + public string? Name { get; set; } +} + +public class HelloResponse +{ + public string? Result { get; set; } +} + From 6eecb07ef5dc59e80728c6f58da4d7180128b596 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 00:27:37 +0800 Subject: [PATCH 021/140] Fix NRE in OpenApi impls --- .../OpenApiMetadata.cs | 8 ++++++-- .../ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs index 1c448dffa10..179b14a2709 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs @@ -218,9 +218,13 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s { Schema = formSchema, }; - foreach (var entry in formSchema.Properties) + if (formSchema.Properties.Count > 0) { - formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + formType.Encoding ??= new OrderedDictionary(); + foreach (var entry in formSchema.Properties) + { + formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + } } var requestBody = new OpenApiRequestBody { diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs index 5eab50eeecc..25e79a49c15 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs @@ -218,9 +218,13 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s { Schema = formSchema, }; - foreach (var entry in formSchema.Properties) + if (formSchema.Properties.Count > 0) { - formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + formType.Encoding ??= new OrderedDictionary(); + foreach (var entry in formSchema.Properties) + { + formType.Encoding[entry.Key] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = false }; + } } var requestBody = new OpenApiRequestBody { From f3a83bf2459460bba525303c3de701b82de11d43 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 00:27:58 +0800 Subject: [PATCH 022/140] Only register primary HTTP Method for APIs --- .../ServiceStackDocumentTransformer.cs | 22 ++++++++++++++----- .../ServiceStack.AspNetCore.OpenApi3.csproj | 2 +- .../ServiceStackDocumentFilter.cs | 22 ++++++++++++++----- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs index e9130f1c3ff..fa0d7410118 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs @@ -71,13 +71,25 @@ public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerC { foreach (var restPath in restPathList) { - var verbs = new List(); - verbs.AddRange(restPath.AllowsAllVerbs - ? new[] { HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete } - : restPath.Verbs); - var routePath = restPath.Path.Replace("*", string.Empty); var requestType = restPath.RequestType; + + // Only register the primary HTTP method for each endpoint + var primaryMethod = ServiceClientUtils.GetHttpMethod(requestType); + var verbs = new List(); + if (primaryMethod != null) + { + verbs.Add(primaryMethod); + } + else if (!restPath.AllowsAllVerbs) + { + verbs.AddRange(restPath.Verbs); + } + else + { + // Fallback to POST if no primary method can be determined + verbs.Add(HttpMethods.Post); + } if (!HostContext.Metadata.OperationsMap.TryGetValue(requestType, out var opMeta)) continue; diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj index 240d117fdb2..9c3b14b9279 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj @@ -4,7 +4,7 @@ net10.0 enable enable - ServiceStack.AspNetCore.OpenApi + ServiceStack.AspNetCore.OpenApi diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs index 2dd8ce06ae3..9d9d7aa20e5 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs @@ -59,13 +59,25 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { foreach (var restPath in restPathList) { - var verbs = new List(); - verbs.AddRange(restPath.AllowsAllVerbs - ? new[] { HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete } - : restPath.Verbs); - var routePath = restPath.Path.Replace("*", string.Empty); var requestType = restPath.RequestType; + + // Only register the primary HTTP method for each endpoint + var primaryMethod = ServiceClientUtils.GetHttpMethod(requestType); + var verbs = new List(); + if (primaryMethod != null) + { + verbs.Add(primaryMethod); + } + else if (!restPath.AllowsAllVerbs) + { + verbs.AddRange(restPath.Verbs); + } + else + { + // Fallback to POST if no primary method can be determined + verbs.Add(HttpMethods.Post); + } if (!HostContext.Metadata.OperationsMap.TryGetValue(requestType, out var opMeta)) continue; From e0f1d7fc0ecc23015c9499a9673b4e92231fdf16 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 00:28:26 +0800 Subject: [PATCH 023/140] Move NorthwindBlazor to OpenApi.Microsoft and Scalar UI --- .../NorthwindBlazor/Configure.OpenApi.cs | 78 +++++++++++-------- .../NorthwindBlazor/NorthwindBlazor.csproj | 13 +++- .../NorthwindBlazor/ServiceModel/Hello.cs | 1 + 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/ServiceStack/tests/NorthwindBlazor/Configure.OpenApi.cs b/ServiceStack/tests/NorthwindBlazor/Configure.OpenApi.cs index 0e1657543f2..44bd3f41f97 100644 --- a/ServiceStack/tests/NorthwindBlazor/Configure.OpenApi.cs +++ b/ServiceStack/tests/NorthwindBlazor/Configure.OpenApi.cs @@ -1,10 +1,7 @@ -using System.ComponentModel; -using System.Reflection; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi; using MyApp.Data; -using Swashbuckle.AspNetCore.SwaggerGen; +using Scalar.AspNetCore; [assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] @@ -16,44 +13,63 @@ public void Configure(IWebHostBuilder builder) => builder .ConfigureServices((context, services) => { if (context.HostingEnvironment.IsDevelopment()) { - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(options => + services.AddOpenApi(options => { - options.OperationFilter(); + options.AddOperationTransformer(); }); - services.AddServiceStackSwagger(); - services.AddBasicAuth(); - //services.AddJwtAuth(); - - services.AddTransient(); + services.AddServiceStackOpenApi(configure: metadata => + { + // metadata.AddBasicAuth(); + // metadata.AddJwtBearer(); + }); + } + }) + .Configure((context, app) => { + if (context.HostingEnvironment.IsDevelopment()) + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapOpenApi(); + endpoints.MapScalarApiReference(); + }); } }); +} - public class StartupFilter : IStartupFilter +public class OpenApiDisplayNameOperationTransformer : IOpenApiOperationTransformer +{ + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { - public Action Configure(Action next) => app => + if (context.Description.ActionDescriptor.EndpointMetadata.FirstOrDefault(x => + x is OpenApiDisplayNameAttribute) is OpenApiDisplayNameAttribute attr) { - app.UseSwagger(); - app.UseSwaggerUI(); - next(app); - }; + operation.Extensions ??= new Dictionary(); + operation.Extensions["x-displayName"] = new OpenApiStringExtension(attr.DisplayName); + } + return Task.CompletedTask; + } +} + +// Custom IOpenApiExtension implementation for string values +public class OpenApiStringExtension : IOpenApiExtension +{ + private readonly string _value; + + public OpenApiStringExtension(string value) + { + _value = value; } - - public class OpenApiDisplayNameOperationFilter : IOperationFilter + + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - if (context.ApiDescription.ActionDescriptor.EndpointMetadata.FirstOrDefault(x => - x is OpenApiDisplayNameAttribute) is OpenApiDisplayNameAttribute attr) - { - operation.AddExtension("x-displayName", new OpenApiString(attr.DisplayName)); - } - } + writer.WriteValue(_value); } } + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] public class OpenApiDisplayNameAttribute : Attribute { public string DisplayName { get; set;} -} \ No newline at end of file +} diff --git a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj index a8dd5d8627e..582a91ce2a6 100644 --- a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj +++ b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj @@ -22,8 +22,8 @@ - - + + @@ -31,11 +31,18 @@ - + + + + + + + + diff --git a/ServiceStack/tests/NorthwindBlazor/ServiceModel/Hello.cs b/ServiceStack/tests/NorthwindBlazor/ServiceModel/Hello.cs index b71b30675ce..7a9a3ced2d6 100644 --- a/ServiceStack/tests/NorthwindBlazor/ServiceModel/Hello.cs +++ b/ServiceStack/tests/NorthwindBlazor/ServiceModel/Hello.cs @@ -1,4 +1,5 @@ using ServiceStack; +using MyApp; namespace MyApp.ServiceModel; From fe4552de2b14d8f9e7381d3e01c52f7a3dd888a1 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 00:28:37 +0800 Subject: [PATCH 024/140] Move AdhocNew to OpenApi.Microsoft and Scalar UI --- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 17 +- .../tests/AdhocNew/Configure.OpenApi.cs | 36 +- ServiceStack/tests/AdhocNew/swagger.json | 2788 ----------------- 3 files changed, 29 insertions(+), 2812 deletions(-) delete mode 100644 ServiceStack/tests/AdhocNew/swagger.json diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index ba768e59f06..d9f3d98870c 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -25,10 +25,8 @@ - - - - + + @@ -47,7 +45,7 @@ - + @@ -55,5 +53,12 @@ - + + + + + + + + diff --git a/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs b/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs index 79ee417fdaa..58d97bceafe 100644 --- a/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs +++ b/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs @@ -1,4 +1,5 @@ using MyApp.Data; +using Scalar.AspNetCore; [assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] @@ -10,24 +11,23 @@ public void Configure(IWebHostBuilder builder) => builder .ConfigureServices((context, services) => { if (context.HostingEnvironment.IsDevelopment()) { - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); - - services.AddServiceStackSwagger(); - services.AddBasicAuth(); - //services.AddJwtAuth(); - - services.AddTransient(); + services.AddOpenApi(); + services.AddServiceStackOpenApi(configure: metadata => + { + metadata.AddBasicAuth(); + //metadata.AddJwtBearer(); + }); + } + }) + .Configure((context, app) => { + if (context.HostingEnvironment.IsDevelopment()) + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapOpenApi(); + endpoints.MapScalarApiReference(); + }); } }); - - public class StartupFilter : IStartupFilter - { - public Action Configure(Action next) => app => - { - app.UseSwagger(); - app.UseSwaggerUI(); - next(app); - }; - } } \ No newline at end of file diff --git a/ServiceStack/tests/AdhocNew/swagger.json b/ServiceStack/tests/AdhocNew/swagger.json deleted file mode 100644 index 48284c4f88a..00000000000 --- a/ServiceStack/tests/AdhocNew/swagger.json +++ /dev/null @@ -1,2788 +0,0 @@ -{ - "openapi": "3.0.4", - "info": { - "title": "AdhocNew", - "version": "1.0" - }, - "paths": { - "/hello/{Name}": { - "get": { - "tags": [ - "AdhocNew" - ], - "parameters": [ - { - "name": "Name", - "in": "path", - "required": true, - "schema": { - "type": "string", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HelloResponse" - } - } - } - } - } - } - }, - "/v1/chat/completions": { - "post": { - "tags": [ - "AI" - ], - "summary": "Chat Completions API (OpenAI-Compatible)", - "description": "The industry-standard, message-based interface for interfacing with Large Language Models.", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "ChatCompletion", - "type": "object", - "properties": { - "audio": { - "$ref": "#/components/schemas/AiChatAudio" - }, - "enable_thinking": { - "type": "boolean", - "description": "Whether to enable thinking mode for some Qwen models and providers.", - "nullable": true - }, - "frequency_penalty": { - "type": "number", - "description": "Number between `-2.0` and `2.0`. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", - "format": "double", - "nullable": true - }, - "logit_bias": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - }, - "description": "Modify the likelihood of specified tokens appearing in the completion.", - "nullable": true - }, - "logprobs": { - "type": "boolean", - "description": "Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the content of message.", - "nullable": true - }, - "max_completion_tokens": { - "type": "integer", - "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.", - "format": "int32", - "nullable": true - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AiMessage" - }, - "description": "The messages to generate chat completions for." - }, - "metadata": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", - "nullable": true - }, - "modalities": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Output types that you would like the model to generate. Most models are capable of generating text, which is the default:", - "nullable": true - }, - "model": { - "type": "string", - "description": "ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API" - }, - "n": { - "type": "integer", - "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", - "format": "int32", - "nullable": true - }, - "parallel_tool_calls": { - "type": "boolean", - "description": "Whether to enable parallel function calling during tool use.", - "nullable": true - }, - "presence_penalty": { - "type": "number", - "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", - "format": "double", - "nullable": true - }, - "prompt_cache_key": { - "type": "string", - "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.", - "nullable": true - }, - "reasoning_effort": { - "type": "string", - "description": "Constrains effort on reasoning for reasoning models. Currently supported values are minimal, low, medium, and high (none, default). Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.", - "nullable": true - }, - "response_format": { - "$ref": "#/components/schemas/AiResponseFormat" - }, - "safety_identifier": { - "type": "string", - "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.", - "nullable": true - }, - "seed": { - "type": "integer", - "description": "This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend.", - "format": "int32", - "nullable": true - }, - "service_tier": { - "type": "string", - "description": "Specifies the processing type used for serving the request.", - "nullable": true - }, - "stop": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Up to 4 sequences where the API will stop generating further tokens.", - "nullable": true - }, - "store": { - "type": "boolean", - "description": "Whether or not to store the output of this chat completion request for use in our model distillation or evals products.", - "nullable": true - }, - "stream": { - "type": "boolean", - "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a `data: [DONE]` message.", - "nullable": true - }, - "temperature": { - "type": "number", - "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.", - "format": "double", - "nullable": true - }, - "tools": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Tool" - }, - "description": "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.", - "nullable": true - }, - "top_logprobs": { - "type": "integer", - "description": "An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. logprobs must be set to true if this parameter is used.", - "format": "int32", - "nullable": true - }, - "top_p": { - "type": "number", - "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.", - "format": "double", - "nullable": true - }, - "verbosity": { - "type": "string", - "description": "Constrains the verbosity of the model's response. Lower values will result in more concise responses, while higher values will result in more verbose responses. Currently supported values are low, medium, and high.", - "nullable": true - } - }, - "description": "Chat Completions API (OpenAI-Compatible)" - }, - "encoding": { - "audio": { - "style": "form", - "explode": false - }, - "enable_thinking": { - "style": "form", - "explode": false - }, - "frequency_penalty": { - "style": "form", - "explode": false - }, - "logit_bias": { - "style": "form", - "explode": false - }, - "logprobs": { - "style": "form", - "explode": false - }, - "max_completion_tokens": { - "style": "form", - "explode": false - }, - "messages": { - "style": "form", - "explode": false - }, - "metadata": { - "style": "form", - "explode": false - }, - "modalities": { - "style": "form", - "explode": false - }, - "model": { - "style": "form", - "explode": false - }, - "n": { - "style": "form", - "explode": false - }, - "parallel_tool_calls": { - "style": "form", - "explode": false - }, - "presence_penalty": { - "style": "form", - "explode": false - }, - "prompt_cache_key": { - "style": "form", - "explode": false - }, - "reasoning_effort": { - "style": "form", - "explode": false - }, - "response_format": { - "style": "form", - "explode": false - }, - "safety_identifier": { - "style": "form", - "explode": false - }, - "seed": { - "style": "form", - "explode": false - }, - "service_tier": { - "style": "form", - "explode": false - }, - "stop": { - "style": "form", - "explode": false - }, - "store": { - "style": "form", - "explode": false - }, - "stream": { - "style": "form", - "explode": false - }, - "temperature": { - "style": "form", - "explode": false - }, - "tools": { - "style": "form", - "explode": false - }, - "top_logprobs": { - "style": "form", - "explode": false - }, - "top_p": { - "style": "form", - "explode": false - }, - "verbosity": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChatCompletion" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChatResponse" - } - } - } - } - } - } - }, - "/auth/{provider}": { - "post": { - "tags": [ - "auth" - ], - "summary": "Sign In", - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "style": "simple", - "explode": true, - "schema": { - "type": "string", - "description": "AuthProvider, e.g. credentials" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "Authenticate", - "type": "object", - "properties": { - "userName": { - "type": "string" - }, - "password": { - "type": "string" - }, - "rememberMe": { - "type": "boolean", - "nullable": true - }, - "accessToken": { - "type": "string" - }, - "accessTokenSecret": { - "type": "string" - }, - "returnUrl": { - "type": "string" - }, - "errorView": { - "type": "string" - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - } - } - }, - "description": "Sign In" - }, - "encoding": { - "userName": { - "style": "form", - "explode": false - }, - "password": { - "style": "form", - "explode": false - }, - "rememberMe": { - "style": "form", - "explode": false - }, - "accessToken": { - "style": "form", - "explode": false - }, - "accessTokenSecret": { - "style": "form", - "explode": false - }, - "returnUrl": { - "style": "form", - "explode": false - }, - "errorView": { - "style": "form", - "explode": false - }, - "meta": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/Authenticate" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticateResponse" - } - } - } - } - } - } - }, - "/auth": { - "get": { - "tags": [ - "auth" - ], - "summary": "Sign In", - "parameters": [ - { - "name": "provider", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "UserName", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Password", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "RememberMe", - "in": "query", - "schema": { - "type": "boolean", - "nullable": true - } - }, - { - "name": "AccessToken", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "AccessTokenSecret", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "ReturnUrl", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "ErrorView", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Meta", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticateResponse" - } - } - } - } - } - }, - "post": { - "tags": [ - "auth" - ], - "summary": "Sign In", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "Authenticate", - "type": "object", - "properties": { - "provider": { - "type": "string", - "description": "AuthProvider, e.g. credentials" - }, - "userName": { - "type": "string" - }, - "password": { - "type": "string" - }, - "rememberMe": { - "type": "boolean", - "nullable": true - }, - "accessToken": { - "type": "string" - }, - "accessTokenSecret": { - "type": "string" - }, - "returnUrl": { - "type": "string" - }, - "errorView": { - "type": "string" - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - } - } - }, - "description": "Sign In" - }, - "encoding": { - "provider": { - "style": "form", - "explode": false - }, - "userName": { - "style": "form", - "explode": false - }, - "password": { - "style": "form", - "explode": false - }, - "rememberMe": { - "style": "form", - "explode": false - }, - "accessToken": { - "style": "form", - "explode": false - }, - "accessTokenSecret": { - "style": "form", - "explode": false - }, - "returnUrl": { - "style": "form", - "explode": false - }, - "errorView": { - "style": "form", - "explode": false - }, - "meta": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/Authenticate" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticateResponse" - } - } - } - } - } - } - }, - "/bookings": { - "get": { - "tags": [ - "bookings" - ], - "summary": "Find Bookings", - "description": "Find out how to quickly create a C# Bookings App from Scratch", - "parameters": [ - { - "name": "Id", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "Skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "Take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "OrderBy", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "OrderByDesc", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Include", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Fields", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Meta", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BookingQueryResponse" - } - } - } - } - } - }, - "post": { - "tags": [ - "bookings" - ], - "summary": "Create a new Booking", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "CreateBooking", - "required": [ - "name", - "roomNumber", - "cost" - ], - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name this Booking is for" - }, - "roomType": { - "$ref": "#/components/schemas/RoomType" - }, - "roomNumber": { - "type": "integer", - "format": "int32" - }, - "cost": { - "type": "number", - "format": "double" - }, - "bookingStartDate": { - "type": "string", - "format": "date-time" - }, - "bookingEndDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "notes": { - "type": "string", - "nullable": true - }, - "couponId": { - "type": "string", - "nullable": true - } - }, - "description": "Create a new Booking" - }, - "encoding": { - "name": { - "style": "form", - "explode": false - }, - "roomType": { - "style": "form", - "explode": false - }, - "roomNumber": { - "style": "form", - "explode": false - }, - "cost": { - "style": "form", - "explode": false - }, - "bookingStartDate": { - "style": "form", - "explode": false - }, - "bookingEndDate": { - "style": "form", - "explode": false - }, - "notes": { - "style": "form", - "explode": false - }, - "couponId": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateBooking" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IdResponse" - } - } - } - } - }, - "security": [ - { - "basic": [ ] - } - ] - } - }, - "/bookings/{Id}": { - "get": { - "tags": [ - "bookings" - ], - "summary": "Find Bookings", - "description": "Find out how to quickly create a C# Bookings App from Scratch", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "Skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "Take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "OrderBy", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "OrderByDesc", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Include", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Fields", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Meta", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BookingQueryResponse" - } - } - } - } - } - } - }, - "/coupons": { - "get": { - "tags": [ - "bookings" - ], - "summary": "Find Coupons", - "parameters": [ - { - "name": "Id", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "Take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "nullable": true - } - }, - { - "name": "OrderBy", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "OrderByDesc", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Include", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Fields", - "in": "query", - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "Meta", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CouponQueryResponse" - } - } - } - } - } - }, - "post": { - "tags": [ - "bookings" - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "CreateCoupon", - "required": [ - "id", - "description", - "discount", - "expiryDate" - ], - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "discount": { - "type": "integer", - "format": "int32" - }, - "expiryDate": { - "type": "string", - "format": "date-time" - } - }, - "description": "CreateCoupon" - }, - "encoding": { - "id": { - "style": "form", - "explode": false - }, - "description": { - "style": "form", - "explode": false - }, - "discount": { - "style": "form", - "explode": false - }, - "expiryDate": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCoupon" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IdResponse" - } - } - } - } - }, - "security": [ - { - "basic": [ ] - } - ] - } - }, - "/booking/{Id}": { - "patch": { - "tags": [ - "bookings" - ], - "summary": "Update an existing Booking", - "description": "Find out how to quickly create a C# Bookings App from Scratch", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "style": "simple", - "explode": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "UpdateBooking", - "required": [ - "roomNumber", - "cost" - ], - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - }, - "roomType": { - "$ref": "#/components/schemas/RoomType" - }, - "roomNumber": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "cost": { - "type": "number", - "format": "double", - "nullable": true - }, - "bookingStartDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "bookingEndDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "notes": { - "type": "string", - "nullable": true - }, - "couponId": { - "type": "string", - "nullable": true - }, - "cancelled": { - "type": "boolean", - "nullable": true - } - }, - "description": "Update an existing Booking" - }, - "encoding": { - "name": { - "style": "form", - "explode": false - }, - "roomType": { - "style": "form", - "explode": false - }, - "roomNumber": { - "style": "form", - "explode": false - }, - "cost": { - "style": "form", - "explode": false - }, - "bookingStartDate": { - "style": "form", - "explode": false - }, - "bookingEndDate": { - "style": "form", - "explode": false - }, - "notes": { - "style": "form", - "explode": false - }, - "couponId": { - "style": "form", - "explode": false - }, - "cancelled": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateBooking" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IdResponse" - } - } - } - } - }, - "security": [ - { - "basic": [ ] - } - ] - }, - "delete": { - "tags": [ - "bookings" - ], - "summary": "Delete a Booking", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "security": [ - { - "basic": [ ] - } - ] - } - }, - "/coupons/{Id}": { - "patch": { - "tags": [ - "bookings" - ], - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "style": "simple", - "explode": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "title": "UpdateCoupon", - "required": [ - "description", - "discount", - "expiryDate" - ], - "type": "object", - "properties": { - "description": { - "type": "string", - "nullable": true - }, - "discount": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "expiryDate": { - "type": "string", - "format": "date-time", - "nullable": true - } - }, - "description": "UpdateCoupon" - }, - "encoding": { - "description": { - "style": "form", - "explode": false - }, - "discount": { - "style": "form", - "explode": false - }, - "expiryDate": { - "style": "form", - "explode": false - } - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCoupon" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IdResponse" - } - } - } - } - }, - "security": [ - { - "basic": [ ] - } - ] - }, - "delete": { - "tags": [ - "bookings" - ], - "summary": "Delete a Coupon", - "parameters": [ - { - "name": "Id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "nullable": true - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "security": [ - { - "basic": [ ] - } - ] - } - } - }, - "components": { - "schemas": { - "AdminData": { - "title": "AdminData", - "type": "object", - "description": "AdminData" - }, - "AiChatAudio": { - "title": "AiChatAudio", - "type": "object", - "properties": { - "format": { - "type": "string", - "description": "Specifies the output audio format. Must be one of wav, mp3, flac, opus, or pcm16." - }, - "voice": { - "type": "string", - "description": "The voice the model uses to respond. Supported voices are alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, and shimmer." - } - }, - "description": "Parameters for audio output. Required when audio output is requested with modalities: [audio]" - }, - "AiCompletionUsage": { - "type": "object", - "properties": { - "acceptedPredictionTokens": { - "type": "integer", - "description": "When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.\n\n", - "format": "int32" - }, - "audioTokens": { - "type": "integer", - "description": "Audio input tokens generated by the model.", - "format": "int32" - }, - "reasoningTokens": { - "type": "integer", - "description": "Tokens generated by the model for reasoning.", - "format": "int32" - }, - "rejectedPredictionTokens": { - "type": "integer", - "description": "When using Predicted Outputs, the number of tokens in the prediction that did not appear in the completion.", - "format": "int32" - } - }, - "additionalProperties": false - }, - "AiContent": { - "title": "AiContent", - "type": "object", - "description": "AiContent" - }, - "AiMessage": { - "title": "AiMessage", - "type": "object", - "properties": { - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AiContent" - }, - "description": "The contents of the message.", - "nullable": true - }, - "name": { - "type": "string", - "description": "An optional name for the participant. Provides the model information to differentiate between participants of the same role.", - "nullable": true - }, - "role": { - "type": "string", - "description": "The role of the author of this message. Valid values are `system`, `user`, `assistant` and `tool`." - }, - "tool_call_id": { - "type": "string", - "description": "Tool call that this message is responding to.", - "nullable": true - }, - "tool_calls": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ToolCall" - }, - "description": "The tool calls generated by the model, such as function calls.", - "nullable": true - } - }, - "description": "A list of messages comprising the conversation so far." - }, - "AiPromptUsage": { - "type": "object", - "properties": { - "acceptedPredictionTokens": { - "type": "integer", - "description": "When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.\n\n", - "format": "int32" - }, - "audioTokens": { - "type": "integer", - "description": "Audio input tokens present in the prompt.", - "format": "int32" - }, - "cachedTokens": { - "type": "integer", - "description": "Cached tokens present in the prompt.", - "format": "int32" - } - }, - "additionalProperties": false - }, - "AiResponseFormat": { - "title": "AiResponseFormat", - "type": "object", - "properties": { - "response_format": { - "$ref": "#/components/schemas/ResponseFormat" - } - }, - "description": "AiResponseFormat" - }, - "AiUsage": { - "type": "object", - "properties": { - "completionTokens": { - "type": "integer", - "description": "Number of tokens in the generated completion.", - "format": "int32" - }, - "promptTokens": { - "type": "integer", - "description": "Number of tokens in the prompt.", - "format": "int32" - }, - "totalTokens": { - "type": "integer", - "description": "Total number of tokens used in the request (prompt + completion).", - "format": "int32" - }, - "completionTokensDetails": { - "$ref": "#/components/schemas/AiCompletionUsage" - }, - "promptTokensDetails": { - "$ref": "#/components/schemas/AiPromptUsage" - } - }, - "additionalProperties": false - }, - "Authenticate": { - "title": "Authenticate", - "type": "object", - "properties": { - "provider": { - "type": "string", - "description": "AuthProvider, e.g. credentials" - }, - "userName": { - "type": "string" - }, - "password": { - "type": "string" - }, - "rememberMe": { - "type": "boolean", - "nullable": true - }, - "accessToken": { - "type": "string" - }, - "accessTokenSecret": { - "type": "string" - }, - "returnUrl": { - "type": "string" - }, - "errorView": { - "type": "string" - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - } - } - }, - "description": "Sign In" - }, - "AuthenticateResponse": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "nullable": true - }, - "sessionId": { - "type": "string", - "nullable": true - }, - "userName": { - "type": "string", - "nullable": true - }, - "displayName": { - "type": "string", - "nullable": true - }, - "referrerUrl": { - "type": "string", - "nullable": true - }, - "bearerToken": { - "type": "string", - "nullable": true - }, - "refreshToken": { - "type": "string", - "nullable": true - }, - "refreshTokenExpiry": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "profileUrl": { - "type": "string", - "nullable": true - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "permissions": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "authProvider": { - "type": "string", - "nullable": true - }, - "responseStatus": { - "$ref": "#/components/schemas/ResponseStatus" - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "nullable": true - } - }, - "additionalProperties": false - }, - "Booking": { - "type": "object", - "properties": { - "createdDate": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string", - "nullable": true - }, - "modifiedDate": { - "type": "string", - "format": "date-time" - }, - "modifiedBy": { - "type": "string", - "nullable": true - }, - "deletedDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "id": { - "type": "integer", - "format": "int32" - }, - "name": { - "type": "string", - "nullable": true - }, - "roomType": { - "$ref": "#/components/schemas/RoomType" - }, - "roomNumber": { - "type": "integer", - "format": "int32" - }, - "bookingStartDate": { - "type": "string", - "format": "date-time" - }, - "bookingEndDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "cost": { - "type": "number", - "format": "double" - }, - "couponId": { - "type": "string", - "nullable": true - }, - "discount": { - "$ref": "#/components/schemas/Coupon" - }, - "notes": { - "type": "string", - "nullable": true - }, - "cancelled": { - "type": "boolean", - "nullable": true - }, - "employee": { - "$ref": "#/components/schemas/User" - } - }, - "additionalProperties": false - }, - "BookingQueryResponse": { - "type": "object", - "properties": { - "offset": { - "type": "integer", - "format": "int32" - }, - "total": { - "type": "integer", - "format": "int32" - }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Booking" - }, - "nullable": true - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - }, - "responseStatus": { - "$ref": "#/components/schemas/ResponseStatus" - } - }, - "additionalProperties": false - }, - "ChatCompletion": { - "title": "ChatCompletion", - "type": "object", - "properties": { - "audio": { - "$ref": "#/components/schemas/AiChatAudio" - }, - "enable_thinking": { - "type": "boolean", - "description": "Whether to enable thinking mode for some Qwen models and providers.", - "nullable": true - }, - "frequency_penalty": { - "type": "number", - "description": "Number between `-2.0` and `2.0`. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", - "format": "double", - "nullable": true - }, - "logit_bias": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - }, - "description": "Modify the likelihood of specified tokens appearing in the completion.", - "nullable": true - }, - "logprobs": { - "type": "boolean", - "description": "Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the content of message.", - "nullable": true - }, - "max_completion_tokens": { - "type": "integer", - "description": "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.", - "format": "int32", - "nullable": true - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AiMessage" - }, - "description": "The messages to generate chat completions for." - }, - "metadata": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", - "nullable": true - }, - "modalities": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Output types that you would like the model to generate. Most models are capable of generating text, which is the default:", - "nullable": true - }, - "model": { - "type": "string", - "description": "ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API" - }, - "n": { - "type": "integer", - "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", - "format": "int32", - "nullable": true - }, - "parallel_tool_calls": { - "type": "boolean", - "description": "Whether to enable parallel function calling during tool use.", - "nullable": true - }, - "presence_penalty": { - "type": "number", - "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", - "format": "double", - "nullable": true - }, - "prompt_cache_key": { - "type": "string", - "description": "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.", - "nullable": true - }, - "reasoning_effort": { - "type": "string", - "description": "Constrains effort on reasoning for reasoning models. Currently supported values are minimal, low, medium, and high (none, default). Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.", - "nullable": true - }, - "response_format": { - "$ref": "#/components/schemas/AiResponseFormat" - }, - "safety_identifier": { - "type": "string", - "description": "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.", - "nullable": true - }, - "seed": { - "type": "integer", - "description": "This feature is in Beta. If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend.", - "format": "int32", - "nullable": true - }, - "service_tier": { - "type": "string", - "description": "Specifies the processing type used for serving the request.", - "nullable": true - }, - "stop": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Up to 4 sequences where the API will stop generating further tokens.", - "nullable": true - }, - "store": { - "type": "boolean", - "description": "Whether or not to store the output of this chat completion request for use in our model distillation or evals products.", - "nullable": true - }, - "stream": { - "type": "boolean", - "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a `data: [DONE]` message.", - "nullable": true - }, - "temperature": { - "type": "number", - "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.", - "format": "double", - "nullable": true - }, - "tools": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Tool" - }, - "description": "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.", - "nullable": true - }, - "top_logprobs": { - "type": "integer", - "description": "An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. logprobs must be set to true if this parameter is used.", - "format": "int32", - "nullable": true - }, - "top_p": { - "type": "number", - "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.", - "format": "double", - "nullable": true - }, - "verbosity": { - "type": "string", - "description": "Constrains the verbosity of the model's response. Lower values will result in more concise responses, while higher values will result in more verbose responses. Currently supported values are low, medium, and high.", - "nullable": true - } - }, - "description": "Chat Completions API (OpenAI-Compatible)" - }, - "ChatResponse": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "A unique identifier for the chat completion.", - "nullable": true - }, - "choices": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Choice" - }, - "description": "A list of chat completion choices. Can be more than one if n is greater than 1.", - "nullable": true - }, - "created": { - "type": "integer", - "description": "The Unix timestamp (in seconds) of when the chat completion was created.", - "format": "int64" - }, - "model": { - "type": "string", - "description": "The model used for the chat completion.", - "nullable": true - }, - "systemFingerprint": { - "type": "string", - "description": "This fingerprint represents the backend configuration that the model runs with.", - "nullable": true - }, - "object": { - "type": "string", - "description": "The object type, which is always chat.completion.", - "nullable": true - }, - "serviceTier": { - "type": "string", - "description": "Specifies the processing type used for serving the request.", - "nullable": true - }, - "usage": { - "$ref": "#/components/schemas/AiUsage" - }, - "provider": { - "type": "string", - "description": "The provider used for the chat completion.", - "nullable": true - }, - "metadata": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format.", - "nullable": true - }, - "responseStatus": { - "$ref": "#/components/schemas/ResponseStatus" - } - }, - "additionalProperties": false - }, - "Choice": { - "type": "object", - "properties": { - "finishReason": { - "type": "string", - "description": "The reason the model stopped generating tokens. This will be stop if the model hit a natural stop point or a provided stop sequence, length if the maximum number of tokens specified in the request was reached, content_filter if content was omitted due to a flag from our content filters, tool_calls if the model called a tool", - "nullable": true - }, - "index": { - "type": "integer", - "description": "The index of the choice in the list of choices.", - "format": "int32" - }, - "message": { - "$ref": "#/components/schemas/ChoiceMessage" - } - }, - "additionalProperties": false - }, - "ChoiceAnnotation": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "The type of the URL citation. Always url_citation.", - "nullable": true - }, - "urlCitation": { - "$ref": "#/components/schemas/UrlCitation" - } - }, - "additionalProperties": false - }, - "ChoiceAudio": { - "type": "object", - "properties": { - "data": { - "type": "string", - "description": "Base64 encoded audio bytes generated by the model, in the format specified in the request.", - "nullable": true - }, - "expiresAt": { - "type": "integer", - "description": "The Unix timestamp (in seconds) for when this audio response will no longer be accessible on the server for use in multi-turn conversations.", - "format": "int32" - }, - "id": { - "type": "string", - "description": "Unique identifier for this audio response.", - "nullable": true - }, - "transcript": { - "type": "string", - "description": "Transcript of the audio generated by the model.", - "nullable": true - } - }, - "additionalProperties": false - }, - "ChoiceMessage": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The contents of the message.", - "nullable": true - }, - "refusal": { - "type": "string", - "description": "The refusal message generated by the model.", - "nullable": true - }, - "reasoning": { - "type": "string", - "description": "The reasoning process used by the model.", - "nullable": true - }, - "role": { - "type": "string", - "description": "The role of the author of this message.", - "nullable": true - }, - "annotations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ChoiceAnnotation" - }, - "description": "Annotations for the message, when applicable, as when using the web search tool.", - "nullable": true - }, - "audio": { - "$ref": "#/components/schemas/ChoiceAudio" - }, - "toolCalls": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ToolCall" - }, - "description": "The tool calls generated by the model, such as function calls.", - "nullable": true - } - }, - "additionalProperties": false - }, - "Coupon": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string", - "nullable": true - }, - "discount": { - "type": "integer", - "format": "int32" - }, - "expiryDate": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false - }, - "CouponQueryResponse": { - "type": "object", - "properties": { - "offset": { - "type": "integer", - "format": "int32" - }, - "total": { - "type": "integer", - "format": "int32" - }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Coupon" - }, - "nullable": true - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - }, - "responseStatus": { - "$ref": "#/components/schemas/ResponseStatus" - } - }, - "additionalProperties": false - }, - "CreateBooking": { - "title": "CreateBooking", - "required": [ - "name", - "roomNumber", - "cost" - ], - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name this Booking is for" - }, - "roomType": { - "$ref": "#/components/schemas/RoomType" - }, - "roomNumber": { - "type": "integer", - "format": "int32" - }, - "cost": { - "type": "number", - "format": "double" - }, - "bookingStartDate": { - "type": "string", - "format": "date-time" - }, - "bookingEndDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "notes": { - "type": "string", - "nullable": true - }, - "couponId": { - "type": "string", - "nullable": true - } - }, - "description": "Create a new Booking" - }, - "CreateCoupon": { - "title": "CreateCoupon", - "required": [ - "id", - "description", - "discount", - "expiryDate" - ], - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "discount": { - "type": "integer", - "format": "int32" - }, - "expiryDate": { - "type": "string", - "format": "date-time" - } - }, - "description": "CreateCoupon" - }, - "DeleteBooking": { - "title": "DeleteBooking", - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32" - } - }, - "description": "Delete a Booking" - }, - "DeleteCoupon": { - "title": "DeleteCoupon", - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "description": "Delete a Coupon" - }, - "Hello": { - "title": "Hello", - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - } - }, - "description": "Hello" - }, - "HelloResponse": { - "required": [ - "result" - ], - "type": "object", - "properties": { - "result": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, - "IdResponse": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true - }, - "responseStatus": { - "$ref": "#/components/schemas/ResponseStatus" - } - }, - "additionalProperties": false - }, - "QueryBookings": { - "title": "QueryBookings", - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "skip": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "take": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "orderBy": { - "type": "string", - "nullable": true - }, - "orderByDesc": { - "type": "string", - "nullable": true - }, - "include": { - "type": "string", - "nullable": true - }, - "fields": { - "type": "string", - "nullable": true - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "nullable": true - } - }, - "description": "Find Bookings" - }, - "QueryCoupons": { - "title": "QueryCoupons", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "skip": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "take": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "orderBy": { - "type": "string", - "nullable": true - }, - "orderByDesc": { - "type": "string", - "nullable": true - }, - "include": { - "type": "string", - "nullable": true - }, - "fields": { - "type": "string", - "nullable": true - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "nullable": true - } - }, - "description": "Find Coupons" - }, - "QueryUsers": { - "title": "QueryUsers", - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true - }, - "skip": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "take": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "orderBy": { - "type": "string", - "nullable": true - }, - "orderByDesc": { - "type": "string", - "nullable": true - }, - "include": { - "type": "string", - "nullable": true - }, - "fields": { - "type": "string", - "nullable": true - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "nullable": true - } - }, - "description": "QueryUsers" - }, - "QueueCheckUrls": { - "title": "QueueCheckUrls", - "type": "object", - "properties": { - "urls": { - "type": "string" - } - }, - "description": "QueueCheckUrls" - }, - "ResponseError": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "nullable": true - }, - "fieldName": { - "type": "string", - "nullable": true - }, - "message": { - "type": "string", - "nullable": true - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - } - }, - "additionalProperties": false - }, - "ResponseFormat": { - "enum": [ - "Text", - "JsonObject" - ], - "type": "string" - }, - "ResponseStatus": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "nullable": true - }, - "message": { - "type": "string", - "nullable": true - }, - "stackTrace": { - "type": "string", - "nullable": true - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResponseError" - }, - "nullable": true - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - } - }, - "additionalProperties": false - }, - "RoomType": { - "enum": [ - "Single", - "Double", - "Queen", - "Twin", - "Suite" - ], - "type": "string" - }, - "SearchRootSummary": { - "title": "SearchRootSummary", - "type": "object", - "properties": { - "skip": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "take": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "orderBy": { - "type": "string", - "nullable": true - }, - "orderByDesc": { - "type": "string", - "nullable": true - }, - "include": { - "type": "string", - "nullable": true - }, - "fields": { - "type": "string", - "nullable": true - }, - "meta": { - "title": "Dictionary", - "type": "object", - "additionalProperties": { - "type": "string", - "nullable": true - }, - "nullable": true - } - }, - "description": "SearchRootSummary" - }, - "TestNullable": { - "title": "TestNullable", - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "requiredId": { - "type": "integer", - "format": "int32" - }, - "ids": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - }, - "nullable": true - }, - "requiredIds": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - } - }, - "names": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "requiredNames": { - "type": "array", - "items": { - "type": "string" - } - }, - "roomNumbers": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - }, - "nullable": true - }, - "requiredRoomNumbers": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - } - } - }, - "description": "TestNullable" - }, - "Tool": { - "title": "Tool", - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/ToolType" - } - }, - "description": "Tool" - }, - "ToolCall": { - "title": "ToolCall", - "type": "object", - "properties": { - "function": { - "type": "string", - "description": "The function that the model called." - }, - "id": { - "type": "string", - "description": "The ID of the tool call." - }, - "type": { - "type": "string", - "description": "The type of the tool. Currently, only `function` is supported." - } - }, - "description": "The tool calls generated by the model, such as function calls." - }, - "ToolType": { - "enum": [ - "Function" - ], - "type": "string" - }, - "UpdateBooking": { - "title": "UpdateBooking", - "required": [ - "roomNumber", - "cost" - ], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32" - }, - "name": { - "type": "string", - "nullable": true - }, - "roomType": { - "$ref": "#/components/schemas/RoomType" - }, - "roomNumber": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "cost": { - "type": "number", - "format": "double", - "nullable": true - }, - "bookingStartDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "bookingEndDate": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "notes": { - "type": "string", - "nullable": true - }, - "couponId": { - "type": "string", - "nullable": true - }, - "cancelled": { - "type": "boolean", - "nullable": true - } - }, - "description": "Update an existing Booking" - }, - "UpdateCoupon": { - "title": "UpdateCoupon", - "required": [ - "description", - "discount", - "expiryDate" - ], - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "description": { - "type": "string", - "nullable": true - }, - "discount": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "expiryDate": { - "type": "string", - "format": "date-time", - "nullable": true - } - }, - "description": "UpdateCoupon" - }, - "UrlCitation": { - "type": "object", - "properties": { - "endIndex": { - "type": "integer", - "description": "The index of the last character of the URL citation in the message.", - "format": "int32" - }, - "startIndex": { - "type": "integer", - "description": "The index of the first character of the URL citation in the message.", - "format": "int32" - }, - "title": { - "type": "string", - "description": "The title of the web resource.", - "nullable": true - }, - "url": { - "type": "string", - "description": "The URL of the web resource.", - "nullable": true - } - }, - "additionalProperties": false - }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true - }, - "userName": { - "type": "string", - "nullable": true - }, - "firstName": { - "type": "string", - "nullable": true - }, - "lastName": { - "type": "string", - "nullable": true - }, - "displayName": { - "type": "string", - "nullable": true - }, - "profileUrl": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - } - }, - "securitySchemes": { - "basic": { - "type": "http", - "description": "HTTP Basic access authentication", - "scheme": "basic" - } - } - } -} \ No newline at end of file From 7df32613bc4078f608ffb732b56cc30fd2c10a1d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 16:49:25 +0800 Subject: [PATCH 025/140] Don't redirect to /metadata by default --- ServiceStack/src/ServiceStack/HttpHandlerFactory.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack/HttpHandlerFactory.cs b/ServiceStack/src/ServiceStack/HttpHandlerFactory.cs index b2204b42549..eae51a9d64a 100644 --- a/ServiceStack/src/ServiceStack/HttpHandlerFactory.cs +++ b/ServiceStack/src/ServiceStack/HttpHandlerFactory.cs @@ -77,7 +77,6 @@ internal static void Init() if (DefaultHttpHandler == null && !string.IsNullOrEmpty(config.MetadataRedirectPath)) { - DefaultHttpHandler = new RedirectHttpHandler { RelativeUrl = config.MetadataRedirectPath }; NonRootModeDefaultHttpHandler = new RedirectHttpHandler { RelativeUrl = config.MetadataRedirectPath }; } From 9425c108d3a1b747a8f98563885f1d79ec4f5e16 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 16:49:57 +0800 Subject: [PATCH 026/140] Make .net10 build of ServiceStack.Common and remove netstandard2.0 --- .../ServiceStack.Common.csproj | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj index 48c9bf87332..d7a3e9dccc0 100644 --- a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj +++ b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj @@ -2,7 +2,7 @@ ServiceStack.Common ServiceStack.Common - net472;netstandard2.0;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 ServiceStack Common libraries for ServiceStack projects #Script, Virtual File System, SimpleContainer and Common library for ServiceStack projects. @@ -17,6 +17,9 @@ $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER + @@ -25,15 +28,4 @@ - - - - - - - - - - - \ No newline at end of file From 6febfaa0cdd7234097122989826ce19adc309ac8 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 22 Nov 2025 16:50:05 +0800 Subject: [PATCH 027/140] Add SvgCreator --- .../src/ServiceStack.Common/SvgCreator.cs | 147 ++++++++++++++++++ ServiceStack/src/ServiceStack/SvgFeature.cs | 25 +-- 2 files changed, 148 insertions(+), 24 deletions(-) create mode 100644 ServiceStack/src/ServiceStack.Common/SvgCreator.cs diff --git a/ServiceStack/src/ServiceStack.Common/SvgCreator.cs b/ServiceStack/src/ServiceStack.Common/SvgCreator.cs new file mode 100644 index 00000000000..8267ff7296f --- /dev/null +++ b/ServiceStack/src/ServiceStack.Common/SvgCreator.cs @@ -0,0 +1,147 @@ +#nullable enable +using System; + +namespace ServiceStack; + +public static class SvgCreator +{ + public static string[] DarkColors { get; set; } = + [ + "#334155", + "#374151", + "#44403c", + "#b91c1c", + "#c2410c", + "#b45309", + "#4d7c0f", + "#15803d", + "#047857", + "#0f766e", + "#0e7490", + "#0369a1", + "#1d4ed8", + "#4338ca", + "#6d28d9", + "#7e22ce", + "#a21caf", + "#be185d", + "#be123c", + // + "#824d26", + "#865081", + "#0c7047", + "#0064a7", + "#8220d0", + "#009645", + "#ab00f0", + "#9a3c69", + "#227632", + "#4b40bd", + "#ad3721", + "#6710f2", + "#1a658a", + "#078e57", + "#2721e1", + "#168407", + "#019454", + "#967312", + "#6629d8", + "#108546", + "#9a2aa1", + "#3d7813", + "#257124", + "#6f14ed", + "#1f781d", + "#a29906", + ]; + + public static string GetDarkColor(int index) => DarkColors[index % DarkColors.Length]; + + public static string CreateSvg(char letter, string? bgColor = null, string? textColor = null) + { + #if NET6_0_OR_GREATER + bgColor ??= GetDarkColor(Random.Shared.Next(DarkColors.Length)); + #else + bgColor ??= GetDarkColor(new Random().Next(DarkColors.Length)); + #endif + textColor ??= "#FFF"; + + var svg = $@" + + {letter} + "; + return svg; + } + + public static string CreateSvgDataUri(char letter, string? bgColor = null, string? textColor = null) => + ToDataUri(CreateSvg(letter, bgColor, textColor)); + + public static string Decode(string dataUri) + { + return dataUri + .Replace("'","\"") + .Replace("%25","%") + .Replace("%23","#") + .Replace("%3C","<") + .Replace("%3E",">") + .Replace("%3F","?") + .Replace("%5B","[") + .Replace("%5C","\\") + .Replace("%5D","]") + .Replace("%5E","^") + .Replace("%60","`") + .Replace("%7B","{") + .Replace("%7C","|") + .Replace("%7D","}"); + } + + public static string DataUriToSvg(string dataUri) => Decode(dataUri.RightPart(',')); + + public static char GradeLetter(int votes) => votes >= 9 + ? 'A' + : votes >= 6 + ? 'B' + : votes >= 3 + ? 'C' + : votes >= 2 + ? 'D' + : 'F'; + + public static string GradeBgColor(char grade) => grade switch + { + 'A' => "#16a34a", + 'B' => "#2563eb", + 'C' => "#4b5563", + 'D' => "#dc2626", + _ => "#7f1d1d" + }; + + public static string CreateGradeSvg(char grade) => CreateSvg(grade, GradeBgColor(grade), "#fff"); + + public static string CreateGradeDataUri(char grade) => ToDataUri(CreateGradeSvg(grade)); + + public static string? Encode(string svg) + { + if (string.IsNullOrEmpty(svg)) + return null; + + //['%','#','<','>','?','[','\\',']','^','`','{','|','}'].map(x => `.Replace("${x}","` + encodeURIComponent(x) + `")`).join('') + return svg.Replace("\r", " ").Replace("\n", "") + .Replace("\"","'") + .Replace("%","%25") + .Replace("#","%23") + .Replace("<","%3C") + .Replace(">","%3E") + .Replace("?","%3F") + .Replace("[","%5B") + .Replace("\\","%5C") + .Replace("]","%5D") + .Replace("^","%5E") + .Replace("`","%60") + .Replace("{","%7B") + .Replace("|","%7C") + .Replace("}","%7D"); + } + + public static string ToDataUri(string svg) => "data:image/svg+xml," + Encode(svg); +} \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack/SvgFeature.cs b/ServiceStack/src/ServiceStack/SvgFeature.cs index bb54757aa89..544adb26b67 100644 --- a/ServiceStack/src/ServiceStack/SvgFeature.cs +++ b/ServiceStack/src/ServiceStack/SvgFeature.cs @@ -369,30 +369,7 @@ public static string Fill(string svg, string fillColor) return svg; } - public static string Encode(string svg) - { - if (string.IsNullOrEmpty(svg)) - return null; - - //['%','#','<','>','?','[','\\',']','^','`','{','|','}'].map(x => `.Replace("${x}","` + encodeURIComponent(x) + `")`).join('') - return svg.Replace("\r", " ").Replace("\n", "") - .Replace("\"","'") - .Replace("%","%25") - .Replace("#","%23") - .Replace("<","%3C") - .Replace(">","%3E") - .Replace("?","%3F") - .Replace("[","%5B") - .Replace("\\","%5C") - .Replace("]","%5D") - .Replace("^","%5E") - .Replace("`","%60") - .Replace("{","%7B") - .Replace("|","%7C") - .Replace("}","%7D"); - } - - public static string ToDataUri(string svg) => "data:image/svg+xml," + Encode(svg); + public static string ToDataUri(string svg) => SvgCreator.ToDataUri(svg); public static string GetBackgroundImageCss(string name) => InBackgroundImageCss(GetImage(name)); public static string GetBackgroundImageCss(string name, string fillColor) => InBackgroundImageCss(GetImage(name, fillColor)); From 528b64f64b156e07bc6f4772af71fe85c8669f52 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sun, 23 Nov 2025 08:36:34 +0800 Subject: [PATCH 028/140] Upgrade to Npgsql 10.* --- .../ServiceStack.OrmLite.PostgreSQL.csproj | 2 +- .../ServiceStack.OrmLite.PostgreSQL.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj index 12cc4210a9c..d652b04cf6c 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj @@ -30,7 +30,7 @@ - + diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj index 34423542a3a..9d743963961 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/ServiceStack.OrmLite.PostgreSQL.Tests.csproj @@ -27,6 +27,6 @@ - + \ No newline at end of file From c73dba0f2adbbcf626d025c05d6f6fc15d7baeda Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 00:47:43 +0800 Subject: [PATCH 029/140] Add NodeProxy --- .../src/ServiceStack.Extensions/NodeProxy.cs | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs new file mode 100644 index 00000000000..fe2b898397a --- /dev/null +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -0,0 +1,467 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack; + +public class NodeProxy +{ + public HttpClient Client { get; set; } + public bool Verbose { get; set; } = true; + public string LogPrefix { get; set; } = "[node] "; + + public List CacheFileExtensions { get; set; } = [ + ".js", + ".css", + ".ico", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".svg", + ".woff", + ".woff2", + ".ttf", + ".eot", + ".otf", + ".map" + ]; + + public Func ShouldCache { get; set; } + + public bool DefaultShouldCache(HttpContext context) + { + // Ignore if local + if (context.Request.Host.Value!.Contains("localhost")) + return false; + // Ignore Cache-Control headers + if (context.Request.Headers.TryGetValue("Cache-Control", out var cacheControlValues)) + return false; + // Ignore if has QueryString + if (context.Request.QueryString.HasValue) + return false; + + var path = context.Request.Path.Value ?? string.Empty; + if (path.Length > 0) + { + foreach (var ext in CacheFileExtensions) + { + if (path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + return false; + } + + public ConcurrentDictionary Cache { get; } = new(); + + public NodeProxy(HttpClient client) + { + Init(client); + } + + public NodeProxy(string baseUrl, bool ignoreCerts = false) + { + // HTTPS not needed when proxying to internally + HttpMessageHandler nextHandler = ignoreCerts + ? new HttpClientHandler { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + } + : new HttpClientHandler(); + var client = new HttpClient(nextHandler) { + BaseAddress = new Uri(baseUrl) + }; + Init(client); + } + + private void Init(HttpClient client) + { + Client = client; + ShouldCache = DefaultShouldCache; + } + + public bool TryStartNode(string workingDirectory, out Process process) + { + process = new Process + { + StartInfo = new() { + FileName = "npm", + Arguments = "run dev", + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }, + EnableRaisingEvents = true, + }; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + if (Verbose) + { + var logPrefixTrimmed = LogPrefix.TrimEnd(); + process.OutputDataReceived += (s, e) => { + if (e.Data != null) + { + if (!string.IsNullOrEmpty(logPrefixTrimmed)) + { + Console.Write(logPrefixTrimmed + ":"); + } + Console.WriteLine(e.Data); + } + }; + process.ErrorDataReceived += (s, e) => { + if (e.Data != null) + { + Console.Write(LogPrefix + "ERROR:"); + Console.WriteLine(e.Data); + } + }; + } + if (!process.Start()) + { + return false; + } + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + return true; + } + + static bool IsHopByHopHeader(string headerName) + { + return headerName.Equals("Connection", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Keep-Alive", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Proxy-Connection", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Upgrade", StringComparison.OrdinalIgnoreCase); + } + + public async Task HttpToNode(HttpContext context) + { + var request = context.Request; + + var cacheKey = request.Path.Value ?? string.Empty; + + // Handle ?clear commands even if this request itself isn't cacheable + var qs = request.QueryString.Value ?? string.Empty; + if (qs.Contains("?clear")) + { + if (qs.Contains("?clear=all")) + { + Cache.Clear(); + } + else + { + Cache.TryRemove(cacheKey, out _); + } + } + + var shouldCache = ShouldCache(context); + if (shouldCache && Cache.TryGetValue(cacheKey, out var cached)) + { + if (Verbose) Console.WriteLine($"Cache hit: {cacheKey} |mimeType| {cached.mimeType} |encoding| {cached.encoding} |size| {cached.data.Length}"); + context.Response.ContentType = cached.mimeType; + if (!string.IsNullOrEmpty(cached.encoding)) + { + context.Response.Headers["Content-Encoding"] = cached.encoding; + } + await context.Response.Body.WriteAsync(cached.data, context.RequestAborted); + return; + } + + // Build relative URI (path + query) + var path = request.Path.HasValue ? request.Path.Value : "/"; + var query = request.QueryString.HasValue ? request.QueryString.Value : string.Empty; + var targetUri = new Uri(path + query, UriKind.Relative); + + using var forwardRequest = new HttpRequestMessage(new HttpMethod(request.Method), targetUri); + + // Copy headers (excluding hop-by-hop headers) + foreach (var header in request.Headers) + { + if (IsHopByHopHeader(header.Key)) + continue; + + if (!forwardRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray())) + { + forwardRequest.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } + + // Copy body for non-GET methods + if (!ServiceStack.HttpMethods.IsGet(request.Method) && + !ServiceStack.HttpMethods.IsHead(request.Method) && + !ServiceStack.HttpMethods.IsDelete(request.Method) && + !ServiceStack.HttpMethods.IsTrace(request.Method)) + { + forwardRequest.Content = new StreamContent(request.Body); + } + + using var response = await Client.SendAsync( + forwardRequest, + HttpCompletionOption.ResponseHeadersRead, + context.RequestAborted); + + context.Response.StatusCode = (int)response.StatusCode; + foreach (var header in response.Headers) + { + if (IsHopByHopHeader(header.Key)) + continue; + + context.Response.Headers[header.Key] = header.Value.ToArray(); + } + foreach (var header in response.Content.Headers) + { + if (IsHopByHopHeader(header.Key)) + continue; + + context.Response.Headers[header.Key] = header.Value.ToArray(); + } + + // ASP.NET Core will set its own transfer-encoding + context.Response.Headers.Remove("transfer-encoding"); + + if (context.Response.StatusCode == StatusCodes.Status200OK) + { + if (shouldCache) + { + var bytes = await response.Content.ReadAsByteArrayAsync(); + if (bytes.Length > 0) + { + var mimeType = response.Content.Headers.ContentType?.ToString() + ?? ServiceStack.MimeTypes.GetMimeType(cacheKey); + var encoding = response.Content.Headers.ContentEncoding.FirstOrDefault(); + Cache[cacheKey] = (mimeType, bytes, encoding); + if (Verbose) Console.WriteLine($"Cache miss: {cacheKey} |mimeType| {mimeType} |encoding| {encoding} |size| {bytes.Length}"); + await context.Response.Body.WriteAsync(bytes, context.RequestAborted); + return; + } + } + } + + await response.Content.CopyToAsync(context.Response.Body, context.RequestAborted); + } +} + +public static class ProxyExtensions +{ + + /// + /// Proxy 404s to Node.js (except for API/backend routes) must be registered before endpoints + /// + public static void MapNotFoundToNode(this WebApplication app, NodeProxy proxy, string[]? ignorePaths=null) + { + app.Use(async (context, next) => + { + await next(); + + if (context.Response.StatusCode == StatusCodes.Status404NotFound && + !context.Response.HasStarted) + { + var pathValue = context.Request.Path.Value ?? string.Empty; + + // Keep backend/api/identity/swagger/auth 404s as-is + if (ignorePaths != null && ignorePaths.Any(x => pathValue.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + { + return; + } + + // Clear the 404 and let Next handle it + context.Response.Clear(); + await proxy.HttpToNode(context); + } + }); + } + + /// + /// Map clean URLs to .html files + /// + public static void MapCleanUrls(this WebApplication app) + { + // Serve .html files without extension + app.Use(async (context, next) => + { + // Only process GET requests that don't have an extension and don't start with /api + var path = context.Request.Path.Value; + if (context.Request.Method == "GET" && !string.IsNullOrEmpty(path) && !Path.HasExtension(path) + && !path.StartsWith("/api", StringComparison.OrdinalIgnoreCase)) + { + var fileProvider = app.Environment.WebRootFileProvider; + var fileInfo = fileProvider.GetFileInfo(path + ".html"); + if (fileInfo.Exists && !fileInfo.IsDirectory) + { + context.Response.ContentType = "text/html"; + using var stream = fileInfo.CreateReadStream(); + await stream.CopyToAsync(context.Response.Body); // Serve the HTML file directly + return; // Don't call next(), we've handled the request + } + } + await next(); + }); + } + + /// + /// Map Next.js HMR WebSocket requests + /// + public static IEndpointConventionBuilder MapNextHmr(this WebApplication app, NodeProxy proxy) + { + return app.Map("/_next/webpack-hmr", async context => + { + if (context.WebSockets.IsWebSocketRequest) + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + else + { + // HMR endpoint expects WebSocket connections only + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("WebSocket connection expected"); + } + }); + } + + /// + /// Proxy WebSocket requests to Node.js + /// + public static async Task WebSocketToNode(HttpContext context, Uri nextServerBase, bool allowInvalidCerts=true) + { + using var clientSocket = await context.WebSockets.AcceptWebSocketAsync(); + + using var nextSocket = new ClientWebSocket(); + if (allowInvalidCerts && nextServerBase.Scheme == "https") + { + nextSocket.Options.RemoteCertificateValidationCallback = (_, _, _, _) => true; + } + + if (context.Request.Headers.TryGetValue("Cookie", out var cookieValues)) + { + nextSocket.Options.SetRequestHeader("Cookie", cookieValues.ToString()); + } + + var builder = new UriBuilder(nextServerBase) + { + Scheme = nextServerBase.Scheme == "https" ? "wss" : "ws", + Path = context.Request.Path.HasValue ? context.Request.Path.Value : "/", + Query = context.Request.QueryString.HasValue + ? context.Request.QueryString.Value!.TrimStart('?') + : string.Empty + }; + + await nextSocket.ConnectAsync(builder.Uri, context.RequestAborted); + + var forwardTask = PumpWebSocket(clientSocket, nextSocket, context.RequestAborted); + var reverseTask = PumpWebSocket(nextSocket, clientSocket, context.RequestAborted); + + await Task.WhenAll(forwardTask, reverseTask); + } + + static async Task PumpWebSocket( + WebSocket source, + WebSocket destination, + CancellationToken cancellationToken=default) + { + try + { + var buffer = new byte[8192]; + + while (source.State == WebSocketState.Open && + destination.State == WebSocketState.Open) + { + var result = await source.ReceiveAsync( + new ArraySegment(buffer), cancellationToken); + + if (result.MessageType == WebSocketMessageType.Close) + { + await destination.CloseAsync( + source.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + source.CloseStatusDescription, + cancellationToken); + break; + } + + await destination.SendAsync( + new ArraySegment(buffer, 0, result.Count), + result.MessageType, + result.EndOfMessage, + cancellationToken); + } + } + catch (Exception e) + { + Console.WriteLine($"WebSocket Proxy: {e.Message}"); + } + } + + /// + /// Run Next.js dev server if not already running + /// + public static System.Diagnostics.Process? RunNodeProcess(this WebApplication app, + NodeProxy proxy, + string lockFile, + string workingDirectory, + bool registerExitHandler=true) + { + var process = app.StartNodeProcess(proxy, lockFile, workingDirectory, registerExitHandler); + var logPrefix = proxy.LogPrefix; + if (process != null) + { + Console.WriteLine(logPrefix + "Started Next.js dev server"); + } + else + { + Console.WriteLine(logPrefix + "Next.js dev server already running"); + } + return process; + } + + public static System.Diagnostics.Process? StartNodeProcess(this WebApplication app, + NodeProxy proxy, + string lockFile, + string workingDirectory, + bool registerExitHandler=true) + { + if (!File.Exists(lockFile)) + { + if (!proxy.TryStartNode(workingDirectory, out var process)) + return null; + + var verbose = proxy.Verbose; + var logPrefix = string.IsNullOrEmpty(proxy.LogPrefix) ? "" : proxy.LogPrefix + " "; + process.Exited += (s, e) => { + if (verbose) Console.WriteLine(logPrefix + "Exited: " + process.ExitCode); + File.Delete(lockFile); + }; + + if (registerExitHandler) + { + app.Lifetime.ApplicationStopping.Register(() => { + if (!process.HasExited) + { + if (verbose) Console.WriteLine(logPrefix + "Teminating process: " + process.Id); + process.Kill(entireProcessTree: true); + } + }); + } + return process; + } + + return null; + } + + public static IEndpointConventionBuilder MapFallbackToNode(this WebApplication app, NodeProxy proxy) + { + return app.MapFallback(proxy.HttpToNode); + } +} From a878ed94c88e0a833b98f3c6efbcbcb72ce56a9a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 08:54:05 +0800 Subject: [PATCH 030/140] No longer redirect to /metadata by default, reenable it by configuring DefaultRedirectPath --- .../Support/Host/ExampleAppHostHttpListener.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/Support/Host/ExampleAppHostHttpListener.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/Support/Host/ExampleAppHostHttpListener.cs index 2d2590ad5f1..d32077a0f08 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/Support/Host/ExampleAppHostHttpListener.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/Support/Host/ExampleAppHostHttpListener.cs @@ -465,6 +465,7 @@ public override void Configure(Container container) WsdlServiceNamespace = "http://www.servicestack.net/types", DebugMode = true, PreferredContentTypes = { MimeTypes.ProtoBuf }, + DefaultRedirectPath = "/metadata" }); this.RegisterRequestBinder( @@ -535,6 +536,7 @@ public override void Configure(Container container) }, WsdlServiceNamespace = "http://www.servicestack.net/types", DebugMode = true, + DefaultRedirectPath = "/metadata" }); this.RegisterRequestBinder( From d8999b02d36e5d157b6c7c538b5893cd5d32f61b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 13:56:24 +0800 Subject: [PATCH 031/140] Add .net 10 cached linq expression test failure --- .../CachedExpressionTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs new file mode 100644 index 00000000000..54f454bfe05 --- /dev/null +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs @@ -0,0 +1,69 @@ +using System; +using NUnit.Framework; +using ServiceStack.Admin; +using ServiceStack.OrmLite; + +namespace ServiceStack.WebHost.Endpoints.Tests; + +public class CachedExpressionTests +{ + [Test] + public void Can_compile_cached_expressions() + { + var dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider); + using var db = dbFactory.Open(); + db.CreateTable(); + + var now = DateTime.UtcNow; + var take = 100; + + var request = new RequestLogs + { + Ids = [1,2,3] + }; + + var q = db.From(); + if (request.BeforeSecs.HasValue) + q = q.Where(x => (now - x.DateTime) <= TimeSpan.FromSeconds(request.BeforeSecs.Value)); + if (request.AfterSecs.HasValue) + q = q.Where(x => (now - x.DateTime) > TimeSpan.FromSeconds(request.AfterSecs.Value)); + if (!request.OperationName.IsNullOrEmpty()) + q = q.Where(x => x.OperationName == request.OperationName); + if (!request.IpAddress.IsNullOrEmpty()) + q = q.Where(x => x.IpAddress == request.IpAddress); + if (!request.ForwardedFor.IsNullOrEmpty()) + q = q.Where(x => x.ForwardedFor == request.ForwardedFor); + if (!request.UserAuthId.IsNullOrEmpty()) + q = q.Where(x => x.UserAuthId == request.UserAuthId); + if (!request.SessionId.IsNullOrEmpty()) + q = q.Where(x => x.SessionId == request.SessionId); + if (!request.Referer.IsNullOrEmpty()) + q = q.Where(x => x.Referer == request.Referer); + if (!request.PathInfo.IsNullOrEmpty()) + q = q.Where(x => x.PathInfo == request.PathInfo); + if (!request.BearerToken.IsNullOrEmpty()) + q = q.Where("Headers LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); + if (!request.Ids.IsEmpty()) + q = q.Where(x => request.Ids.Contains(x.Id)); + if (request.BeforeId.HasValue) + q = q.Where(x => x.Id <= request.BeforeId); + if (request.AfterId.HasValue) + q = q.Where(x => x.Id > request.AfterId); + if (request.WithErrors.HasValue) + q = request.WithErrors.Value + ? q.Where(x => x.Error != null || x.StatusCode >= 400) + : q.Where(x => x.Error == null); + if (request.DurationLongerThan.HasValue) + q = q.Where(x => x.RequestDuration > request.DurationLongerThan.Value); + if (request.DurationLessThan.HasValue) + q = q.Where(x => x.RequestDuration < request.DurationLessThan.Value); + q = string.IsNullOrEmpty(request.OrderBy) + ? q.OrderByDescending(x => x.Id) + : q.OrderBy(request.OrderBy); + q = request.Skip > 0 + ? q.Limit(request.Skip, take) + : q.Limit(take); + + var results = db.Select(q); + } +} \ No newline at end of file From 40e6bf521af5401e590d27a2423595f2fc7d828d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 14:41:50 +0800 Subject: [PATCH 032/140] Fix .net10 cached linq expression test --- .../CachedExpressionCompiler.cs | 62 ++++++++++++++++++- .../CachedExpressionTests.cs | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs index 8a7e0208202..0413a80eb05 100644 --- a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs +++ b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs @@ -212,7 +212,46 @@ private static Func CompileFromMemberAccess(Expression CompileSlow(Expression> expr) { // fallback compilation system - just compile the expression directly - return expr.Compile(); + try + { + return expr.Compile(); + } + catch (Exception) + { + // Try to recover from invalid ref struct boxing (e.g. Span implicit conversion) + if (expr.Body is UnaryExpression u + && u.NodeType == ExpressionType.Convert + && u.Type == typeof(object) + && u.Operand is MethodCallExpression m + && m.Method.Name == "op_Implicit") + { + var returnType = m.Method.ReturnType; + bool isRefStruct = false; +#if NET6_0_OR_GREATER + isRefStruct = returnType.IsByRefLike; +#else + isRefStruct = returnType.Name.Contains("Span"); +#endif + + if (isRefStruct) + { + try + { + // Strip the implicit conversion and try compiling the inner expression + var inner = m.Arguments[0]; + var newBody = Expression.Convert(inner, typeof(object)); + var newLambda = Expression.Lambda>(newBody, expr.Parameters); + return newLambda.Compile(); + } + catch + { + // If recovery fails, ignore and throw original + } + } + } + + throw; + } } } } @@ -575,12 +614,27 @@ protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBind return GiveUp(node); } + private bool IsRefStruct(Type type) + { +#if NET6_0_OR_GREATER + return type.IsByRefLike; +#else + return type.Name.Contains("Span"); +#endif + } + protected override Expression VisitMethodCall(MethodCallExpression node) { if (_gaveUp) { return node; } + + if (IsRefStruct(node.Type)) + { + return GiveUp(node); + } + _currentChain.Elements.Add(new MethodCallExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitMethodCall(node); } @@ -650,6 +704,12 @@ protected override Expression VisitUnary(UnaryExpression node) { return node; } + + if (IsRefStruct(node.Type)) + { + return GiveUp(node); + } + _currentChain.Elements.Add(new UnaryExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitUnary(node); } diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs index 54f454bfe05..fa290903315 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs @@ -64,6 +64,6 @@ public void Can_compile_cached_expressions() ? q.Limit(request.Skip, take) : q.Limit(take); - var results = db.Select(q); + Assert.That(q.WhereExpression, Is.EqualTo("WHERE \"Id\" IN (@0,@1,@2)")); } } \ No newline at end of file From ce5d832f2720c8ac0b152f454d44402d5355c40a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 15:36:55 +0800 Subject: [PATCH 033/140] tidy --- .../ServiceStack.Common/ExpressionUtils.cs | 4 +- .../ServiceStack.Jobs/SqliteRequestLogger.cs | 8 +- .../ServiceStack.Server/DbRequestLogger.cs | 3 +- .../ExpressionUtilsTests.cs | 217 +++++++++--------- 4 files changed, 117 insertions(+), 115 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Common/ExpressionUtils.cs b/ServiceStack/src/ServiceStack.Common/ExpressionUtils.cs index dcf3bef5f07..eb4613c341f 100644 --- a/ServiceStack/src/ServiceStack.Common/ExpressionUtils.cs +++ b/ServiceStack/src/ServiceStack.Common/ExpressionUtils.cs @@ -64,7 +64,7 @@ public static string[] GetFieldNames(this Expression> expr) if (expr.Body is MemberExpression member) { if (member.Member.DeclaringType.IsAssignableFrom(typeof(T))) - return new[] { member.Member.Name }; + return [member.Member.Name]; var array = CachedExpressionCompiler.Evaluate(member); if (array is IEnumerable strEnum) @@ -94,7 +94,7 @@ public static string[] GetFieldNames(this Expression> expr) { member = unary.Operand as MemberExpression; if (member != null) - return new[] { member.Member.Name }; + return [member.Member.Name]; } throw new ArgumentException("Invalid Fields List Expression: " + expr); diff --git a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs index e1d91c5b45b..02b9ea5564e 100644 --- a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs @@ -126,6 +126,7 @@ public List QueryLogs(RequestLogs request) var take = request.Take ?? MaxLimit; var q = db.From(); + var Headers = q.DialectProvider.GetQuotedColumnName(nameof(RequestLog.Headers)); if (request.BeforeSecs.HasValue) q = q.Where(x => (now - x.DateTime) <= TimeSpan.FromSeconds(request.BeforeSecs.Value)); if (request.AfterSecs.HasValue) @@ -145,7 +146,7 @@ public List QueryLogs(RequestLogs request) if (!request.PathInfo.IsNullOrEmpty()) q = q.Where(x => x.PathInfo == request.PathInfo); if (!request.BearerToken.IsNullOrEmpty()) - q = q.Where("Headers LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); + q = q.Where(Headers + " LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); if (!request.Ids.IsEmpty()) q = q.Where(x => request.Ids.Contains(x.Id)); if (request.BeforeId.HasValue) @@ -597,11 +598,12 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont apiKey.SqlVerifyFragment(); using var db = OpenMonthDb(month); + var Headers = db.GetDialectProvider().GetQuotedColumnName(nameof(RequestLog.Headers)); var tableExists = db.TableExists(); var lastLogId = tableExists ? db.Single(db.From() - .And("Headers LIKE {0}", $"%Bearer {apiKey}%") + .And(Headers + " LIKE {0}", $"%Bearer {apiKey}%") .OrderByDescending(x => x.Id).Limit(1))?.Id : null; @@ -625,7 +627,7 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont batch = db.Select( db.From() .Where(x => x.Id > lastPk) - .And("Headers LIKE {0}", $"%Bearer {apiKey}%") + .And(Headers + " LIKE {0}", $"%Bearer {apiKey}%") .OrderBy(x => x.Id) .Limit(config.BatchSize)); diff --git a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs index 133723cf3c8..4165736f378 100644 --- a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs @@ -337,6 +337,7 @@ public List QueryLogs(RequestLogs request) var take = request.Take ?? MaxLimit; var q = db.From(); + var Headers = q.DialectProvider.GetQuotedColumnName(nameof(RequestLog.Headers)); if (request.BeforeSecs.HasValue) q = q.Where(x => (now - x.DateTime) <= TimeSpan.FromSeconds(request.BeforeSecs.Value)); if (request.AfterSecs.HasValue) @@ -356,7 +357,7 @@ public List QueryLogs(RequestLogs request) if (!request.PathInfo.IsNullOrEmpty()) q = q.Where(x => x.PathInfo == request.PathInfo); if (!request.BearerToken.IsNullOrEmpty()) - q = q.Where("Headers LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); + q = q.Where(Headers + " LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); if (!request.Ids.IsEmpty()) q = q.Where(x => request.Ids.Contains(x.Id)); if (request.BeforeId.HasValue) diff --git a/ServiceStack/tests/ServiceStack.Common.Tests/ExpressionUtilsTests.cs b/ServiceStack/tests/ServiceStack.Common.Tests/ExpressionUtilsTests.cs index 4a888d13fd3..d9ffbbfefb7 100644 --- a/ServiceStack/tests/ServiceStack.Common.Tests/ExpressionUtilsTests.cs +++ b/ServiceStack/tests/ServiceStack.Common.Tests/ExpressionUtilsTests.cs @@ -6,140 +6,139 @@ using ServiceStack.DataAnnotations; using ServiceStack.Text; -namespace ServiceStack.Common.Tests +namespace ServiceStack.Common.Tests; + +public class ExpressionUtilsTests { - public class ExpressionUtilsTests + abstract class AbstractBase { - abstract class AbstractBase - { - public string BaseMember { get; set; } - } + public string BaseMember { get; set; } + } - class Derived : AbstractBase - { - public string DerivedMember { get; set; } - } + class Derived : AbstractBase + { + public string DerivedMember { get; set; } + } - [Test] - public void Does_GetMemberName() - { - Assert.That(ExpressionUtils.GetMemberName((Poco x) => x.Name), - Is.EqualTo("Name")); + [Test] + public void Does_GetMemberName() + { + Assert.That(ExpressionUtils.GetMemberName((Poco x) => x.Name), + Is.EqualTo("Name")); - Assert.That(ExpressionUtils.GetMemberName((ModelWithFieldsOfNullableTypes x) => x.NId), - Is.EqualTo("NId")); - } + Assert.That(ExpressionUtils.GetMemberName((ModelWithFieldsOfNullableTypes x) => x.NId), + Is.EqualTo("NId")); + } - public Expression> GetAssignmentExpression(Expression> expr) - { - return expr; - } + public Expression> GetAssignmentExpression(Expression> expr) + { + return expr; + } - [Test] - public void Can_get_assigned_constants() + [Test] + public void Can_get_assigned_constants() + { + Assert.That(GetAssignmentExpression(() => new Poco { Name = "Foo" }).AssignedValues(), + Is.EquivalentTo(new Dictionary { + {"Name", "Foo"} + })); + } + + [Test] + public void Can_get_assigned_expressions() + { + 2.Times(i => { - Assert.That(GetAssignmentExpression(() => new Poco { Name = "Foo" }).AssignedValues(), + Assert.That(GetAssignmentExpression(() => new Poco { Name = i % 2 == 0 ? "Foo" : "Bar" }).AssignedValues(), Is.EquivalentTo(new Dictionary { - {"Name", "Foo"} + { "Name", i % 2 == 0 ? "Foo" : "Bar" } })); - } + }); + } - [Test] - public void Can_get_assigned_expressions() - { - 2.Times(i => - { - Assert.That(GetAssignmentExpression(() => new Poco { Name = i % 2 == 0 ? "Foo" : "Bar" }).AssignedValues(), - Is.EquivalentTo(new Dictionary { - { "Name", i % 2 == 0 ? "Foo" : "Bar" } - })); - }); - } - - [Test] - public void Can_get_fields_list_from_property_expression() - { - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => x.Name), - Is.EquivalentTo(new[] { "Name" })); + [Test] + public void Can_get_fields_list_from_property_expression() + { + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => x.Name), + Is.EquivalentTo(new[] { "Name" })); - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => x.Id), - Is.EquivalentTo(new[] { "Id" })); - } + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => x.Id), + Is.EquivalentTo(new[] { "Id" })); + } - [Test] - public void Can_get_fields_list_from_anon_object() - { - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new { x.Id, x.Name }), - Is.EquivalentTo(new[] { "Id", "Name" })); - } + [Test] + public void Can_get_fields_list_from_anon_object() + { + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new { x.Id, x.Name }), + Is.EquivalentTo(new[] { "Id", "Name" })); + } - [Test] - public void Can_get_fields_list_from_Typed_object() - { - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new Poco { Id = x.Id, Name = x.Name }), - Is.EquivalentTo(new[] { "Id", "Name" })); - } + [Test] + public void Can_get_fields_list_from_Typed_object() + { + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new Poco { Id = x.Id, Name = x.Name }), + Is.EquivalentTo(new[] { "Id", "Name" })); + } - [Test] - public void Can_get_fields_list_from_array() - { - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new[] { "Id", "Name" }), - Is.EquivalentTo(new[] { "Id", "Name" })); + [Test] + public void Can_get_fields_list_from_array() + { + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new[] { "Id", "Name" }), + Is.EquivalentTo(new[] { "Id", "Name" })); - var id = "Id"; + var id = "Id"; - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new[] { id, "Na" + "me" }), - Is.EquivalentTo(new[] { "Id", "Name" })); - } + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => new[] { id, "Na" + "me" }), + Is.EquivalentTo(new[] { "Id", "Name" })); + } - [Test] - public void Can_get_fields_list_from_list() - { - var list = new List { "Id", "Name" }; + [Test] + public void Can_get_fields_list_from_list() + { + var list = new List { "Id", "Name" }; - Assert.That(ExpressionUtils.GetFieldNames((Poco x) => list), - Is.EquivalentTo(new[] { "Id", "Name" })); - } + Assert.That(ExpressionUtils.GetFieldNames((Poco x) => list), + Is.EquivalentTo(new[] { "Id", "Name" })); + } - [Test] - public void Can_get_fields_from_abstract_base_class() - { - Assert.That(ExpressionUtils.GetFieldNames(p => p.BaseMember), - Is.EquivalentTo(new[] {"BaseMember"})); - Assert.That(ExpressionUtils.GetFieldNames(p => p.DerivedMember), - Is.EquivalentTo(new[] { "DerivedMember" })); - } + [Test] + public void Can_get_fields_from_abstract_base_class() + { + Assert.That(ExpressionUtils.GetFieldNames(p => p.BaseMember), + Is.EquivalentTo(new[] {"BaseMember"})); + Assert.That(ExpressionUtils.GetFieldNames(p => p.DerivedMember), + Is.EquivalentTo(new[] { "DerivedMember" })); + } - public class Question - { - public int Id { get; set; } - public string Text { get; set; } + public class Question + { + public int Id { get; set; } + public string Text { get; set; } - [CustomField("json")] - public List Answers { get; set; } - } + [CustomField("json")] + public List Answers { get; set; } + } - public class Answer - { - public int Id { get; set; } - public string Text { get; set; } - } + public class Answer + { + public int Id { get; set; } + public string Text { get; set; } + } - [Test] - public void Can_get_assigned_ComplexTypes() + [Test] + public void Can_get_assigned_ComplexTypes() + { + var assignedValues = GetAssignmentExpression(() => new Question { - var assignedValues = GetAssignmentExpression(() => new Question + Id = 1, + Answers = new List { - Id = 1, - Answers = new List - { - new Answer { Id = 1, Text = "Q1 Answer1" } - } - }).AssignedValues(); - - Assert.That(assignedValues.Count, Is.EqualTo(2)); - var assignedValue = (List)assignedValues["Answers"]; - Assert.That(assignedValue[0].Text, Is.EqualTo("Q1 Answer1")); - } + new Answer { Id = 1, Text = "Q1 Answer1" } + } + }).AssignedValues(); + + Assert.That(assignedValues.Count, Is.EqualTo(2)); + var assignedValue = (List)assignedValues["Answers"]; + Assert.That(assignedValue[0].Text, Is.EqualTo("Q1 Answer1")); } } \ No newline at end of file From 10a2ecf41bd0a33b6af8e415682e3f0bd269e2ef Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 19:01:55 +0800 Subject: [PATCH 034/140] Fix Expression tests to support .net10 breaking changes --- .../Expressions/SqlExpression.cs | 132 ++++++++- .../Expression/PrimaryExpressionsTest.cs | 21 +- .../ExpressionVisitorTests.cs | 8 +- .../CachedExpressionCompiler.cs | 264 +++++++++++++----- .../CachedExpressionTests.cs | 121 +++++--- 5 files changed, 409 insertions(+), 137 deletions(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs index c6075317a8b..a3973b9e379 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs @@ -2537,6 +2537,12 @@ protected virtual object VisitMethodCall(MethodCallExpression m) return ret; } + // In C# 14, array.Contains() can resolve to MemoryExtensions.Contains (extension method) + // which should be treated as a collection Contains (SQL IN) not string Contains (SQL LIKE) + // Check this FIRST before other Contains checks + if (IsSpanContainsOnArray(m)) + return VisitSpanContainsMethodCall(m); + if (IsStaticArrayMethod(m)) return VisitStaticArrayMethodCall(m); @@ -2795,9 +2801,40 @@ public IList GetAllFields() protected virtual bool IsStaticArrayMethod(MethodCallExpression m) { - return (m.Object == null - && m.Method.Name == "Contains" - && m.Arguments.Count == 2); + if (m.Method.Name != "Contains" || m.Arguments.Count != 2) + return false; + + // In C# 14, array.Contains() can resolve to different methods due to span conversions + // We need to check if this is a collection Contains (for SQL IN) vs string Contains (for SQL LIKE) + // Static array methods have m.Object == null + if (m.Object != null) + return false; + + // Check if the declaring type is a collection-related type (not string-related) + var declaringType = m.Method.DeclaringType; + if (declaringType == null) + return false; + + // Exclude string/span Contains methods that should use LIKE + if (declaringType == typeof(string) || + declaringType.Name == "MemoryExtensions" || + declaringType.FullName?.StartsWith("System.MemoryExtensions") == true) + return false; + + // Check if first argument is a collection type (not string) + if (m.Arguments.Count > 0) + { + var firstArgType = m.Arguments[0].Type; + if (firstArgType == typeof(string)) + return false; + + // Accept if it's an array or implements IEnumerable<> + if (firstArgType.IsArray || + firstArgType.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) + return true; + } + + return false; } protected virtual object VisitStaticArrayMethodCall(MethodCallExpression m) @@ -2821,11 +2858,70 @@ protected virtual object VisitStaticArrayMethodCall(MethodCallExpression m) private static bool IsEnumerableMethod(MethodCallExpression m) { - return m.Object != null - && m.Object.Type.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>)) - && m.Object.Type != typeof(string) - && m.Method.Name == "Contains" - && m.Arguments.Count == 1; + if (m.Method.Name != "Contains" || m.Arguments.Count != 1 || m.Object == null) + return false; + + var objectType = m.Object.Type; + + // Exclude string Contains (should use LIKE) + if (objectType == typeof(string)) + return false; + + // In C# 14, need to exclude span-based Contains methods + var declaringType = m.Method.DeclaringType; + if (declaringType != null && + (declaringType.Name == "MemoryExtensions" || + declaringType.FullName?.StartsWith("System.MemoryExtensions") == true)) + return false; + + // Accept collection Contains (should use IN) + return objectType.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>)); + } + + private static bool IsSpanContainsOnArray(MethodCallExpression m) + { + // In C# 14, array.Contains() can resolve to MemoryExtensions.Contains(ReadOnlySpan, T) + // This is a static extension method where: + // - m.Object is null (static method) + // - m.Arguments[0] is the collection/array (converted to span) + // - m.Arguments[1] is the value to search for + + if (m.Method.Name != "Contains") + return false; + + // Must be a static method (extension method) + if (m.Object != null) + return false; + + // Must have 2 or 3 arguments (2 for basic Contains, 3 for Contains with comparer) + if (m.Arguments.Count != 2 && m.Arguments.Count != 3) + return false; + + var declaringType = m.Method.DeclaringType; + if (declaringType == null) + return false; + + // Check if this is MemoryExtensions.Contains + var isMemoryExtensions = declaringType.Name == "MemoryExtensions" || + declaringType.FullName?.StartsWith("System.MemoryExtensions") == true; + + if (!isMemoryExtensions) + return false; + + // Check the second argument type to determine if this is string/char Contains or collection Contains + var secondArgType = m.Arguments[1].Type; + + // If the second argument is a string or ReadOnlySpan, this is string Contains (should use LIKE) + if (secondArgType == typeof(string) || secondArgType == typeof(char)) + return false; + + if (secondArgType.Name == "ReadOnlySpan`1" && secondArgType.GenericTypeArguments.Length > 0 && + secondArgType.GenericTypeArguments[0] == typeof(char)) + return false; + + // Otherwise, this is a collection Contains (should use IN) + // The second argument is the value being searched for (e.g., x.Id) + return true; } protected virtual object VisitEnumerableMethodCall(MethodCallExpression m) @@ -2842,6 +2938,26 @@ protected virtual object VisitEnumerableMethodCall(MethodCallExpression m) } } + protected virtual object VisitSpanContainsMethodCall(MethodCallExpression m) + { + // Handle MemoryExtensions.Contains(ReadOnlySpan, T) which is used in C# 14 + // for array.Contains() calls. This is a static extension method where: + // - m.Arguments[0] is the collection/array (converted to span) + // - m.Arguments[1] is the value to search for + switch (m.Method.Name) + { + case "Contains": + List args = this.VisitExpressionList(m.Arguments); + // args[0] is the collection, args[1] is the column/value to check + object quotedColName = args[1]; + Expression collectionExpr = m.Arguments[0]; + return ToInPartialString(collectionExpr, quotedColName); + + default: + throw new NotSupportedException(); + } + } + private object ToInPartialString(Expression memberExpr, object quotedColName) { var result = EvaluateExpression(memberExpr); diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/Expression/PrimaryExpressionsTest.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/Expression/PrimaryExpressionsTest.cs index 9919317b7ec..f3ae2ca3874 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/Expression/PrimaryExpressionsTest.cs +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/Expression/PrimaryExpressionsTest.cs @@ -34,21 +34,11 @@ public TestClass(T value) } } - private struct TestStruct + private struct TestStruct(T value) { - public T Property => field; - - public readonly T field; - - public T Method() - { - return field; - } - - public TestStruct(T value) - { - field = value; - } + public T Property => this.TheField; + public readonly T TheField = value; + public T Method() => this.TheField; } [Test] @@ -227,7 +217,7 @@ public void Can_select_struct_int_field_expression() using (var db = OpenDbConnection()) { - var actual = db.Select(q => q.IntColumn == tmp.field); + var actual = db.Select(q => q.IntColumn == tmp.TheField); Assert.IsNotNull(actual); Assert.AreEqual(1, actual.Count); @@ -238,6 +228,7 @@ public void Can_select_struct_int_field_expression() [Test] public void Can_select_struct_int_property_expression() { + OrmLiteUtils.PrintSql(); var tmp = new TestStruct(12); var expected = new TestType diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ExpressionVisitorTests.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ExpressionVisitorTests.cs index bb3e74d0c99..8e03d38b212 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ExpressionVisitorTests.cs +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ExpressionVisitorTests.cs @@ -214,9 +214,15 @@ public void Can_Select_using_constant_Yoda_condition() [Test] public void Can_Select_using_int_array_constructed_inside_Contains() { - var q = Db.From().Where(x => new int?[] { 10, 30 }.Contains(x.NullableIntCol)); + var q = Db.From().Where(x => new int[] { 1, 3 }.Contains(x.Id)); + Assert.That(q.WhereExpression, Does.Contain("IN (@0,@1)")); var target = Db.Select(q); CollectionAssert.AreEquivalent(new[] { 1, 3 }, target.Select(t => t.Id).ToArray()); + + q = Db.From().Where(x => new int?[] { 10, 30 }.Contains(x.NullableIntCol)); + Assert.That(q.WhereExpression, Does.Contain("IN (@0,@1)")); + target = Db.Select(q); + CollectionAssert.AreEquivalent(new[] { 1, 3 }, target.Select(t => t.Id).ToArray()); } [Test] diff --git a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs index 0413a80eb05..e094587eac5 100644 --- a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs +++ b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using System.Security.Cryptography; +using System.Text; namespace ServiceStack { @@ -16,11 +18,13 @@ namespace ServiceStack // The unit tests for the ExpressionUtil.* types are in the System.Web.Mvc.Test project. public static class CachedExpressionCompiler { - private static readonly ParameterExpression _unusedParameterExpr = Expression.Parameter(typeof(object), "_unused"); + private static readonly ParameterExpression _unusedParameterExpr = + Expression.Parameter(typeof(object), "_unused"); // Implements caching around LambdaExpression.Compile() so that equivalent expression trees only have to be // compiled once. - public static Func Compile(this Expression> lambdaExpression) + public static Func Compile( + this Expression> lambdaExpression) { if (lambdaExpression == null) throw new ArgumentNullException(nameof(lambdaExpression)); @@ -40,7 +44,8 @@ public static object Evaluate(Expression arg) private static Func Wrap(Expression arg) { - Expression> lambdaExpr = Expression.Lambda>(Expression.Convert(arg, typeof(object)), _unusedParameterExpr); + Expression> lambdaExpr = + Expression.Lambda>(Expression.Convert(arg, typeof(object)), _unusedParameterExpr); return ExpressionUtil.CachedExpressionCompiler.Process(lambdaExpr); } } @@ -48,10 +53,130 @@ private static Func Wrap(Expression arg) namespace ServiceStack.ExpressionUtil { + public static class ExpressionCacheKey + { + public static bool TryGetKey(LambdaExpression expr, out string key) + { + try + { + if (!CanCache(expr)) + { + key = null; + return false; + } + + var sb = new StringBuilder(); + new CanonicalExpressionPrinter(sb).Visit(expr); + key = sb.ToString(); + return true; + } + catch + { + key = null; + return false; + } + } + + public static bool CanCache(Expression expr) + => CacheableExpressionVisitor.IsCacheable(expr); + + private static string Hash(string input) + { + using var sha = SHA256.Create(); + return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(input))); + } + + private sealed class CanonicalExpressionPrinter(StringBuilder sb) : ExpressionVisitor + { + protected override Expression VisitConstant(ConstantExpression node) + { + // Remove closure object identity + if (node.Type.Name.Contains("DisplayClass")) + { + sb.Append($"CONST(closure:{node.Type.FullName})"); + return node; + } + + // Normal constants + sb.Append($"CONST({node.Value})"); + return node; + } + + protected override Expression VisitMember(MemberExpression node) + { + if (node.Expression is ConstantExpression c && + c.Type.Name.Contains("DisplayClass")) + { + // Include the closure declaring type + member type to handle array/ref struct differences + sb.Append("CLOSURE_MEMBER("); + sb.Append(c.Type.FullName); + sb.Append("."); + sb.Append(node.Member.Name); + sb.Append(":"); + sb.Append(node.Type.FullName); + sb.Append(")"); + return node; + } + sb.Append($"MEMBER({node.Member.DeclaringType}.{node.Member.Name}:{node.Type.FullName})"); + return base.VisitMember(node); + } + + protected override Expression VisitLambda(Expression node) + { + sb.Append("LAMBDA("); + foreach (var p in node.Parameters) + sb.Append(p.Type.FullName + ";"); + sb.Append(")=>"); + return base.VisitLambda(node); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + sb.Append($"BIN({node.NodeType})"); + return base.VisitBinary(node); + } + + protected override Expression VisitParameter(ParameterExpression node) + { + sb.Append($"PARAM({node.Type.FullName})"); + return node; + } + } + + private sealed class CacheableExpressionVisitor : ExpressionVisitor + { + public bool Cacheable { get; private set; } = true; + + public static bool IsCacheable(Expression expr) + { + var v = new CacheableExpressionVisitor(); + v.Visit(expr); + return v.Cacheable; + } + + protected override Expression VisitInvocation(InvocationExpression node) + { + // Cannot reliably cache invocation expressions + Cacheable = false; + return node; + } + + protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) + { + Cacheable = false; + return node; + } + + protected override Expression VisitTry(TryExpression node) + { + // Safe but unusual — allow it + return base.VisitTry(node); + } + } + } // BinaryExpression fingerprint class // Useful for things like array[index] - internal sealed class BinaryExpressionFingerprint : ExpressionFingerprint { public BinaryExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method) @@ -101,15 +226,13 @@ public static Func Process(Expression { private static Func _identityFunc; + private static readonly ConcurrentDictionary> _simpleMemberAccessDict = new(); + private static readonly ConcurrentDictionary> _constMemberAccessDict = new(); - private static readonly ConcurrentDictionary> _simpleMemberAccessDict = - new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> + _fingerprintedCache = new(); - private static readonly ConcurrentDictionary> _constMemberAccessDict = - new ConcurrentDictionary>(); - - private static readonly ConcurrentDictionary> _fingerprintedCache = - new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _slowCompileCache = new(); public static Func Compile(Expression> expr) { @@ -117,7 +240,7 @@ public static Func Compile(Expression> expr) ?? CompileFromConstLookup(expr) ?? CompileFromMemberAccess(expr) ?? CompileFromFingerprint(expr) - ?? CompileSlow(expr); + ?? CachedCompileSlow(expr); } private static Func CompileFromConstLookup(Expression> expr) @@ -153,7 +276,8 @@ private static Func CompileFromIdentityFunc(Expression CompileFromFingerprint(Expression> expr) { - ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out var capturedConstants); + ExpressionFingerprintChain fingerprint = + FingerprintingExpressionVisitor.GetFingerprintChain(expr, out var capturedConstants); if (fingerprint != null) { @@ -197,7 +321,8 @@ private static Func CompileFromMemberAccess(Expression>(newMemberAccessExpr, constParamExpr); + var newLambdaExpr = + Expression.Lambda>(newMemberAccessExpr, constParamExpr); return newLambdaExpr.Compile(); }); @@ -209,21 +334,28 @@ private static Func CompileFromMemberAccess(Expression CachedCompileSlow(Expression> expr) + { + if (ExpressionCacheKey.TryGetKey(expr, out var key)) + { + return _slowCompileCache.GetOrAdd(key, _ => CompileSlow(expr)); + } + return CompileSlow(expr); + } + private static Func CompileSlow(Expression> expr) { +#if DEBUG + Console.WriteLine("SlowCompile"); +#endif + // fallback compilation system - just compile the expression directly try { - return expr.Compile(); - } - catch (Exception) - { - // Try to recover from invalid ref struct boxing (e.g. Span implicit conversion) - if (expr.Body is UnaryExpression u - && u.NodeType == ExpressionType.Convert + // Try to handle invalid ref struct boxing (e.g. Span implicit conversion) + if (expr.Body is UnaryExpression { NodeType: ExpressionType.Convert } u && u.Type == typeof(object) - && u.Operand is MethodCallExpression m - && m.Method.Name == "op_Implicit") + && u.Operand is MethodCallExpression { Method.Name: "op_Implicit" } m) { var returnType = m.Method.ReturnType; bool isRefStruct = false; @@ -235,7 +367,7 @@ private static Func CompileSlow(Expression> expr) if (isRefStruct) { - try + try { // Strip the implicit conversion and try compiling the inner expression var inner = m.Arguments[0]; @@ -250,20 +382,21 @@ private static Func CompileSlow(Expression> expr) } } + return expr.Compile(); + } + catch (Exception) + { throw; } } } } - internal sealed class ConditionalExpressionFingerprint : ExpressionFingerprint + internal sealed class ConditionalExpressionFingerprint(ExpressionType nodeType, Type type) + : ExpressionFingerprint(nodeType, type) { - public ConditionalExpressionFingerprint(ExpressionType nodeType, Type type) - : base(nodeType, type) - { - // There are no properties on ConditionalExpression that are worth including in - // the fingerprint. - } + // There are no properties on ConditionalExpression that are worth including in + // the fingerprint. public override bool Equals(object obj) { @@ -278,14 +411,11 @@ public override int GetHashCode() } } - internal sealed class ConstantExpressionFingerprint : ExpressionFingerprint + internal sealed class ConstantExpressionFingerprint(ExpressionType nodeType, Type type) + : ExpressionFingerprint(nodeType, type) { - public ConstantExpressionFingerprint(ExpressionType nodeType, Type type) - : base(nodeType, type) - { - // There are no properties on ConstantExpression that are worth including in - // the fingerprint. - } + // There are no properties on ConstantExpression that are worth including in + // the fingerprint. public override bool Equals(object obj) { @@ -300,14 +430,11 @@ public override int GetHashCode() } } - internal sealed class DefaultExpressionFingerprint : ExpressionFingerprint + internal sealed class DefaultExpressionFingerprint(ExpressionType nodeType, Type type) + : ExpressionFingerprint(nodeType, type) { - public DefaultExpressionFingerprint(ExpressionType nodeType, Type type) - : base(nodeType, type) - { - // There are no properties on DefaultExpression that are worth including in - // the fingerprint. - } + // There are no properties on DefaultExpression that are worth including in + // the fingerprint. public override bool Equals(object obj) { @@ -322,19 +449,13 @@ public override int GetHashCode() } } - internal abstract class ExpressionFingerprint + internal abstract class ExpressionFingerprint(ExpressionType nodeType, Type type) { - protected ExpressionFingerprint(ExpressionType nodeType, Type type) - { - NodeType = nodeType; - Type = type; - } - // the type of expression node, e.g. OP_ADD, MEMBER_ACCESS, etc. - public ExpressionType NodeType { get; private set; } + public ExpressionType NodeType { get; private set; } = nodeType; // the CLR type resulting from this expression, e.g. int, string, etc. - public Type Type { get; private set; } + public Type Type { get; private set; } = type; internal virtual void AddToHashCodeCombiner(HashCodeCombiner combiner) { @@ -364,31 +485,22 @@ public override int GetHashCode() internal sealed class ExpressionFingerprintChain : IEquatable { - public readonly List Elements = new List(); - + public readonly List Elements = new(); public bool Equals(ExpressionFingerprintChain other) { // Two chains are considered equal if two elements appearing in the same index in // each chain are equal (value equality, not referential equality). - if (other == null) - { return false; - } if (this.Elements.Count != other.Elements.Count) - { return false; - } for (int i = 0; i < this.Elements.Count; i++) { if (!Equals(this.Elements[i], other.Elements[i])) - { return false; - } } - return true; } @@ -401,7 +513,6 @@ public override int GetHashCode() { HashCodeCombiner combiner = new HashCodeCombiner(); Elements.ForEach(combiner.AddFingerprint); - return combiner.CombinedHash; } } @@ -427,7 +538,8 @@ private T GiveUp(T node) // Returns the fingerprint chain + captured constants list for this expression, or null // if the expression couldn't be fingerprinted. - public static ExpressionFingerprintChain GetFingerprintChain(Expression expr, out List capturedConstants) + public static ExpressionFingerprintChain GetFingerprintChain(Expression expr, + out List capturedConstants) { FingerprintingExpressionVisitor visitor = new FingerprintingExpressionVisitor(); visitor.Visit(expr); @@ -463,6 +575,7 @@ protected override Expression VisitBinary(BinaryExpression node) { return node; } + _currentChain.Elements.Add(new BinaryExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitBinary(node); } @@ -483,6 +596,7 @@ protected override Expression VisitConditional(ConditionalExpression node) { return node; } + _currentChain.Elements.Add(new ConditionalExpressionFingerprint(node.NodeType, node.Type)); return base.VisitConditional(node); } @@ -510,6 +624,7 @@ protected override Expression VisitDefault(DefaultExpression node) { return node; } + _currentChain.Elements.Add(new DefaultExpressionFingerprint(node.NodeType, node.Type)); return base.VisitDefault(node); } @@ -540,6 +655,7 @@ protected override Expression VisitIndex(IndexExpression node) { return node; } + _currentChain.Elements.Add(new IndexExpressionFingerprint(node.NodeType, node.Type, node.Indexer)); return base.VisitIndex(node); } @@ -565,6 +681,7 @@ protected override Expression VisitLambda(Expression node) { return node; } + _currentChain.Elements.Add(new LambdaExpressionFingerprint(node.NodeType, node.Type)); return base.VisitLambda(node); } @@ -585,6 +702,7 @@ protected override Expression VisitMember(MemberExpression node) { return node; } + _currentChain.Elements.Add(new MemberExpressionFingerprint(node.NodeType, node.Type, node.Member)); return base.VisitMember(node); } @@ -694,6 +812,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression node) { return node; } + _currentChain.Elements.Add(new TypeBinaryExpressionFingerprint(node.NodeType, node.Type, node.TypeOperand)); return base.VisitTypeBinary(node); } @@ -750,6 +869,7 @@ public void AddEnumerable(IEnumerable e) AddObject(o); count++; } + AddInt32(count); } } @@ -770,7 +890,9 @@ public void AddObject(object o) internal sealed class HoistingExpressionVisitor : ExpressionVisitor { - private static readonly ParameterExpression _hoistedConstantsParamExpr = Expression.Parameter(typeof(List), "hoistedConstants"); + private static readonly ParameterExpression _hoistedConstantsParamExpr = + Expression.Parameter(typeof(List), "hoistedConstants"); + private int _numConstantsProcessed; // factory will create instance @@ -784,14 +906,18 @@ public static Expression> Hoist(Expression> e var visitor = new HoistingExpressionVisitor(); var rewrittenBodyExpr = visitor.Visit(expr.Body); - var rewrittenLambdaExpr = Expression.Lambda>(rewrittenBodyExpr, expr.Parameters[0], _hoistedConstantsParamExpr); + var rewrittenLambdaExpr = + Expression.Lambda>(rewrittenBodyExpr, expr.Parameters[0], + _hoistedConstantsParamExpr); return rewrittenLambdaExpr; } protected override Expression VisitConstant(ConstantExpression node) { // rewrite the constant expression as (TConst)hoistedConstants[i]; - return Expression.Convert(Expression.Property(_hoistedConstantsParamExpr, "Item", Expression.Constant(_numConstantsProcessed++)), node.Type); + return Expression.Convert( + Expression.Property(_hoistedConstantsParamExpr, "Item", Expression.Constant(_numConstantsProcessed++)), + node.Type); } } diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs index fa290903315..64eb7bb3249 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs @@ -1,69 +1,102 @@ using System; +using System.Linq; using NUnit.Framework; using ServiceStack.Admin; +using ServiceStack.DataAnnotations; using ServiceStack.OrmLite; namespace ServiceStack.WebHost.Endpoints.Tests; public class CachedExpressionTests { + void AssertExpr(Action> fn, string expected) + { + var q = new OrmLite.Sqlite.SqliteExpression(SqliteDialect.Provider); + Console.WriteLine($"Running: {expected}"); + fn(q); + Assert.That(q.WhereExpression, Is.EqualTo(expected)); + } + + [Test] + public void Does_reuse_cached_expressions() + { + Can_compile_cached_expressions(); + Can_compile_cached_expressions(); + } + [Test] public void Can_compile_cached_expressions() { var dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider); using var db = dbFactory.Open(); - db.CreateTable(); var now = DateTime.UtcNow; var take = 100; var request = new RequestLogs { + BeforeSecs = 1, + AfterSecs = 1, + OperationName = nameof(RequestLogs), + IpAddress = "127.0.0.1", + ForwardedFor = "127.0.0.1", + UserAuthId = "1", + SessionId = "1", + Referer = "https://example.org", + PathInfo = "/requestlogs", + BeforeId = 1, + AfterId = 1, + DurationLongerThan = TimeSpan.FromSeconds(1), + DurationLessThan = TimeSpan.FromSeconds(1), Ids = [1,2,3] }; - var q = db.From(); - if (request.BeforeSecs.HasValue) - q = q.Where(x => (now - x.DateTime) <= TimeSpan.FromSeconds(request.BeforeSecs.Value)); - if (request.AfterSecs.HasValue) - q = q.Where(x => (now - x.DateTime) > TimeSpan.FromSeconds(request.AfterSecs.Value)); - if (!request.OperationName.IsNullOrEmpty()) - q = q.Where(x => x.OperationName == request.OperationName); - if (!request.IpAddress.IsNullOrEmpty()) - q = q.Where(x => x.IpAddress == request.IpAddress); - if (!request.ForwardedFor.IsNullOrEmpty()) - q = q.Where(x => x.ForwardedFor == request.ForwardedFor); - if (!request.UserAuthId.IsNullOrEmpty()) - q = q.Where(x => x.UserAuthId == request.UserAuthId); - if (!request.SessionId.IsNullOrEmpty()) - q = q.Where(x => x.SessionId == request.SessionId); - if (!request.Referer.IsNullOrEmpty()) - q = q.Where(x => x.Referer == request.Referer); - if (!request.PathInfo.IsNullOrEmpty()) - q = q.Where(x => x.PathInfo == request.PathInfo); - if (!request.BearerToken.IsNullOrEmpty()) - q = q.Where("Headers LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"); - if (!request.Ids.IsEmpty()) - q = q.Where(x => request.Ids.Contains(x.Id)); - if (request.BeforeId.HasValue) - q = q.Where(x => x.Id <= request.BeforeId); - if (request.AfterId.HasValue) - q = q.Where(x => x.Id > request.AfterId); - if (request.WithErrors.HasValue) - q = request.WithErrors.Value - ? q.Where(x => x.Error != null || x.StatusCode >= 400) - : q.Where(x => x.Error == null); - if (request.DurationLongerThan.HasValue) - q = q.Where(x => x.RequestDuration > request.DurationLongerThan.Value); - if (request.DurationLessThan.HasValue) - q = q.Where(x => x.RequestDuration < request.DurationLessThan.Value); - q = string.IsNullOrEmpty(request.OrderBy) - ? q.OrderByDescending(x => x.Id) - : q.OrderBy(request.OrderBy); - q = request.Skip > 0 - ? q.Limit(request.Skip, take) - : q.Limit(take); - - Assert.That(q.WhereExpression, Is.EqualTo("WHERE \"Id\" IN (@0,@1,@2)")); + AssertExpr(q => + q.Where(x => (now - x.DateTime) <= TimeSpan.FromSeconds(request.BeforeSecs.Value)), + "WHERE ((@0 - \"DateTime\") <= @1)"); + AssertExpr(q => + q.Where(x => (now - x.DateTime) > TimeSpan.FromSeconds(request.AfterSecs.Value)), + "WHERE ((@0 - \"DateTime\") > @1)"); + AssertExpr(q => + q.Where(x => x.OperationName == request.OperationName), + "WHERE (\"OperationName\" = @0)"); + AssertExpr(q => + q.Where(x => x.IpAddress == request.IpAddress), + "WHERE (\"IpAddress\" = @0)"); + AssertExpr(q => + q.Where(x => x.ForwardedFor == request.ForwardedFor), + "WHERE (\"ForwardedFor\" = @0)"); + AssertExpr(q => + q.Where(x => x.UserAuthId == request.ForwardedFor), + "WHERE (\"UserAuthId\" = @0)"); + AssertExpr(q => + q.Where(x => x.SessionId == request.SessionId), + "WHERE (\"SessionId\" = @0)"); + AssertExpr(q => + q.Where(x => x.Referer == request.Referer), + "WHERE (\"Referer\" = @0)"); + AssertExpr(q => + q.Where(x => x.PathInfo == request.PathInfo), + "WHERE (\"PathInfo\" = @0)"); + AssertExpr(q => + q.Where(q.Column(x => x.Headers) + " LIKE {0}", $"%Bearer {request.BearerToken.SqlVerifyFragment()}%"), + "WHERE \"Headers\" LIKE @0"); + AssertExpr(q => q.Where(x => request.Ids.Contains(x.Id)), + "WHERE \"Id\" IN (@0,@1,@2)"); + AssertExpr(q => + q.Where(x => x.Id <= request.BeforeId), + "WHERE (\"Id\" <= @0)"); + AssertExpr(q => + q.Where(x => x.Id > request.AfterId), + "WHERE (\"Id\" > @0)"); + AssertExpr(q => + q.Where(x => x.Error != null || x.StatusCode >= 400), + "WHERE ((\"Error\" is not null) OR (\"StatusCode\" >= @0))"); + AssertExpr(q => + q.Where(x => x.RequestDuration > request.DurationLongerThan.Value), + "WHERE (\"RequestDuration\" > @0)"); + AssertExpr(q => + q.Where(x => x.RequestDuration < request.DurationLessThan.Value), + "WHERE (\"RequestDuration\" < @0)"); } } \ No newline at end of file From a40d88036c4a2386a9364b18246323c48b0fead8 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 19:02:00 +0800 Subject: [PATCH 035/140] Delete NetCoreTestsRunner.cs --- .../NetCoreTestsRunner.cs | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/NetCoreTestsRunner.cs diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/NetCoreTestsRunner.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/NetCoreTestsRunner.cs deleted file mode 100644 index 461a2132570..00000000000 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.PostgreSQL.Tests/NetCoreTestsRunner.cs +++ /dev/null @@ -1,48 +0,0 @@ -//NUnitLite isn't recognized in VS2017 - shouldn't need NUnitLite with NUnit 3.5+ https://github.com/nunit/dotnet-test-nunit -#if NUNITLITE -using NUnitLite; -using NUnit.Common; -using System.Reflection; -using ServiceStack; -using ServiceStack.Text; -using System; -using System.Globalization; -using System.Threading; -using ServiceStack.OrmLite.Tests; - -namespace ServiceStack.OrmLite.PostgreSQL.Tests -{ - public class NetCoreTestsRunner - { - /// - /// The main program executes the tests. Output may be routed to - /// various locations, depending on the arguments passed. - /// - /// Run with --help for a full list of arguments supported - /// - public static int Main(string[] args) - { - var licenseKey = Environment.GetEnvironmentVariable("SERVICESTACK_LICENSE"); - if (licenseKey.IsNullOrEmpty()) - throw new ArgumentNullException("SERVICESTACK_LICENSE", "Add Environment variable for SERVICESTACK_LICENSE"); - - Licensing.RegisterLicense(licenseKey); - //"ActivatedLicenseFeatures: ".Print(LicenseUtils.ActivatedLicenseFeatures()); - - var postgreSqlDb = Environment.GetEnvironmentVariable("POSTGRESQL_DB"); - - if (!String.IsNullOrEmpty(postgreSqlDb)) - { - TestConfig.PostgreSqlDb = postgreSqlDb; - } - - CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); - JsConfig.InitStatics(); - - //JsonServiceClient client = new JsonServiceClient(); - var writer = new ExtendedTextWrapper(Console.Out); - return new AutoRun(((IReflectableType)typeof(NetCoreTestsRunner)).GetTypeInfo().Assembly).Execute(args, writer, Console.In); - } - } -} -#endif \ No newline at end of file From 9a114b7fa0a4c1d266b2f250614b6863d7ea754c Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:06:16 +0800 Subject: [PATCH 036/140] Disable in DEBUG --- ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs index dc9d7066aaf..0cb58f46f5b 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs @@ -490,8 +490,10 @@ public static LicenseFeature ActivatedLicenseFeatures() public static void ApprovedUsage(int allowedUsage, int actualUsage, string message) { +#if !DEBUG if (actualUsage > allowedUsage) throw new LicenseException(message.Fmt(allowedUsage)).Trace(); +#endif } // Only used for testing license validation From e7f84ca0efc70dd7ca8885ea4bd5e136469773cf Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:08:38 +0800 Subject: [PATCH 037/140] Workaround .net10 cache expression issues --- .../CachedExpressionCompiler.cs | 129 ++++++++++++++---- .../src/ServiceStack.Common/TypeExtensions.cs | 9 ++ .../CachedExpressionTests.cs | 24 +++- 3 files changed, 136 insertions(+), 26 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs index e094587eac5..25beffd33f6 100644 --- a/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs +++ b/ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs @@ -78,7 +78,12 @@ public static bool TryGetKey(LambdaExpression expr, out string key) } public static bool CanCache(Expression expr) - => CacheableExpressionVisitor.IsCacheable(expr); + { + if (ClosureSafety.HasMutableClosure(expr)) + return false; + + return CacheableExpressionVisitor.IsCacheable(expr); + } private static string Hash(string input) { @@ -173,6 +178,100 @@ protected override Expression VisitTry(TryExpression node) return base.VisitTry(node); } } + + public static class ClosureSafety + { + public static bool HasMutableClosure(Expression expr) + { + var detector = new MutableClosureDetector(); + detector.Visit(expr); + return detector.Result; + } + + private sealed class MutableClosureDetector : ExpressionVisitor + { + public bool Result { get; private set; } + + public override Expression Visit(Expression node) + { + if (Result || node is null) + return node; + + return base.Visit(node); + } + + protected override Expression VisitMember(MemberExpression node) + { + if (Result) + return node; + + // Identify DisplayClass closure (C# compiler generated) + if (node.Expression is ConstantExpression c && + c.Type.IsNestedPrivate && + c.Type.Name.Contains("DisplayClass")) + { + Type capturedType = node.Type; + + if (IsMutableType(capturedType)) + Result = true; + } + + return base.VisitMember(node); + } + + private static bool IsMutableType(Type type) + { + // Arrays are always mutable + if (type.IsArray) + return true; + + // ref structs and Span are stack-only, must never be cached + if (type.IsRefStruct()) + return true; + + // Classes are mutable unless proven otherwise + if (!type.IsValueType) + return true; + + // Enums are immutable + if (type.IsEnum) + return false; + + // Primitive value types are immutable + if (type.IsPrimitive) + return false; + + // Decimal, DateTime, Guid are immutable structs + if (type == typeof(decimal) || + type == typeof(DateTime) || + type == typeof(Guid) || + type == typeof(TimeSpan)) + return false; + + // Nullable → check underlying type + if (Nullable.GetUnderlyingType(type) is Type underlying) + return IsMutableType(underlying); + + // Structs are mutable unless all fields are readonly, and those fields are immutable + return StructIsMutable(type); + } + + private static bool StructIsMutable(Type type) + { + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (!field.IsInitOnly) + return true; // mutable field + + if (IsMutableType(field.FieldType)) + return true; // immutable wrapper around mutable type + } + + return false; // readonly struct of immutable fields + } + } + } + } // BinaryExpression fingerprint class @@ -358,14 +457,7 @@ private static Func CompileSlow(Expression> expr) && u.Operand is MethodCallExpression { Method.Name: "op_Implicit" } m) { var returnType = m.Method.ReturnType; - bool isRefStruct = false; -#if NET6_0_OR_GREATER - isRefStruct = returnType.IsByRefLike; -#else - isRefStruct = returnType.Name.Contains("Span"); -#endif - - if (isRefStruct) + if (returnType.IsRefStruct()) { try { @@ -732,15 +824,6 @@ protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBind return GiveUp(node); } - private bool IsRefStruct(Type type) - { -#if NET6_0_OR_GREATER - return type.IsByRefLike; -#else - return type.Name.Contains("Span"); -#endif - } - protected override Expression VisitMethodCall(MethodCallExpression node) { if (_gaveUp) @@ -748,7 +831,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return node; } - if (IsRefStruct(node.Type)) + if (node.Type.IsRefStruct()) { return GiveUp(node); } @@ -824,7 +907,7 @@ protected override Expression VisitUnary(UnaryExpression node) return node; } - if (IsRefStruct(node.Type)) + if (node.Type.IsRefStruct()) { return GiveUp(node); } @@ -837,11 +920,7 @@ protected override Expression VisitUnary(UnaryExpression node) internal class HashCodeCombiner { private long _combinedHash64 = 0x1505L; - - public int CombinedHash - { - get { return _combinedHash64.GetHashCode(); } - } + public int CombinedHash => _combinedHash64.GetHashCode(); public void AddFingerprint(ExpressionFingerprint fingerprint) { diff --git a/ServiceStack/src/ServiceStack.Common/TypeExtensions.cs b/ServiceStack/src/ServiceStack.Common/TypeExtensions.cs index 6f12465ac7c..c72b6c79bcc 100644 --- a/ServiceStack/src/ServiceStack.Common/TypeExtensions.cs +++ b/ServiceStack/src/ServiceStack.Common/TypeExtensions.cs @@ -432,4 +432,13 @@ public static LambdaExpression CreatePropertyAccessorExpression(Type type, Prope paramInstance); return lambda; } + + public static bool IsRefStruct(this Type type) + { +#if NET6_0_OR_GREATER + return type.IsByRefLike; +#else + return type.Name.Contains("Span"); +#endif + } } diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs index 64eb7bb3249..238b577a255 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CachedExpressionTests.cs @@ -9,12 +9,16 @@ namespace ServiceStack.WebHost.Endpoints.Tests; public class CachedExpressionTests { - void AssertExpr(Action> fn, string expected) + void AssertExpr(Action> fn, string expected, object[] paramValues = null) { var q = new OrmLite.Sqlite.SqliteExpression(SqliteDialect.Provider); Console.WriteLine($"Running: {expected}"); fn(q); Assert.That(q.WhereExpression, Is.EqualTo(expected)); + if (paramValues != null) + { + Assert.That(q.Params.Select(x => x.Value), Is.EqualTo(paramValues)); + } } [Test] @@ -23,6 +27,24 @@ public void Does_reuse_cached_expressions() Can_compile_cached_expressions(); Can_compile_cached_expressions(); } + + [Test] + public void Does_not_cache_array_expressions() + { + var request = new RequestLogs + { + Ids = [1,2,3] + }; + AssertExpr(q => q.Where(x => request.Ids.Contains(x.Id)), + "WHERE \"Id\" IN (@0,@1,@2)", [1,2,3]); + + request = new RequestLogs + { + Ids = [4,5,6] + }; + AssertExpr(q => q.Where(x => request.Ids.Contains(x.Id)), + "WHERE \"Id\" IN (@0,@1,@2)", [4,5,6]); + } [Test] public void Can_compile_cached_expressions() From 63fa34677c2723a9defed94f935177bccc645b4d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:10:54 +0800 Subject: [PATCH 038/140] Fix DbJobs transaction issues --- .../src/ServiceStack.Server/DbJobs.cs | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Server/DbJobs.cs b/ServiceStack/src/ServiceStack.Server/DbJobs.cs index f95bed2f06c..4d7bd6b366c 100644 --- a/ServiceStack/src/ServiceStack.Server/DbJobs.cs +++ b/ServiceStack/src/ServiceStack.Server/DbJobs.cs @@ -790,29 +790,35 @@ public void ArchiveJob(BackgroundJob job) var now = DateTime.UtcNow; var requestId = Guid.NewGuid().ToString("N"); var completedJob = job.PopulateJob(new CompletedJob()); - using var db = feature.OpenDb(); - using var trans = db.OpenTransaction(); - - using var dbMonth = feature.OpenMonthDb(job.CreatedDate); - try - { - dbMonth.Insert(completedJob); - } - catch (Exception e) + + // Archive to monthly database (separate connection, no transaction needed) + using (var dbMonth = feature.OpenMonthDb(job.CreatedDate)) { - var existingJob = dbMonth.SingleById(completedJob.Id); - if (existingJob != null) + try { - log.LogWarning("Existing CompletedJob {Id} already exists, updating instead", completedJob.Id); - dbMonth.Update(completedJob); + dbMonth.Insert(completedJob); } - else + catch (Exception e) { - log.LogError(e, "Failed to Insert CompletedJob {Id}: {Message}", completedJob.Id, e.Message); + var existingJob = dbMonth.SingleById(completedJob.Id); + if (existingJob != null) + { + log.LogWarning("Existing CompletedJob {Id} already exists, updating instead", completedJob.Id); + dbMonth.Update(completedJob); + } + else + { + log.LogError(e, "Failed to Insert CompletedJob {Id}: {Message}", completedJob.Id, e.Message); + } } } + + // Update main database with transaction + using var db = feature.OpenDb(); + using var trans = db.OpenTransaction(); + db.DeleteById(job.Id); - + // Execute any jobs depending on this job db.UpdateOnly(() => new BackgroundJob { RequestId = requestId, @@ -821,10 +827,11 @@ public void ArchiveJob(BackgroundJob job) State = BackgroundJobState.Queued, ParentId = job.Id, }, where:x => x.CompletedDate == null && x.RequestId == null && x.DependsOn == job.Id); - - trans.Commit(); - + var dispatchJobs = db.Select(x => x.RequestId == requestId); + + trans.Commit(); + if (dispatchJobs.Count > 0) { log.LogInformation("JOBS Queued {Count} Jobs dependent on {JobId}", dispatchJobs.Count, job.Id); From 6c089723ac29292dfb62a717ce0450555c232c48 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:11:22 +0800 Subject: [PATCH 039/140] Update llms.json --- .../src/ServiceStack.AI.Chat/chat/llms.json | 112 +++++++++++++----- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json b/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json index d6884706d41..90c94d1b459 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json +++ b/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json @@ -219,32 +219,39 @@ "base_url": "https://openrouter.ai/api", "api_key": "$OPENROUTER_API_KEY", "models": { - "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", - "llama4:109b": "meta-llama/llama-4-scout:free", - "llama4:400b": "meta-llama/llama-4-maverick:free", + "kat-coder-pro": "kwaipilot/kat-coder-pro:free", + "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", + "tongyi-deepresearch:30b": "alibaba/tongyi-deepresearch-30b-a3b:free", + "longcat-flash-chat": "meituan/longcat-flash-chat:free", + "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", "deepseek-v3.1:671b": "deepseek/deepseek-chat-v3.1:free", - "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", - "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", - "gemma3n:e2b": "google/gemma-3n-e2b-it:free", + "gpt-oss:20b": "openai/gpt-oss-20b:free", "glm-4.5-air": "z-ai/glm-4.5-air:free", + "qwen3-coder": "qwen/qwen3-coder:free", + "kimi-k2": "moonshotai/kimi-k2:free", + "venice-mistral:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", + "gemma3n:2b": "google/gemma-3n-e2b-it:free", + "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", + "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", + "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", + "gemma3n:4b": "google/gemma-3n-e4b-it:free", + "qwen3:30b": "qwen/qwen3-30b-a3b:free", + "qwen3:14b": "qwen/qwen3-14b:free", + "qwen3:235b": "qwen/qwen3-235b-a22b:free", "mai-ds-r1": "microsoft/mai-ds-r1:free", + "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", + "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", + "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", + "gemma3:4b": "google/gemma-3-4b-it:free", + "gemma3:12b": "google/gemma-3-12b-it:free", + "gemma3:27b": "google/gemma-3-27b-it:free", + "deepseek-r1": "deepseek/deepseek-r1-0528:free", + "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free", - "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", - "deepseek-r1-distill-llama:70b": "deepseek/deepseek-r1-distill-llama-70b:free", - "gpt-oss:20b": "openai/gpt-oss-20b:free", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", + "llama3.2:3b": "meta-llama/llama-3.2-3b-instruct:free", "mistral-nemo:12b": "mistralai/mistral-nemo:free", - "venice-uncensored:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", - "llama3.3:8b": "meta-llama/llama-3.3-8b-instruct:free", - "gemma3:27b": "google/gemma-3-27b-it:free", - "qwen3-coder": "qwen/qwen3-coder:free", - "qwen3:235b": "qwen/qwen3-235b-a22b:free", - "qwen3:30b": "qwen/qwen3-30b-a3b:free", "qwen3:4b": "qwen/qwen3-4b:free", - "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", - "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", - "minimax-m2": "minimax/minimax-m2:free" + "mistral:7b": "mistralai/mistral-7b-instruct:free" }, "default_pricing": { "input": "0", @@ -398,6 +405,10 @@ "base_url": "https://api.openai.com", "api_key": "$OPENAI_API_KEY", "models": { + "gpt-5.1": "gpt-5.1", + "gpt-5.1-chat-latest": "gpt-5.1-chat-latest", + "gpt-5.1-codex": "gpt-5.1-codex", + "gpt-5.1-codex-mini": "gpt-5.1-codex-mini", "gpt-5-nano": "gpt-5-nano", "gpt-5-mini": "gpt-5-mini", "gpt-5": "gpt-5", @@ -417,6 +428,22 @@ "gpt-3.5-turbo": "gpt-3.5-turbo" }, "pricing": { + "gpt-5.1": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-chat-latest": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-codex": { + "input": "0.00000125", + "output": "0.00001" + }, + "gpt-5.1-codex-mini": { + "input": "0.00000025", + "output": "0.000002" + }, "gpt-5-nano": { "input": "0.00000005", "output": "0.0000004" @@ -859,6 +886,10 @@ "gemma3:4b": "google/gemma-3-4b-it", "gemma3:12b": "google/gemma-3-12b-it", "gemma3:27b": "google/gemma-3-27b-it", + "gpt-5.1": "openai/gpt-5.1", + "gpt-5.1-chat": "openai/gpt-5.1-chat", + "gpt-5.1-codex": "openai/gpt-5.1-codex", + "gpt-5.1-codex-mini": "openai/gpt-5.1-codex-mini", "gpt-5": "openai/gpt-5", "gpt-5-chat": "openai/gpt-5-chat", "gpt-5-mini": "openai/gpt-5-mini", @@ -883,6 +914,7 @@ "minimax-m2": "minimax/minimax-m2", "kimi-k2": "moonshotai/kimi-k2", "kimi-k2-thinking": "moonshotai/kimi-k2-thinking", + "kimi-linear:48b": "moonshotai/kimi-linear-48b-a3b-instruct", "deepseek-v3.1:671b": "deepseek/deepseek-chat", "deepseek-v3.2-exp": "deepseek/deepseek-v3.2-exp", "deepseek-chat-v3.1:671b": "deepseek/deepseek-chat-v3.1", @@ -905,8 +937,8 @@ }, "pricing": { "meta-llama/llama-3.1-405b-instruct": { - "input": "0.0000008", - "output": "0.0000008" + "input": "0.0000035", + "output": "0.0000035" }, "meta-llama/llama-3.3-70b-instruct": { "input": "0.00000013", @@ -984,6 +1016,22 @@ "input": "0.00000009", "output": "0.00000016" }, + "openai/gpt-5.1": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-chat": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-codex": { + "input": "0.00000125", + "output": "0.00001" + }, + "openai/gpt-5.1-codex-mini": { + "input": "0.00000025", + "output": "0.000002" + }, "openai/gpt-5": { "input": "0.00000125", "output": "0.00001" @@ -1062,23 +1110,31 @@ }, "z-ai/glm-4.5": { "input": "0.00000035", - "output": "0.00000155" + "output": "0.0000015" }, "z-ai/glm-4.5-air": { "input": "0.00000013", "output": "0.00000085" }, "minimax/minimax-m2": { - "input": "0.00000015", - "output": "0.00000045" + "input": "0.000000255", + "output": "0.00000102" }, "moonshotai/kimi-k2": { - "input": "0.00000014", - "output": "0.00000249" + "input": "0.0000005", + "output": "0.0000024" + }, + "moonshotai/kimi-k2-thinking": { + "input": "0.00000055", + "output": "0.00000225" + }, + "moonshotai/kimi-linear-48b-a3b-instruct": { + "input": "0.0000003", + "output": "0.0000006" }, "deepseek/deepseek-chat": { "input": "0.0000003", - "output": "0.00000085" + "output": "0.0000012" }, "deepseek/deepseek-v3.2-exp": { "input": "0.00000027", From a7b2a7680bd9e8c94cb07165b32c93d429cc4a13 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:11:46 +0800 Subject: [PATCH 040/140] Update NodeProxy --- .../NodeProxy.Usage.md | 115 +++++++++ .../src/ServiceStack.Extensions/NodeProxy.cs | 222 ++++++++++++++---- 2 files changed, 293 insertions(+), 44 deletions(-) create mode 100644 ServiceStack/src/ServiceStack.Extensions/NodeProxy.Usage.md diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.Usage.md b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.Usage.md new file mode 100644 index 00000000000..9263109dbaa --- /dev/null +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.Usage.md @@ -0,0 +1,115 @@ +# NodeProxy Cache Configuration + +## Overview + +The `NodeProxy` class now includes intelligent in-memory caching with configurable size limits to optimize performance when proxying to Node.js applications. + +## Default Settings + +- **MaxFileSizeBytes**: 5 MB (individual file limit) +- **MaxCacheSizeBytes**: 100 MB (total cache size limit) +- **LRU Eviction**: Automatically evicts least recently used entries when cache is full + +## Configuration Examples + +### Basic Usage (Default Settings) + +```csharp +var proxy = new NodeProxy("http://localhost:3000"); +// Uses default: 5 MB per file, 100 MB total cache +``` + +### Custom Size Limits + +```csharp +var proxy = new NodeProxy("http://localhost:3000") +{ + MaxFileSizeBytes = 10 * 1024 * 1024, // 10 MB per file + MaxCacheSizeBytes = 200 * 1024 * 1024, // 200 MB total cache + Verbose = true // Enable cache logging +}; +``` + +### Conservative Settings (Low Memory) + +```csharp +var proxy = new NodeProxy("http://localhost:3000") +{ + MaxFileSizeBytes = 1 * 1024 * 1024, // 1 MB per file + MaxCacheSizeBytes = 25 * 1024 * 1024, // 25 MB total cache +}; +``` + +### Disable Caching for Large Files + +```csharp +var proxy = new NodeProxy("http://localhost:3000") +{ + MaxFileSizeBytes = 512 * 1024, // 512 KB per file (skip large bundles) + MaxCacheSizeBytes = 50 * 1024 * 1024, // 50 MB total cache +}; +``` + +## Cache Statistics + +Monitor cache performance: + +```csharp +var stats = proxy.GetCacheStats(); +Console.WriteLine($"Cache Hits: {stats.hits}"); +Console.WriteLine($"Cache Misses: {stats.misses}"); +Console.WriteLine($"Hit Rate: {stats.hitRate:P2}"); +Console.WriteLine($"Entries: {stats.entryCount}"); +Console.WriteLine($"Total Size: {stats.totalSize / 1024 / 1024} MB"); +``` + +## Cache Management + +### Clear All Cache + +```csharp +proxy.ClearCache(); +``` + +### Remove Specific Entry + +```csharp +proxy.RemoveCacheEntry("/static/bundle.js"); +``` + +### Clear via HTTP Request + +``` +GET /?clear=all // Clear entire cache +GET /path/to/file?clear // Clear specific file +``` + +## How It Works + +1. **Size Check**: Files larger than `MaxFileSizeBytes` are never cached +2. **LRU Eviction**: When adding a new entry would exceed `MaxCacheSizeBytes`, the least recently used entries are evicted +3. **Access Tracking**: Each cache hit updates the `LastAccessTime` for LRU tracking +4. **Thread-Safe**: All cache operations are thread-safe using locks and interlocked operations + +## Recommended Settings by Scenario + +### Production (High Traffic) +- MaxFileSizeBytes: 5-10 MB +- MaxCacheSizeBytes: 200-500 MB + +### Development +- MaxFileSizeBytes: 5 MB +- MaxCacheSizeBytes: 100 MB + +### Low Memory Environments +- MaxFileSizeBytes: 1 MB +- MaxCacheSizeBytes: 25-50 MB + +### Disable Caching +```csharp +var proxy = new NodeProxy("http://localhost:3000") +{ + ShouldCache = (context) => false // Never cache +}; +``` + diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index fe2b898397a..1158545f427 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -10,29 +10,39 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace ServiceStack; public class NodeProxy { public HttpClient Client { get; set; } - public bool Verbose { get; set; } = true; - public string LogPrefix { get; set; } = "[node] "; + public ILogger? Log { get; set; } + + /// + /// Maximum size in bytes for an individual file to be cached (default: 5 MB) + /// + public long MaxFileSizeBytes { get; set; } = 5 * 1024 * 1024; // 5 MB + + /// + /// Maximum total cache size in bytes (default: 100 MB) + /// + public long MaxCacheSizeBytes { get; set; } = 100 * 1024 * 1024; // 100 MB public List CacheFileExtensions { get; set; } = [ - ".js", - ".css", - ".ico", - ".png", - ".jpg", - ".jpeg", - ".gif", - ".svg", - ".woff", - ".woff2", - ".ttf", - ".eot", - ".otf", + ".js", + ".css", + ".ico", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".svg", + ".woff", + ".woff2", + ".ttf", + ".eot", + ".otf", ".map" ]; @@ -64,7 +74,34 @@ public bool DefaultShouldCache(HttpContext context) return false; } - public ConcurrentDictionary Cache { get; } = new(); + private class CacheEntry(string mimeType, byte[] data, string? encoding, DateTime lastAccessTime) + { + public string MimeType { get; } = mimeType; + public byte[] Data { get; } = data; + public string? Encoding { get; } = encoding; + public DateTime LastAccessTime { get; set; } = lastAccessTime; + public long Size => Data.Length; + } + + private readonly ConcurrentDictionary _cache = new(); + private long _totalCacheSize = 0; + private long _cacheHits = 0; + private long _cacheMisses = 0; + private readonly object _cacheLock = new(); + + public ConcurrentDictionary Cache + { + get + { + // Provide backward compatibility by converting internal cache to old format + var result = new ConcurrentDictionary(); + foreach (var kvp in _cache) + { + result[kvp.Key] = (kvp.Value.MimeType, kvp.Value.Data, kvp.Value.Encoding); + } + return result; + } + } public NodeProxy(HttpClient client) { @@ -91,6 +128,92 @@ private void Init(HttpClient client) Client = client; ShouldCache = DefaultShouldCache; } + + public bool LogDebug => Log != null && Log.IsEnabled(LogLevel.Debug); + + private void AddToCache(string key, CacheEntry entry) + { + lock (_cacheLock) + { + // Check if we need to evict entries to make room + var newSize = _totalCacheSize + entry.Size; + + // If adding this entry exceeds the limit, evict LRU entries + while (newSize > MaxCacheSizeBytes && _cache.Count > 0) + { + // Find the least recently used entry + var lruKey = _cache.OrderBy(x => x.Value.LastAccessTime).First().Key; + if (_cache.TryRemove(lruKey, out var removed)) + { + _totalCacheSize -= removed.Size; + newSize = _totalCacheSize + entry.Size; + Log?.LogInformation("Cache evicted (LRU): {LruKey} |size| {Size}", lruKey, removed.Size); + } + } + + // Only add if it fits within the total cache limit + if (entry.Size <= MaxCacheSizeBytes) + { + // Remove old entry if updating + if (_cache.TryRemove(key, out var oldEntry)) + { + _totalCacheSize -= oldEntry.Size; + } + + _cache[key] = entry; + _totalCacheSize += entry.Size; + } + else + { + Log?.LogInformation("Cache skip (exceeds total limit): {Key} |size| {Size} |limit| {MaxCacheSizeBytes}", + key, entry.Size, MaxCacheSizeBytes); + } + } + } + + /// + /// Get cache statistics + /// + public (long hits, long misses, double hitRate, int entryCount, long totalSize) GetCacheStats() + { + var hits = Interlocked.Read(ref _cacheHits); + var misses = Interlocked.Read(ref _cacheMisses); + var total = hits + misses; + var hitRate = total > 0 ? (double)hits / total : 0.0; + + lock (_cacheLock) + { + return (hits, misses, hitRate, _cache.Count, _totalCacheSize); + } + } + + /// + /// Clear all cache entries + /// + public void ClearCache() + { + lock (_cacheLock) + { + _cache.Clear(); + _totalCacheSize = 0; + } + } + + /// + /// Remove a specific cache entry + /// + public bool RemoveCacheEntry(string key) + { + lock (_cacheLock) + { + if (_cache.TryRemove(key, out var entry)) + { + _totalCacheSize -= entry.Size; + return true; + } + return false; + } + } public bool TryStartNode(string workingDirectory, out Process process) { @@ -109,24 +232,18 @@ public bool TryStartNode(string workingDirectory, out Process process) }; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; - if (Verbose) + if (LogDebug) { - var logPrefixTrimmed = LogPrefix.TrimEnd(); process.OutputDataReceived += (s, e) => { if (e.Data != null) { - if (!string.IsNullOrEmpty(logPrefixTrimmed)) - { - Console.Write(logPrefixTrimmed + ":"); - } - Console.WriteLine(e.Data); + Log?.LogDebug(e.Data); } }; process.ErrorDataReceived += (s, e) => { if (e.Data != null) { - Console.Write(LogPrefix + "ERROR:"); - Console.WriteLine(e.Data); + Log?.LogError(e.Data); } }; } @@ -160,24 +277,27 @@ public async Task HttpToNode(HttpContext context) { if (qs.Contains("?clear=all")) { - Cache.Clear(); + ClearCache(); } else { - Cache.TryRemove(cacheKey, out _); + RemoveCacheEntry(cacheKey); } } var shouldCache = ShouldCache(context); - if (shouldCache && Cache.TryGetValue(cacheKey, out var cached)) + if (shouldCache && _cache.TryGetValue(cacheKey, out var cached)) { - if (Verbose) Console.WriteLine($"Cache hit: {cacheKey} |mimeType| {cached.mimeType} |encoding| {cached.encoding} |size| {cached.data.Length}"); - context.Response.ContentType = cached.mimeType; - if (!string.IsNullOrEmpty(cached.encoding)) + Interlocked.Increment(ref _cacheHits); + cached.LastAccessTime = DateTime.UtcNow; + if (LogDebug) Log?.LogDebug("Cache hit: {CacheKey} |mimeType| {MimeType} |encoding| {Encoding} |size| {Size}", + cacheKey, cached.MimeType, cached.Encoding, cached.Size); + context.Response.ContentType = cached.MimeType; + if (!string.IsNullOrEmpty(cached.Encoding)) { - context.Response.Headers["Content-Encoding"] = cached.encoding; + context.Response.Headers["Content-Encoding"] = cached.Encoding; } - await context.Response.Body.WriteAsync(cached.data, context.RequestAborted); + await context.Response.Body.WriteAsync(cached.Data, context.RequestAborted); return; } @@ -240,11 +360,28 @@ public async Task HttpToNode(HttpContext context) var bytes = await response.Content.ReadAsByteArrayAsync(); if (bytes.Length > 0) { + Interlocked.Increment(ref _cacheMisses); + + // Check if file size exceeds individual file limit + if (bytes.Length > MaxFileSizeBytes) + { + Log?.LogInformation("Cache skip (too large): {CacheKey} |size| {Length} |limit| {MaxFileSizeBytes}", + cacheKey, bytes.Length, MaxFileSizeBytes); + await context.Response.Body.WriteAsync(bytes, context.RequestAborted); + return; + } + var mimeType = response.Content.Headers.ContentType?.ToString() ?? ServiceStack.MimeTypes.GetMimeType(cacheKey); var encoding = response.Content.Headers.ContentEncoding.FirstOrDefault(); - Cache[cacheKey] = (mimeType, bytes, encoding); - if (Verbose) Console.WriteLine($"Cache miss: {cacheKey} |mimeType| {mimeType} |encoding| {encoding} |size| {bytes.Length}"); + + var entry = new CacheEntry(mimeType, bytes, encoding, DateTime.UtcNow); + + // Add to cache with size management + AddToCache(cacheKey, entry); + + Log?.LogInformation("Cache miss: {CacheKey} |mimeType| {MimeType} |encoding| {Encoding} |size| {Bytes}", + cacheKey, mimeType, encoding, bytes.Length); await context.Response.Body.WriteAsync(bytes, context.RequestAborted); return; } @@ -300,10 +437,10 @@ public static void MapCleanUrls(this WebApplication app) { var fileProvider = app.Environment.WebRootFileProvider; var fileInfo = fileProvider.GetFileInfo(path + ".html"); - if (fileInfo.Exists && !fileInfo.IsDirectory) + if (fileInfo is { Exists: true, IsDirectory: false }) { context.Response.ContentType = "text/html"; - using var stream = fileInfo.CreateReadStream(); + await using var stream = fileInfo.CreateReadStream(); await stream.CopyToAsync(context.Response.Body); // Serve the HTML file directly return; // Don't call next(), we've handled the request } @@ -414,14 +551,13 @@ await destination.SendAsync( bool registerExitHandler=true) { var process = app.StartNodeProcess(proxy, lockFile, workingDirectory, registerExitHandler); - var logPrefix = proxy.LogPrefix; if (process != null) { - Console.WriteLine(logPrefix + "Started Next.js dev server"); + proxy.Log?.LogInformation("Started Next.js dev server"); } else { - Console.WriteLine(logPrefix + "Next.js dev server already running"); + proxy.Log?.LogInformation("Next.js dev server already running"); } return process; } @@ -437,10 +573,8 @@ await destination.SendAsync( if (!proxy.TryStartNode(workingDirectory, out var process)) return null; - var verbose = proxy.Verbose; - var logPrefix = string.IsNullOrEmpty(proxy.LogPrefix) ? "" : proxy.LogPrefix + " "; process.Exited += (s, e) => { - if (verbose) Console.WriteLine(logPrefix + "Exited: " + process.ExitCode); + proxy.Log?.LogDebug("Exited: " + process.ExitCode); File.Delete(lockFile); }; @@ -449,7 +583,7 @@ await destination.SendAsync( app.Lifetime.ApplicationStopping.Register(() => { if (!process.HasExited) { - if (verbose) Console.WriteLine(logPrefix + "Teminating process: " + process.Id); + proxy.Log?.LogDebug("Terminating process: " + process.Id); process.Kill(entireProcessTree: true); } }); From 57d0d57a4f307d3cff4ea4f2ab29689cccb251c4 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:12:02 +0800 Subject: [PATCH 041/140] Fix ipv6 issues --- .../tests/NorthwindAuto/Properties/launchSettings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ServiceStack/tests/NorthwindAuto/Properties/launchSettings.json b/ServiceStack/tests/NorthwindAuto/Properties/launchSettings.json index c45be5082e4..ca71f31a65f 100644 --- a/ServiceStack/tests/NorthwindAuto/Properties/launchSettings.json +++ b/ServiceStack/tests/NorthwindAuto/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "NorthwindAuto": { "commandName": "Project", - "applicationUrl": "https://*:5001;http://*:5000", + "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -11,7 +11,7 @@ "NorthwindAuto-Migrate": { "commandName": "Project", "commandLineArgs": "--AppTasks=migrate", - "applicationUrl": "https://*:5001;http://*:5000", + "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -19,7 +19,7 @@ "NorthwindAuto-Migrate.Revert": { "commandName": "Project", "commandLineArgs": "--AppTasks=migrate.revert", - "applicationUrl": "https://*:5001;http://*:5000", + "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } From 4dd8a1ba727739ec80d84f758ac6034cfd454d63 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:14:05 +0800 Subject: [PATCH 042/140] Update Admin UIs with placeholders on how to install different features when missing --- .../modules/admin-ui/components/AdminChat.mjs | 23 +- .../modules/admin-ui/components/Analytics.mjs | 29 +- .../modules/admin-ui/components/ApiKeys.mjs | 27 +- .../admin-ui/components/BackgroundJobs.mjs | 24 +- .../modules/admin-ui/components/Commands.mjs | 26 +- .../modules/admin-ui/components/CopyLine.mjs | 29 + .../modules/admin-ui/components/Dashboard.mjs | 1 + .../modules/admin-ui/components/Database.mjs | 19 +- .../admin-ui/components/IdentityRoles.mjs | 20 +- .../admin-ui/components/IdentityUsers.mjs | 20 +- .../modules/admin-ui/components/Logging.mjs | 767 +++++++++-------- .../modules/admin-ui/components/Profiling.mjs | 554 +++++++------ .../modules/admin-ui/components/Redis.mjs | 29 +- .../modules/admin-ui/components/Users.mjs | 26 +- .../admin-ui/components/Validation.mjs | 23 +- .../ServiceStack/modules/admin-ui/index.html | 9 + .../ServiceStack/modules/admin-ui/lib/app.mjs | 8 +- .../admin-ui/components/AdminChat.mjs | 23 +- .../admin-ui/components/Analytics.mjs | 29 +- .../admin-ui/components/ApiKeys.mjs | 27 +- .../admin-ui/components/BackgroundJobs.mjs | 24 +- .../admin-ui/components/Commands.mjs | 26 +- .../admin-ui/components/CopyLine.mjs | 31 + .../admin-ui/components/Dashboard.mjs | 1 + .../admin-ui/components/Database.mjs | 19 +- .../admin-ui/components/IdentityRoles.mjs | 20 +- .../admin-ui/components/IdentityUsers.mjs | 20 +- .../admin-ui/components/Logging.mjs | 779 +++++++++--------- .../admin-ui/components/Profiling.mjs | 564 +++++++------ .../admin-ui/components/Redis.mjs | 29 +- .../admin-ui/components/Users.mjs | 26 +- .../admin-ui/components/Validation.mjs | 23 +- .../tests/NorthwindAuto/admin-ui/index.html | 9 + .../tests/NorthwindAuto/admin-ui/lib/app.mjs | 8 +- 34 files changed, 1958 insertions(+), 1334 deletions(-) create mode 100644 ServiceStack/src/ServiceStack/modules/admin-ui/components/CopyLine.mjs create mode 100644 ServiceStack/tests/NorthwindAuto/admin-ui/components/CopyLine.mjs diff --git a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs index d5f6e217e2a..a0a9688243f 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs @@ -431,7 +431,26 @@ export const AdminChat = { LogDetailDialog, }, template: ` -
+
+
+ Admin Chat UI is not enabled +
+
+

+ The ChatFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -780,6 +799,7 @@ export const AdminChat = { setup() { const routes = inject('routes') const server = inject('server') + const plugin = server.plugins.loaded.includes('aichat') const client = useClient() const selectedLog = ref(null) const preview = ref(false) @@ -1639,6 +1659,7 @@ export const AdminChat = { }) return { + plugin, routes, selectedLog, logs, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs index b18d09ae694..006d0b7bb91 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs @@ -1719,7 +1719,28 @@ export const Analytics = { IpAnalytics, }, template: ` -
+
+
+ Admin Analytics UI is not enabled +
+
+

+ The RequestLogsFeature plugin needs to be configured with your App's RDBMS + + Learn more + +

+
+
+
+

For ASP.NET Identity Auth Projects:

+ +

For other ASP.NET Core Projects:

+ +
+
+
+
@@ -1764,13 +1785,14 @@ export const Analytics = { setup(props) { const routes = inject('routes') const server = inject('server') + const plugin = server.plugins.requestLogs const client = useClient() const analytics = ref(null) const loading = ref(false) const error = ref(null) const api = ref(new ApiResult()) - const tabs = ref(server.plugins.requestLogs?.analytics?.tabs ?? {APIs: ''}) - const months = ref(server.plugins.requestLogs?.analytics?.months ?? []) + const tabs = ref(plugin?.analytics?.tabs ?? {APIs: ''}) + const months = ref(plugin?.analytics?.months ?? []) const years = computed(() => Array.from(new Set(months.value.map(x => leftPart(x, '-')))).toReversed()) async function update() { @@ -1807,6 +1829,7 @@ export const Analytics = { nextTick(update) }) return { + plugin, routes, api, analytics, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs index 7608c6f7ba3..d2f3c259894 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs @@ -4,7 +4,26 @@ import { ApiResult, apiValueFmt, humanify, mapGet } from "@servicestack/client" import { AdminQueryApiKeys } from "dtos" export const ApiKeys = { template:` -
+
+
+ API Keys Admin UI is not enabled +
+
+

+ The ApiKeysFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -91,6 +110,7 @@ export const ApiKeys = { const routes = inject('routes') const store = inject('store') const server = inject('server') + const plugin = server.plugins.apiKey const client = useClient() const { formatDate, relativeTime } = useFormatters() const renderKey = ref(1) @@ -98,10 +118,10 @@ export const ApiKeys = { const api = ref(new ApiResult()) const results = computed(() => api.value?.response?.results || []) const columns = 'id,userName,name,visibleKey,createdDate,expiryDate'.split(',') - if (server.plugins.apiKey.scopes.length) { + if (plugin.scopes.length) { columns.push('scopes') } - if (server.plugins.apiKey.features.length) { + if (plugin.features.length) { columns.push('features') } columns.push('lastUsedDate') @@ -172,6 +192,7 @@ export const ApiKeys = { client, store, server, + plugin, routes, renderKey, link, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs index 5fe502d176c..52c374a5177 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs @@ -997,10 +997,31 @@ export const BackgroundJobs = { ScheduledTasks, }, template: ` - +
+
+ Background Jobs Admin UI is not enabled +
+
+

+ The DatabaseJobFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+ `, setup() { const client = useClient() + const server = inject('server') + const plugin = server.plugins.loaded.includes('backgroundjobs') const tabs = { Dashboard, @@ -1044,6 +1065,7 @@ export const BackgroundJobs = { clearTimeout(updateStatsTimeout) }) return { + plugin, info, tabs, tabLabel, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Commands.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Commands.mjs index acfd730b3cc..2373b89c163 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Commands.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Commands.mjs @@ -6,8 +6,23 @@ import { ViewCommands, ExecuteCommand } from "dtos" import { Chart, registerables } from 'chart.js' Chart.register(...registerables) export const Commands = { - template:/*html*/` -
+ template:` +
+
+ Admin Commands UI is not enabled +
+
+

+ The CommandsFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
@@ -238,6 +253,7 @@ export const Commands = { const routes = inject('routes') const server = inject('server') const client = inject('client') + const plugin = server.plugins.commands const { time, relativeTime, @@ -272,7 +288,7 @@ export const Commands = { : {} } const model = ref({}) - const commandInfos = server.plugins.commands.commands + const commandInfos = plugin.commands const navs = ref(Array.from(new Set(commandInfos.map(x => x.tag ?? 'other'))) .map(x => ({ tag: x, @@ -520,8 +536,8 @@ export const Commands = { error.errors?.length > 0 ? '\n' + error.errors?.map(x => ` - ${x.errorCode}: ${x.message}`).join('\n') : null, ].filter(x => !!x).join('\n') } - return { - routes, api, commandTotals, tabs, elChart, bottom, loadingMore, q, type, + return { + plugin, routes, api, commandTotals, tabs, elChart, bottom, loadingMore, q, type, refresh, headerSelected, rowSelected, selectedError, selectedClean, prettyJson, toggleError, rowSelectedError, toDate, time, altError, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/CopyLine.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/CopyLine.mjs new file mode 100644 index 00000000000..dd3ded74f16 --- /dev/null +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/CopyLine.mjs @@ -0,0 +1,29 @@ +import { ref } from "vue" +import { useUtils } from "@servicestack/vue" +const CopyLine = { + template:`
+
+
{{prefix||''}}{{text}}
+
+
+
+ + + + +
+
+
`, + props:['text','prefix'], + setup(props) { + const { copyText } = useUtils() + const copied = ref(false) + function copy(text) { + copied.value = true + copyText(text) + setTimeout(() => copied.value = false, 3000) + } + return { copied, copy, } + } +} +export default CopyLine diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Dashboard.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Dashboard.mjs index fe114ba1aa3..37646e23e2f 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Dashboard.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Dashboard.mjs @@ -189,6 +189,7 @@ export const Dashboard = { validation:'Validation', database:'Database', redis:'Redis', + aichat:'AI Chat', })) function isRegistered(id) { diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Database.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Database.mjs index 0964287288e..c542ef9dceb 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Database.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Database.mjs @@ -5,8 +5,23 @@ import { keydown } from "app" import { AdminDatabase } from "dtos" import { prettyJson } from "core" export const Database = { - template:/*html*/` -
+ template:` +
+
+ Database Admin UI is not enabled +
+
+

+ The AdminDatabaseFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
`, setup() { const routes = inject('routes') @@ -416,6 +447,7 @@ export const Profiling = { }) return { + plugin, routes, api, prettyJson, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs index 6dcf27c960f..db75d6901c7 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs @@ -97,8 +97,27 @@ const NewKey = { } export const Redis = { components: { NewKey }, - template:/*html*/` -
+ template:` +
+
+ Redis Admin UI is not enabled +
+
+

+ The AdminRedisFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -477,7 +496,7 @@ export const Redis = { const tabs = { 'Info':'', 'Search':'search', 'Command':'command' } const relatedResults = computed(() => apiRelated.value?.response?.searchResults.sort((x,y) => x.id > y.id ? 1 : -1) || []) const connectionString = computed(() => endpoint.value.host ? `${endpoint.value.host}:${endpoint.value.port}` : '') - const modifiableConnection = computed(() => plugin.modifiableConnection) + const modifiableConnection = computed(() => plugin?.modifiableConnection) const itemType = computed(() => item.value?.type || '') const itemValue = computed(() => item.value?.value || '') const db = ref(0) @@ -589,8 +608,8 @@ export const Redis = { let linkFields = 'id,skip'.split(',') let plugin = server.plugins.adminRedis let cloneEndpoint = endpoint => Object.assign({}, { host:'localhost',port:6379,ssl:false,username:'',password:'' }, endpoint) - const endpoint = ref(cloneEndpoint(plugin.endpoint)) - const editEndpoint = ref(cloneEndpoint(plugin.endpoint)) + const endpoint = ref(cloneEndpoint(plugin?.endpoint)) + const editEndpoint = ref(cloneEndpoint(plugin?.endpoint)) /** @param {*?} args * @returns {AdminRedis} */ diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Users.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Users.mjs index cfbe1d13833..64026352420 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Users.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Users.mjs @@ -395,8 +395,23 @@ export const EditUser = { } export const Users = { components: { NewUser, EditUser }, - template:/*html*/` -
+ template:` +
+
+ AdminUsersFeature is not enabled +
+
+

+ The AdminUsersFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
@@ -488,12 +503,14 @@ export const Users = { `, setup() { const routes = inject('routes') + const server = inject('server') const store = inject('store') const client = useClient() const refreshKey = ref(1) + const plugin = server.plugins.adminUsers const request = ref(new AdminQueryUsers({ query:routes.q })) - /** @type {Ref>>} */ + /** @type {Ref>} */ const api = ref(new ApiResult()) const results = computed(() => api.value?.response?.results || []) const fieldNames = computed(() => store.adminUsers.queryUserAuthProperties || []) @@ -536,7 +553,8 @@ export const Users = { return { client, - store, + store, + plugin, routes, refreshKey, link, diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs index ec753b1e2c7..0f3e719fd1b 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs @@ -271,8 +271,27 @@ const EditValidationRule = { } export const Validation = { components: { ApiSelector, EditValidationRule }, - template:/*html*/` -
+ template:` +
+
+ ValidationSource is not enabled +
+
+

+ An IValidationSource needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html index cb2b1c09a5c..c196761fa95 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html @@ -93,6 +93,7 @@ "vue": "../js/vue.mjs", "color.js": "../js/color.js", "chart.js": "../js/chart.js", + "highlight.js": "../js/highlight.mjs", "@servicestack/vue": "../js/servicestack-vue.mjs", "@servicestack/client": "../js/servicestack-client.mjs", "highlight.js": "../js/highlight.mjs", @@ -146,6 +147,14 @@

{{store.link?.label}}

+
+ + This Admin UI feature is not currently installed or enabled in your ServiceStack application. + + Learn more + + +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/lib/app.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/lib/app.mjs index 871d402bf33..9b1ea76e288 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/lib/app.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/lib/app.mjs @@ -111,11 +111,12 @@ export function isInput(e) { export function keydown(e, ctx) { const { unRefs } = useUtils() const { canPrev, canNext, nextSkip, take, results, selected, clearFilters } = unRefs(ctx) - if (hasModifierKey(e) || isInput(e.target) || results.length === 0) return + const show = ctx.show || 'show' if (e.key === 'Escape') { clearFilters() return } + if (hasModifierKey(e) || isInput(e.target) || results.length === 0) return if (e.key === 'ArrowLeft' && canPrev) { routes.to({ skip:nextSkip(-take) }) return @@ -124,7 +125,7 @@ export function keydown(e, ctx) { return } let row = selected - if (!row) return routes.to({ show:map(results[0], x => x.id) || '' }) + if (!row) return routes.to({ [show]: map(results[0], x => x.id) || '' }) let activeIndex = results.findIndex(x => x.id === row.id) let navs = { ArrowUp: activeIndex - 1, @@ -135,7 +136,7 @@ export function keydown(e, ctx) { let nextIndex = navs[e.key] if (nextIndex != null) { if (nextIndex === -1) nextIndex = results.length - 1 - routes.to({ show: map(results[nextIndex % results.length], x => x.id) }) + routes.to({ [show]: map(results[nextIndex % results.length], x => x.id) }) if (e.key.startsWith('Arrow')) { e.preventDefault() } @@ -315,7 +316,6 @@ app.component('RouterLink', ServiceStackVue.component('RouterLink')) app.provides({ app, server, client, store, routes, breakpoints, settings }) app.directive('highlightjs', (el, binding) => { if (binding.value) { - //el.className = '' el.innerHTML = enc(binding.value) hljs.highlightElement(el) } diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs index d5f6e217e2a..a0a9688243f 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs @@ -431,7 +431,26 @@ export const AdminChat = { LogDetailDialog, }, template: ` -
+
+
+ Admin Chat UI is not enabled +
+
+

+ The ChatFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -780,6 +799,7 @@ export const AdminChat = { setup() { const routes = inject('routes') const server = inject('server') + const plugin = server.plugins.loaded.includes('aichat') const client = useClient() const selectedLog = ref(null) const preview = ref(false) @@ -1639,6 +1659,7 @@ export const AdminChat = { }) return { + plugin, routes, selectedLog, logs, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs index 13c49d63977..e3a50b38476 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs @@ -1825,7 +1825,28 @@ export const Analytics = { IpAnalytics, }, template: ` -
+
+
+ Admin Analytics UI is not enabled +
+
+

+ The RequestLogsFeature plugin needs to be configured with your App's RDBMS + + Learn more + +

+
+
+
+

For ASP.NET Identity Auth Projects:

+ +

For other ASP.NET Core Projects:

+ +
+
+
+
@@ -1871,13 +1892,14 @@ export const Analytics = { setup(props) { const routes = inject('routes') const server = inject('server') + const plugin = server.plugins.requestLogs const client = useClient() const analytics = ref(null) const loading = ref(false) const error = ref(null) const api = ref(new ApiResult()) - const tabs = ref(server.plugins.requestLogs?.analytics?.tabs ?? {APIs: ''}) - const months = ref(server.plugins.requestLogs?.analytics?.months ?? []) + const tabs = ref(plugin?.analytics?.tabs ?? {APIs: ''}) + const months = ref(plugin?.analytics?.months ?? []) const years = computed(() => Array.from(new Set(months.value.map(x => leftPart(x, '-')))).toReversed()) @@ -1921,6 +1943,7 @@ export const Analytics = { }) return { + plugin, routes, api, analytics, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs index feb2e5fdb3f..6dd254422dd 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs @@ -5,7 +5,26 @@ import { AdminQueryApiKeys } from "dtos" export const ApiKeys = { template:` -
+
+
+ API Keys Admin UI is not enabled +
+
+

+ The ApiKeysFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -94,6 +113,7 @@ export const ApiKeys = { const routes = inject('routes') const store = inject('store') const server = inject('server') + const plugin = server.plugins.apiKey const client = useClient() const { formatDate, relativeTime } = useFormatters() const renderKey = ref(1) @@ -103,10 +123,10 @@ export const ApiKeys = { const results = computed(() => api.value?.response?.results || []) const columns = 'id,userName,name,visibleKey,createdDate,expiryDate'.split(',') - if (server.plugins.apiKey.scopes.length) { + if (plugin.scopes.length) { columns.push('scopes') } - if (server.plugins.apiKey.features.length) { + if (plugin.features.length) { columns.push('features') } columns.push('lastUsedDate') @@ -190,6 +210,7 @@ export const ApiKeys = { client, store, server, + plugin, routes, renderKey, link, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs index a9588e2b024..b27b351d8ac 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs @@ -1043,10 +1043,31 @@ export const BackgroundJobs = { ScheduledTasks, }, template: ` - +
+
+ Background Jobs Admin UI is not enabled +
+
+

+ The DatabaseJobFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+ `, setup() { const client = useClient() + const server = inject('server') + const plugin = server.plugins.loaded.includes('backgroundjobs') const tabs = { Dashboard, @@ -1094,6 +1115,7 @@ export const BackgroundJobs = { }) return { + plugin, info, tabs, tabLabel, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Commands.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Commands.mjs index 4ebd28171c1..255265c0f27 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Commands.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Commands.mjs @@ -7,8 +7,23 @@ import { Chart, registerables } from 'chart.js' Chart.register(...registerables) export const Commands = { - template:/*html*/` -
+ template:` +
+
+ Admin Commands UI is not enabled +
+
+

+ The CommandsFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
@@ -246,6 +261,7 @@ export const Commands = { const routes = inject('routes') const server = inject('server') const client = inject('client') + const plugin = server.plugins.commands const { time, relativeTime, @@ -282,7 +298,7 @@ export const Commands = { } const model = ref({}) - const commandInfos = server.plugins.commands.commands + const commandInfos = plugin.commands const navs = ref(Array.from(new Set(commandInfos.map(x => x.tag ?? 'other'))) .map(x => ({ tag: x, @@ -543,8 +559,8 @@ export const Commands = { ].filter(x => !!x).join('\n') } - return { - routes, api, commandTotals, tabs, elChart, bottom, loadingMore, q, type, + return { + plugin, routes, api, commandTotals, tabs, elChart, bottom, loadingMore, q, type, refresh, headerSelected, rowSelected, selectedError, selectedClean, prettyJson, toggleError, rowSelectedError, toDate, time, altError, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/CopyLine.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/CopyLine.mjs new file mode 100644 index 00000000000..e4187932b89 --- /dev/null +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/CopyLine.mjs @@ -0,0 +1,31 @@ +import { ref } from "vue" +import { useUtils } from "@servicestack/vue" + +const CopyLine = { + template:`
+
+
{{prefix||''}}{{text}}
+
+
+
+ + + + +
+
+
`, + props:['text','prefix'], + setup(props) { + const { copyText } = useUtils() + const copied = ref(false) + function copy(text) { + copied.value = true + copyText(text) + setTimeout(() => copied.value = false, 3000) + } + + return { copied, copy, } + } +} +export default CopyLine \ No newline at end of file diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Dashboard.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Dashboard.mjs index 9540e783719..4a2cbc08842 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Dashboard.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Dashboard.mjs @@ -199,6 +199,7 @@ export const Dashboard = { validation:'Validation', database:'Database', redis:'Redis', + aichat:'AI Chat', })) function isRegistered(id) { diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Database.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Database.mjs index 68f4f690c19..69012cf8422 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Database.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Database.mjs @@ -6,8 +6,23 @@ import { AdminDatabase } from "dtos" import { prettyJson } from "core" export const Database = { - template:/*html*/` -
+ template:` +
+
+ Database Admin UI is not enabled +
+
+

+ The AdminDatabaseFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
`, setup() { @@ -440,6 +461,7 @@ export const Profiling = { }) return { + plugin, routes, api, prettyJson, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs index 51f5bdd7e8a..b156889117b 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs @@ -104,8 +104,27 @@ const NewKey = { export const Redis = { components: { NewKey }, - template:/*html*/` -
+ template:` +
+
+ Redis Admin UI is not enabled +
+
+

+ The AdminRedisFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
@@ -500,7 +519,7 @@ export const Redis = { const tabs = { 'Info':'', 'Search':'search', 'Command':'command' } const relatedResults = computed(() => apiRelated.value?.response?.searchResults.sort((x,y) => x.id > y.id ? 1 : -1) || []) const connectionString = computed(() => endpoint.value.host ? `${endpoint.value.host}:${endpoint.value.port}` : '') - const modifiableConnection = computed(() => plugin.modifiableConnection) + const modifiableConnection = computed(() => plugin?.modifiableConnection) const itemType = computed(() => item.value?.type || '') const itemValue = computed(() => item.value?.value || '') @@ -619,8 +638,8 @@ export const Redis = { let plugin = server.plugins.adminRedis let cloneEndpoint = endpoint => Object.assign({}, { host:'localhost',port:6379,ssl:false,username:'',password:'' }, endpoint) - const endpoint = ref(cloneEndpoint(plugin.endpoint)) - const editEndpoint = ref(cloneEndpoint(plugin.endpoint)) + const endpoint = ref(cloneEndpoint(plugin?.endpoint)) + const editEndpoint = ref(cloneEndpoint(plugin?.endpoint)) /** @param {*?} args * @returns {AdminRedis} */ diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Users.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Users.mjs index ed09907c0a7..6f499dc9898 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Users.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Users.mjs @@ -413,8 +413,23 @@ export const EditUser = { export const Users = { components: { NewUser, EditUser }, - template:/*html*/` -
+ template:` +
+
+ AdminUsersFeature is not enabled +
+
+

+ The AdminUsersFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+
+
@@ -507,12 +522,14 @@ export const Users = { `, setup() { const routes = inject('routes') + const server = inject('server') const store = inject('store') const client = useClient() const refreshKey = ref(1) + const plugin = server.plugins.adminUsers const request = ref(new AdminQueryUsers({ query:routes.q })) - /** @type {Ref>>} */ + /** @type {Ref>} */ const api = ref(new ApiResult()) const results = computed(() => api.value?.response?.results || []) @@ -558,7 +575,8 @@ export const Users = { return { client, - store, + store, + plugin, routes, refreshKey, link, diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs index fc8a0473c53..47c14b6d2ed 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs @@ -296,8 +296,27 @@ const EditValidationRule = { export const Validation = { components: { ApiSelector, EditValidationRule }, - template:/*html*/` -
+ template:` +
+
+ ValidationSource is not enabled +
+
+

+ An IValidationSource needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html index 57e1028a2b9..10ba23cdedd 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html @@ -96,6 +96,7 @@ "vue": "../js/vue.mjs", "color.js": "../js/color.js", "chart.js": "../js/chart.js", + "highlight.js": "../js/highlight.mjs", "@servicestack/vue": "../js/servicestack-vue.mjs", "@servicestack/client": "../js/servicestack-client.mjs", "highlight.js": "../js/highlight.mjs", @@ -152,6 +153,14 @@

{{store.link?.label}}

+
+ + This Admin UI feature is not currently installed or enabled in your ServiceStack application. + + Learn more + + +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/lib/app.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/lib/app.mjs index be139558826..d65ece15489 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/lib/app.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/lib/app.mjs @@ -124,11 +124,12 @@ export function isInput(e) { export function keydown(e, ctx) { const { unRefs } = useUtils() const { canPrev, canNext, nextSkip, take, results, selected, clearFilters } = unRefs(ctx) - if (hasModifierKey(e) || isInput(e.target) || results.length === 0) return + const show = ctx.show || 'show' if (e.key === 'Escape') { clearFilters() return } + if (hasModifierKey(e) || isInput(e.target) || results.length === 0) return if (e.key === 'ArrowLeft' && canPrev) { routes.to({ skip:nextSkip(-take) }) return @@ -137,7 +138,7 @@ export function keydown(e, ctx) { return } let row = selected - if (!row) return routes.to({ show:map(results[0], x => x.id) || '' }) + if (!row) return routes.to({ [show]: map(results[0], x => x.id) || '' }) let activeIndex = results.findIndex(x => x.id === row.id) let navs = { ArrowUp: activeIndex - 1, @@ -148,7 +149,7 @@ export function keydown(e, ctx) { let nextIndex = navs[e.key] if (nextIndex != null) { if (nextIndex === -1) nextIndex = results.length - 1 - routes.to({ show: map(results[nextIndex % results.length], x => x.id) }) + routes.to({ [show]: map(results[nextIndex % results.length], x => x.id) }) if (e.key.startsWith('Arrow')) { e.preventDefault() } @@ -345,7 +346,6 @@ app.component('RouterLink', ServiceStackVue.component('RouterLink')) app.provides({ app, server, client, store, routes, breakpoints, settings }) app.directive('highlightjs', (el, binding) => { if (binding.value) { - //el.className = '' el.innerHTML = enc(binding.value) hljs.highlightElement(el) } From 41879e9a2f37ada57b09c989270fa6751ae8f986 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 24 Nov 2025 23:26:13 +0800 Subject: [PATCH 043/140] Fix debug test --- ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs index 0cb58f46f5b..fc4143f3405 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs @@ -490,7 +490,7 @@ public static LicenseFeature ActivatedLicenseFeatures() public static void ApprovedUsage(int allowedUsage, int actualUsage, string message) { -#if !DEBUG +#if !DEBUG || true if (actualUsage > allowedUsage) throw new LicenseException(message.Fmt(allowedUsage)).Trace(); #endif From b78b98a3e2e9000117cc05f2c88f480597d30da0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 25 Nov 2025 09:55:46 +0800 Subject: [PATCH 044/140] Add tests support for net8.0;net10.0 --- .../ServiceStack.Common.Tests.csproj | 6 +++--- .../ServiceStack.Extensions.Tests.csproj | 19 +++++++++++++++---- ...erviceStack.WebHost.Endpoints.Tests.csproj | 10 +++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj b/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj index 5f53d7b715d..c5d02766af8 100644 --- a/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj @@ -1,7 +1,7 @@  net10.0;net472 - net10.0 + net8.0;net10.0 portable ServiceStack.Common.Tests ServiceStack.Common.Tests @@ -52,10 +52,10 @@ - + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER - + diff --git a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj index 589b08abb3e..4014bf38fc0 100644 --- a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj @@ -2,11 +2,12 @@ net10.0 + default - - + + $(DefineConstants);AUTOQUERY_CRUD @@ -38,10 +39,20 @@ - - + + + + + + + + + + + + diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj index 19f06fac7e0..9270643c11a 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj @@ -1,7 +1,8 @@  net10.0;net472 - net10.0 + net10.0 + Library ServiceStack.WebHost.Endpoints.Tests ServiceStack.WebHost.Endpoints.Tests @@ -18,8 +19,11 @@ $(DefineConstants);NETFX;NET472 + + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + - $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER + $(DefineConstants);NETCORE;NET6_0_OR_GREATER;NET8_0_OR_GREATER;NET10_0_OR_GREATER @@ -69,7 +73,7 @@ - + From 4fd10604a9bea64f45e34183afd0c23b2e6ef497 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 25 Nov 2025 10:14:33 +0800 Subject: [PATCH 045/140] Upgrade to Microsoft.Data.Sqlite 10.* --- .../ServiceStack.OrmLite.Sqlite.Data.csproj | 7 +++---- .../ServiceStack.WebHost.Endpoints.Tests.csproj | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj index febc4a27c23..fa9765fa2e9 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj @@ -5,10 +5,9 @@ ServiceStack.OrmLite.Sqlite ServiceStack.OrmLite.Sqlite.Data ServiceStack.OrmLite.Sqlite - OrmLite.Sqlite with Microsoft.Data.SQLite + OrmLite.Sqlite with Microsoft.Data.Sqlite - Uses Microsoft.Data.SQLite. - .NET Standard 2.0 version of ServiceStack.OrmLite.Sqlite that uses Microsoft.Data.SQLite + Version of OrmLite Sqlite that uses Microsoft.Data.Sqlite SQLite;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs $(DefineConstants);ASYNC @@ -31,7 +30,7 @@ - + diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj index 9270643c11a..6b88df3e52a 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj @@ -76,7 +76,7 @@ - + From a7327231bb3586a1fa9605df9c8e7eb217ad1f5c Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 25 Nov 2025 10:15:18 +0800 Subject: [PATCH 046/140] Use same Microsoft.AspNetCore.OpenApi 8.* ServiceStack.AspNetCore.OpenApi uses --- .../ServiceStackDocumentFilter.cs | 10 +--------- .../ServiceStack.Extensions.Tests.csproj | 6 ++---- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs index 6e55b10039e..2c695ee2d9c 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackDocumentFilter.cs @@ -1,4 +1,3 @@ -using System.Text.Json.Serialization; using Microsoft.OpenApi.Models; using ServiceStack.Host; using Swashbuckle.AspNetCore.SwaggerGen; @@ -6,15 +5,8 @@ namespace ServiceStack.AspNetCore.OpenApi; // Last OpenApi Filter to run -public class ServiceStackDocumentFilter : IDocumentFilter +public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFilter { - private readonly OpenApiMetadata metadata; - - public ServiceStackDocumentFilter(OpenApiMetadata metadata) - { - this.metadata = metadata; - } - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { //Console.WriteLine(GetType().Name + "..."); diff --git a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj index 4014bf38fc0..f15fbe787a1 100644 --- a/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj +++ b/ServiceStack/tests/ServiceStack.Extensions.Tests/ServiceStack.Extensions.Tests.csproj @@ -39,18 +39,16 @@ + + - - - - From 6c6dfb6ed4c13d0c2291b12554fd5916832b49e1 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 25 Nov 2025 10:38:01 +0800 Subject: [PATCH 047/140] Add special handling for missing /admin-ui/chat callout when missing --- .../ServiceStack/modules/admin-ui/index.html | 21 ++++++++++++++++++- .../tests/NorthwindAuto/admin-ui/index.html | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html index c196761fa95..30e85e814ce 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html @@ -148,7 +148,26 @@

{{store.link?.label}}

- +
+
+ Admin Chat UI is not enabled +
+
+

+ The ChatFeature plugin needs to be configured with your App + + Learn more + +

+
+
+
+

Quick start:

+ +
+
+
+ This Admin UI feature is not currently installed or enabled in your ServiceStack application. Learn more diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html index 10ba23cdedd..0b1454e3072 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html @@ -154,7 +154,26 @@

{{store.link?.label}}

- +
+ +
+ This Admin UI feature is not currently installed or enabled in your ServiceStack application. Learn more From 4cecff37ce9e278a012facbdec873efa19c7569e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 25 Nov 2025 15:46:23 +0800 Subject: [PATCH 048/140] Replace x mix with npx add-in in Admin UI pages --- .../modules/admin-ui/components/AdminChat.mjs | 2 +- .../ServiceStack/modules/admin-ui/components/Analytics.mjs | 4 ++-- .../ServiceStack/modules/admin-ui/components/ApiKeys.mjs | 2 +- .../modules/admin-ui/components/BackgroundJobs.mjs | 2 +- .../ServiceStack/modules/admin-ui/components/Logging.mjs | 6 +++--- .../ServiceStack/modules/admin-ui/components/Profiling.mjs | 2 +- .../src/ServiceStack/modules/admin-ui/components/Redis.mjs | 2 +- .../ServiceStack/modules/admin-ui/components/Validation.mjs | 2 +- ServiceStack/src/ServiceStack/modules/admin-ui/index.html | 2 +- .../tests/NorthwindAuto/admin-ui/components/AdminChat.mjs | 2 +- .../tests/NorthwindAuto/admin-ui/components/Analytics.mjs | 4 ++-- .../tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs | 2 +- .../NorthwindAuto/admin-ui/components/BackgroundJobs.mjs | 2 +- .../tests/NorthwindAuto/admin-ui/components/Logging.mjs | 6 +++--- .../tests/NorthwindAuto/admin-ui/components/Profiling.mjs | 2 +- .../tests/NorthwindAuto/admin-ui/components/Redis.mjs | 2 +- .../tests/NorthwindAuto/admin-ui/components/Validation.mjs | 2 +- ServiceStack/tests/NorthwindAuto/admin-ui/index.html | 2 +- 18 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs index a0a9688243f..f687ac1b618 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs @@ -446,7 +446,7 @@ export const AdminChat = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs index 006d0b7bb91..c12d5299a25 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Analytics.mjs @@ -1734,9 +1734,9 @@ export const Analytics = {

For ASP.NET Identity Auth Projects:

- +

For other ASP.NET Core Projects:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs index d2f3c259894..d769eaa14a1 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/ApiKeys.mjs @@ -19,7 +19,7 @@ export const ApiKeys = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs index 52c374a5177..4b57803cca2 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/BackgroundJobs.mjs @@ -1012,7 +1012,7 @@ export const BackgroundJobs = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Logging.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Logging.mjs index c897026f6b2..0b496bb59c8 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Logging.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Logging.mjs @@ -23,11 +23,11 @@ export const Logging = {

Quick start:

- +

To store in SQLite:

- +

To store in PostgreSQL, MySQL or SQL Server:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Profiling.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Profiling.mjs index 82ea82bacd6..1ed600d0dd3 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Profiling.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Profiling.mjs @@ -23,7 +23,7 @@ export const Profiling = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs index db75d6901c7..46a68ac23fb 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Redis.mjs @@ -113,7 +113,7 @@ export const Redis = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs index 0f3e719fd1b..68e50149f1f 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/components/Validation.mjs @@ -287,7 +287,7 @@ export const Validation = {

Quick start:

- +
diff --git a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html index 30e85e814ce..04e3ecebe93 100644 --- a/ServiceStack/src/ServiceStack/modules/admin-ui/index.html +++ b/ServiceStack/src/ServiceStack/modules/admin-ui/index.html @@ -163,7 +163,7 @@

{{store.link?.label}}

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs index a0a9688243f..f687ac1b618 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs @@ -446,7 +446,7 @@ export const AdminChat = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs index e3a50b38476..f7ef46b2e40 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Analytics.mjs @@ -1840,9 +1840,9 @@ export const Analytics = {

For ASP.NET Identity Auth Projects:

- +

For other ASP.NET Core Projects:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs index 6dd254422dd..68c5243810b 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/ApiKeys.mjs @@ -20,7 +20,7 @@ export const ApiKeys = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs index b27b351d8ac..6ba8f254ed7 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/BackgroundJobs.mjs @@ -1058,7 +1058,7 @@ export const BackgroundJobs = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Logging.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Logging.mjs index 57269bbbfac..e7e71540040 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Logging.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Logging.mjs @@ -24,11 +24,11 @@ export const Logging = {

Quick start:

- +

To store in SQLite:

- +

To store in PostgreSQL, MySQL or SQL Server:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Profiling.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Profiling.mjs index 42ae0e008f0..3d0d6907e5d 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Profiling.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Profiling.mjs @@ -24,7 +24,7 @@ export const Profiling = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs index b156889117b..35d55837375 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Redis.mjs @@ -120,7 +120,7 @@ export const Redis = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs index 47c14b6d2ed..583e04ec871 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/Validation.mjs @@ -312,7 +312,7 @@ export const Validation = {

Quick start:

- +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html index 0b1454e3072..836520a9d77 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/index.html +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/index.html @@ -169,7 +169,7 @@

{{store.link?.label}}

Quick start:

- +
From dd4e4a3733c8ba9509357055a7f106937a57be80 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 01:56:43 +0800 Subject: [PATCH 049/140] Add support for MapViteHmr --- .../src/ServiceStack.Extensions/NodeProxy.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 1158545f427..41331437763 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -469,12 +469,45 @@ public static IEndpointConventionBuilder MapNextHmr(this WebApplication app, Nod }); } + /// + /// Map Vite HMR WebSocket requests + /// + public static void MapViteHmr(this WebApplication app, NodeProxy proxy) + { + app.Use(async (context, next) => + { + // Vite HMR uses WebSocket connections on the root path + // Check if this is a WebSocket upgrade request + if (context.WebSockets.IsWebSocketRequest) + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + else + { + await next(); + } + }); + } + /// /// Proxy WebSocket requests to Node.js /// public static async Task WebSocketToNode(HttpContext context, Uri nextServerBase, bool allowInvalidCerts=true) { - using var clientSocket = await context.WebSockets.AcceptWebSocketAsync(); + // Handle WebSocket subprotocol if requested (Vite HMR uses this) + string? requestedProtocol = null; + if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var protocolValues)) + { + requestedProtocol = protocolValues.ToString(); + } + + var acceptOptions = string.IsNullOrEmpty(requestedProtocol) + ? null + : new WebSocketAcceptContext { SubProtocol = requestedProtocol }; + + using var clientSocket = acceptOptions != null + ? await context.WebSockets.AcceptWebSocketAsync(acceptOptions) + : await context.WebSockets.AcceptWebSocketAsync(); using var nextSocket = new ClientWebSocket(); if (allowInvalidCerts && nextServerBase.Scheme == "https") @@ -487,6 +520,12 @@ public static async Task WebSocketToNode(HttpContext context, Uri nextServerBase nextSocket.Options.SetRequestHeader("Cookie", cookieValues.ToString()); } + // Add WebSocket subprotocol if requested + if (!string.IsNullOrEmpty(requestedProtocol)) + { + nextSocket.Options.AddSubProtocol(requestedProtocol); + } + var builder = new UriBuilder(nextServerBase) { Scheme = nextServerBase.Scheme == "https" ? "wss" : "ws", From 2df7e4ab979c04878c31fc619d9594f0c667dfd1 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 14:05:48 +0800 Subject: [PATCH 050/140] tidy --- ServiceStack/src/ServiceStack.RabbitMq/RabbitMqServer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ServiceStack/src/ServiceStack.RabbitMq/RabbitMqServer.cs b/ServiceStack/src/ServiceStack.RabbitMq/RabbitMqServer.cs index 348cd7760f0..8fbe62c5aa6 100644 --- a/ServiceStack/src/ServiceStack.RabbitMq/RabbitMqServer.cs +++ b/ServiceStack/src/ServiceStack.RabbitMq/RabbitMqServer.cs @@ -143,11 +143,8 @@ public bool DisablePublishingToOutq private IConnection connection; private IConnection Connection => connection ??= ConnectionFactory.CreateConnection(); - private readonly Dictionary handlerMap - = new Dictionary(); - - private readonly Dictionary handlerThreadCountMap - = new Dictionary(); + private readonly Dictionary handlerMap = new(); + private readonly Dictionary handlerThreadCountMap = new(); private RabbitMqWorker[] workers; private Dictionary queueWorkerIndexMap; From 06436bab1ca3a0e3faf06fc7035c07512fc58b50 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 14:06:08 +0800 Subject: [PATCH 051/140] Fix Env Version reporting --- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 9 ++++++--- .../src/ServiceStack.Text/PclExport.NetCore.cs | 6 ++++++ ServiceStack.Text/src/ServiceStack.Text/PclExport.cs | 2 ++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index c9d1489fada..37f394c8b92 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -70,8 +70,10 @@ static Env() SupportsDynamic = true; IsNetCore21 = true; #endif -#if NET8_0_OR_GREATER - IsNet8 = true; +#if NET10_0_OR_GREATER + IsNet10 = true; +#elif NET8_0_OR_GREATER + IsNet8 = true; #elif NET6_0_OR_GREATER IsNet6 = true; #endif @@ -128,7 +130,7 @@ internal static void UpdateServerUserAgent() + PclExport.Instance.PlatformName + (IsLinux ? "/Linux" : IsOSX ? "/macOS" : IsUnix ? "/Unix" : IsWindows ? "/Windows" : "/UnknownOS") + (IsIOS ? "/iOS" : IsAndroid ? "/Android" : IsUWP ? "/UWP" : "") - + (IsNet8 ? "/net8" : IsNet6 ? "/net6" : IsNetStandard20 ? "/std2.0" : IsNetFramework ? "/netfx" : "") + (IsMono ? "/Mono" : "") + + (IsNet10 ? "/net10" : IsNet8 ? "/net8" : IsNet6 ? "/net6" : IsNetStandard20 ? "/std2.0" : IsNetFramework ? "/netfx" : "") + (IsMono ? "/Mono" : "") + $"/{LicenseUtils.Info}"; } @@ -159,6 +161,7 @@ internal static void UpdateServerUserAgent() public static bool IsNetCore21 { get; set; } public static bool IsNet6 { get; set; } public static bool IsNet8 { get; set; } + public static bool IsNet10 { get; set; } public static bool IsNetStandard20 { get; set; } public static bool IsNetFramework { get; set; } diff --git a/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs b/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs index 94cf685eb62..d291dfeeb89 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/PclExport.NetCore.cs @@ -10,7 +10,13 @@ public class Net6PclExport : NetStandardPclExport { public Net6PclExport() { +#if NET10_0_OR_GREATER + this.PlatformName = Platforms.Net10; +#elif NET8_0_OR_GREATER + this.PlatformName = Platforms.Net8; +#else this.PlatformName = Platforms.Net6; +#endif ReflectionOptimizer.Instance = EmitReflectionOptimizer.Provider; } diff --git a/ServiceStack.Text/src/ServiceStack.Text/PclExport.cs b/ServiceStack.Text/src/ServiceStack.Text/PclExport.cs index 0adae5d1581..7b5e73ab7dd 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/PclExport.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/PclExport.cs @@ -23,6 +23,8 @@ public static class Platforms { public const string NetStandard = "NETStd"; public const string Net6 = "NET6"; + public const string Net8 = "NET8"; + public const string Net10 = "NET10"; public const string NetFX = "NETFX"; } From 1a1f5210ee6a57c07990f9f65447f87bad4b3a1d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 14:41:41 +0800 Subject: [PATCH 052/140] Remove unused AddSwagger --- .../ServiceStackOpenApiExtensions.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs index 7728284e552..f8eb7e6997b 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs @@ -23,19 +23,6 @@ public static void WithOpenApi(this ServiceStackOptions options) // configuration is needed, but it's currently a no-op. } - public static void AddSwagger(this ServiceStackServicesOptions options, Action? configure = null) - { - configure?.Invoke(OpenApiMetadata.Instance); - - options.Services!.AddSingleton(OpenApiMetadata.Instance); - options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); - options.Services!.AddSingleton, ConfigureServiceStackSwagger>(); - - options.Services!.ConfigurePlugin(feature => { - feature.AddPluginLink("/swagger/index.html", "Swagger UI"); - }); - } - public static void AddServiceStackSwagger(this IServiceCollection services, Action? configure = null) { configure?.Invoke(OpenApiMetadata.Instance); From 160fcfe3dcd113e25a3fe2581b2d209513031b97 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 15:15:36 +0800 Subject: [PATCH 053/140] Fix swagger security serialization issue --- .../OpenApiMetadata.cs | 52 +++++++------------ .../ServiceStackDocumentFilter.cs | 9 ++-- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs index 25e79a49c15..77238a5c906 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs @@ -15,13 +15,6 @@ namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiSecurity { - public static OpenApiSecurityRequirement BasicAuth { get; } = new() - { - { - new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), - [] - } - }; public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() { In = ParameterLocation.Header, @@ -31,13 +24,6 @@ public static class OpenApiSecurity Scheme = BasicAuthenticationHandler.Scheme, }; - public static OpenApiSecurityRequirement JwtBearer { get; } = new() - { - { - new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), - [] - } - }; public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() { In = ParameterLocation.Header, @@ -48,13 +34,6 @@ public static class OpenApiSecurity Scheme = JwtBearerDefaults.AuthenticationScheme, }; - public static OpenApiSecurityRequirement ApiKey { get; } = new() - { - { - new OpenApiSecuritySchemeReference("ApiKey"), - [] - } - }; public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() { Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", @@ -63,6 +42,18 @@ public static class OpenApiSecurity Type = SecuritySchemeType.ApiKey, Scheme = "ApiKey" }; + + /// + /// Create a security requirement for the given scheme that will serialize correctly. + /// The hostDocument is required for the OpenApiSecuritySchemeReference to resolve its Target. + /// + public static OpenApiSecurityRequirement CreateSecurityRequirement(string schemeId, OpenApiDocument hostDocument) + { + return new OpenApiSecurityRequirement + { + { new OpenApiSecuritySchemeReference(schemeId, hostDocument), [] } + }; + } } public class OpenApiMetadata @@ -125,11 +116,9 @@ public class OpenApiMetadata internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); public OpenApiSecurityScheme? SecurityDefinition { get; set; } - public OpenApiSecurityRequirement? SecurityRequirement { get; set; } - + public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } - public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } - + /// /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI /// @@ -138,22 +127,19 @@ public class OpenApiMetadata public void AddBasicAuth() { SecurityDefinition = OpenApiSecurity.BasicAuthScheme; - SecurityRequirement = OpenApiSecurity.BasicAuth; } public void AddJwtBearer() { SecurityDefinition = OpenApiSecurity.JwtBearerScheme; - SecurityRequirement = OpenApiSecurity.JwtBearer; } public void AddApiKeys() { ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; - ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; } - public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) + public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route, OpenApiDocument? hostDocument = null) { if (ExcludeRequestTypes.Contains(operation.RequestType)) return op; @@ -245,18 +231,18 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s if (operation.RequiresAuthentication) { - if (SecurityRequirement != null) + if (SecurityDefinition != null && hostDocument != null) { op.Security ??= new List(); - op.Security.Add(SecurityRequirement); + op.Security.Add(OpenApiSecurity.CreateSecurityRequirement(SecurityDefinition.Scheme, hostDocument)); } } if (operation.RequiresApiKey) { - if (ApiKeySecurityDefinition != null) + if (ApiKeySecurityDefinition != null && hostDocument != null) { op.Security ??= new List(); - op.Security.Add(ApiKeySecurityRequirement); + op.Security.Add(OpenApiSecurity.CreateSecurityRequirement(ApiKeySecurityDefinition.Scheme, hostDocument)); } } diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs index 9d9d7aa20e5..bd7062941ad 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs @@ -12,14 +12,17 @@ public class ServiceStackDocumentFilter(OpenApiMetadata metadata) : IDocumentFil public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { //Console.WriteLine(GetType().Name + "..."); + + // Use AddComponent to properly register security schemes with the workspace + // This is required for OpenApiSecuritySchemeReference.Target to resolve correctly if (metadata.SecurityDefinition != null) { - swaggerDoc.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; + swaggerDoc.AddComponent(metadata.SecurityDefinition.Scheme, metadata.SecurityDefinition); } if (metadata.ApiKeySecurityDefinition != null) { - swaggerDoc.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; + swaggerDoc.AddComponent(metadata.ApiKeySecurityDefinition.Scheme, metadata.ApiKeySecurityDefinition); } // Ensure we have a Paths collection to populate @@ -107,7 +110,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) OperationId = $"{requestType.Name}{verb}{swaggerPath.Replace("/", "_").Replace("{", "_").Replace("}", string.Empty)}", }; - openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath); + openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath, swaggerDoc); // Responses var responses = GetResponses(metadata, restPath, requestType); From b6387919b486bf2affb0841effe08c76dc3bab2d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 15:32:07 +0800 Subject: [PATCH 054/140] Add AddApiKeys services overload --- .../ServiceStackOpenApiExtensions.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs index f8eb7e6997b..d7a611f1e58 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs @@ -42,8 +42,14 @@ public static AuthenticationBuilder AddBasicAuth(this IServiceCollection return new AuthenticationBuilder(services).AddBasicAuth(); } - public static void AddJwtAuth(this IServiceCollection services) { + public static IServiceCollection AddApiKeys(this IServiceCollection services) { + OpenApiMetadata.Instance.AddApiKeys(); + return services; + } + + public static IServiceCollection AddJwtAuth(this IServiceCollection services) { OpenApiMetadata.Instance.AddJwtBearer(); + return services; } public static void AddBasicAuth(this SwaggerGenOptions options) => From 390ad834e317d802cab63e5f6aa415370c144bad Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 15:32:45 +0800 Subject: [PATCH 055/140] Update OpenApi3Swashbuckle --- .../OpenApi3Swashbuckle/Configure.ApiKeys.cs | 62 ++++ .../OpenApi3Swashbuckle/Configure.Auth.cs | 19 ++ .../Configure.Db.Migrations.cs | 140 +++++++++ .../tests/OpenApi3Swashbuckle/Configure.Db.cs | 30 ++ .../OpenApi3Swashbuckle/Configure.OpenApi.cs | 32 ++ ...301000000_CreateIdentitySchema.Designer.cs | 292 ++++++++++++++++++ .../20240301000000_CreateIdentitySchema.cs | 230 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 289 +++++++++++++++++ .../Migrations/Migration1000.cs | 73 +++++ .../OpenApi3Swashbuckle.csproj | 65 ++-- .../tests/OpenApi3Swashbuckle/Program.cs | 37 ++- .../Data/ApplicationDbContext.cs | 9 + .../ServiceInterface/Data/ApplicationUser.cs | 12 + .../Data/CustomUserSession.cs | 51 +++ .../ServiceInterface/MyServices.cs | 10 + .../OpenApi3Swashbuckle/ServiceModel/Hello.cs | 18 +- .../tests/OpenApi3Swashbuckle/package.json | 9 + 17 files changed, 1336 insertions(+), 42 deletions(-) create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.ApiKeys.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.Migrations.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.Designer.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/Migrations/Migration1000.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationDbContext.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationUser.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/CustomUserSession.cs create mode 100644 ServiceStack/tests/OpenApi3Swashbuckle/package.json diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.ApiKeys.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.ApiKeys.cs new file mode 100644 index 00000000000..f4031f97077 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.ApiKeys.cs @@ -0,0 +1,62 @@ +using MyApp.Data; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using ServiceStack.Configuration; + +[assembly: HostingStartup(typeof(MyApp.ConfigureApiKeys))] + +namespace MyApp; + +public class ConfigureApiKeys : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => + { + services.AddPlugin(new ApiKeysFeature + { + // Optional: Available Scopes Admin Users can assign to any API Key + // Features = [ + // "Paid", + // "Tracking", + // ], + // Optional: Available Features Admin Users can assign to any API Key + // Scopes = [ + // "todo:read", + // "todo:write", + // ], + + // Optional: Limit available Scopes Users can assign to their own API Keys + // UserScopes = [ + // "todo:read", + // ], + // Optional: Limit available Features Users can assign to their own API Keys + // UserFeatures = [ + // "Tracking", + // ], + }); + }) + .ConfigureAppHost(appHost => + { + using var db = appHost.Resolve().Open(); + var feature = appHost.GetPlugin(); + feature.InitSchema(db); + + // Optional: Create API Key for specified Users on Startup + if (feature.ApiKeyCount(db) == 0 && db.TableExists(IdentityUsers.TableName)) + { + // admin@email.com ak-87949de37e894627a9f6173154e7cafa + // manager@email.com ak-09d5b92c2201486d9b8dcb3ed5b8b204 + var createApiKeysFor = new [] { "admin@email.com", "manager@email.com" }; + var users = IdentityUsers.GetByUserNames(db, createApiKeysFor); + foreach (var user in users) + { + List scopes = user.UserName == "admin@email.com" + ? [RoleNames.Admin] + : []; + feature.Insert(db, + new() { Name = "Seed API Key", UserId = user.Id, UserName = user.UserName, Scopes = scopes }); + } + } + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs new file mode 100644 index 00000000000..96d502653e1 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs @@ -0,0 +1,19 @@ +using ServiceStack.Auth; +using MyApp.Data; + +[assembly: HostingStartup(typeof(MyApp.ConfigureAuth))] + +namespace MyApp; + +public class ConfigureAuth : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => { + services.AddPlugin(new AuthFeature(IdentityAuth.For(options => { + options.SessionFactory = () => new CustomUserSession(); + options.CredentialsAuth(); + options.BasicAuth(); // Enable Basic Auth for ServiceStack + options.AdminUsersFeature(); + }))); + }); +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.Migrations.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.Migrations.cs new file mode 100644 index 00000000000..3cc849aaf4c --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.Migrations.cs @@ -0,0 +1,140 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using MyApp.Data; +using MyApp.Migrations; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDbMigrations))] + +namespace MyApp; + +// Code-First DB Migrations: https://docs.servicestack.net/ormlite/db-migrations +public class ConfigureDbMigrations : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureAppHost(appHost => { + var migrator = new Migrator(appHost.Resolve(), typeof(Migration1000).Assembly); + AppTasks.Register("migrate", _ => + { + var log = appHost.GetApplicationServices().GetRequiredService>(); + + log.LogInformation("Running EF Migrations..."); + var scopeFactory = appHost.GetApplicationServices().GetRequiredService(); + using (var scope = scopeFactory.CreateScope()) + { + using var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + if (db.Database.GetPendingMigrations().Any()) { + log.LogInformation("Running EF Migrations..."); + db.Database.Migrate(); + } + + // Only seed users if DB was just created + if (!db.Users.Any()) + { + log.LogInformation("Adding Seed Users..."); + AddSeedUsers(scope.ServiceProvider).Wait(); + } + } + + log.LogInformation("Running OrmLite Migrations..."); + migrator.Run(); + }); + AppTasks.Register("migrate.revert", args => migrator.Revert(args[0])); + AppTasks.Register("migrate.rerun", args => migrator.Rerun(args[0])); + AppTasks.Run(); + }); + + private async Task AddSeedUsers(IServiceProvider services) + { + //initializing custom roles + var roleManager = services.GetRequiredService>(); + var userManager = services.GetRequiredService>(); + string[] allRoles = ["Admin", "Manager", "Employee"]; + + void assertResult(IdentityResult result) + { + if (!result.Succeeded) + throw new Exception(result.Errors.First().Description); + } + + async Task EnsureUserAsync(ApplicationUser user, string password, string[]? roles = null) + { + var existingUser = await userManager.FindByEmailAsync(user.Email!); + if (existingUser != null) return; + + await userManager!.CreateAsync(user, password); + if (roles?.Length > 0) + { + var newUser = await userManager.FindByEmailAsync(user.Email!); + assertResult(await userManager.AddToRolesAsync(user, roles)); + } + } + + foreach (var roleName in allRoles) + { + var roleExist = await roleManager.RoleExistsAsync(roleName); + if (!roleExist) + { + //Create the roles and seed them to the database + assertResult(await roleManager.CreateAsync(new IdentityRole(roleName))); + } + } + + ApplicationUser[] users = [ + new() + { + DisplayName = "Test User", + Email = "test@email.com", + UserName = "test@email.com", + FirstName = "Test", + LastName = "User", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Test Employee", + Email = "employee@email.com", + UserName = "employee@email.com", + FirstName = "Test", + LastName = "Employee", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Test Manager", + Email = "manager@email.com", + UserName = "manager@email.com", + FirstName = "Test", + LastName = "Manager", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Admin User", + Email = "admin@email.com", + UserName = "admin@email.com", + FirstName = "Admin", + LastName = "User", + EmailConfirmed = true, + }, + ]; + + for (int i = 0; i < users.Length; i++) + { + var user = users[i]; + user.ProfileUrl ??= SvgCreator.CreateSvgDataUri(char.ToUpper(user.UserName![0]), + bgColor:SvgCreator.GetDarkColor(i)); + var roles = user.UserName switch + { + "admin@email.com" => allRoles, + "manager@email.com" => ["Manager", "Employee"], + "employee@email.com" => ["Employee"], + _ => null, + }; + await EnsureUserAsync(user, "p@55wOrd", roles); + } + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs new file mode 100644 index 00000000000..5295c35ca2e --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using MyApp.Data; +using Microsoft.EntityFrameworkCore.Diagnostics; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDb))] + +namespace MyApp; + +public class ConfigureDb : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + var connectionString = context.Configuration.GetConnectionString("DefaultConnection") + ?? "DataSource=App_Data/app.db;Cache=Shared"; + + services.AddOrmLite(options => options.UseSqlite(connectionString)); + + // $ dotnet ef migrations add CreateIdentitySchema + // $ dotnet ef database update + services.AddDbContext(options => { + options.UseSqlite(connectionString); + options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); + }); + + // Enable built-in Database Admin UI at /admin-ui/database + services.AddPlugin(new AdminDatabaseFeature()); + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs new file mode 100644 index 00000000000..75aac656151 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs @@ -0,0 +1,32 @@ +[assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] + +namespace MyApp; + +public class ConfigureOpenApi : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + if (context.HostingEnvironment.IsDevelopment()) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddServiceStackSwagger(); + services.AddBasicAuth(); + services.AddApiKeys(); + // services.AddJwtAuth(); + + services.AddTransient(); + } + }); + + public class StartupFilter : IStartupFilter + { + public Action Configure(Action next) => app => + { + app.UseSwagger(); + app.UseSwaggerUI(); + next(app); + }; + } +} + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.Designer.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 00000000000..0679247e72d --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,292 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240301000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FacebookUserId") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("GoogleProfilePageUrl") + .HasColumnType("TEXT"); + + b.Property("GoogleUserId") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MicrosoftUserId") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.cs new file mode 100644 index 00000000000..13addaaa7d7 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/20240301000000_CreateIdentitySchema.cs @@ -0,0 +1,230 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyApp.Migrations +{ + /// + public partial class CreateIdentitySchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + ProfileUrl = table.Column(type: "TEXT", nullable: true), + FacebookUserId = table.Column(type: "TEXT", nullable: true), + GoogleUserId = table.Column(type: "TEXT", nullable: true), + GoogleProfilePageUrl = table.Column(type: "TEXT", nullable: true), + MicrosoftUserId = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/ApplicationDbContextModelSnapshot.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 00000000000..6e6727b2c71 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,289 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FacebookUserId") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("GoogleProfilePageUrl") + .HasColumnType("TEXT"); + + b.Property("GoogleUserId") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MicrosoftUserId") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/Migration1000.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/Migration1000.cs new file mode 100644 index 00000000000..5fa5002df4d --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Migrations/Migration1000.cs @@ -0,0 +1,73 @@ +using System.Data; +using ServiceStack; +using ServiceStack.DataAnnotations; +using ServiceStack.OrmLite; +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization; +using MyApp; + +namespace MyApp.Migrations; + +public class Migration1000 : MigrationBase +{ + + [Notes("Captures a Persons Name & Room Booking information")] + public class Booking : AuditBase + { + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } + public RoomType RoomType { get; set; } + public int RoomNumber { get; set; } + [IntlDateTime(DateStyle.Long)] + public DateTime BookingStartDate { get; set; } + [IntlRelativeTime] + public DateTime? BookingEndDate { get; set; } + [IntlNumber(Currency="USD")] + public decimal Cost { get; set; } + public string? Notes { get; set; } + public bool? Cancelled { get; set; } + } + + public enum RoomType + { + Single, + Double, + Queen, + Twin, + Suite, + } + + + public override void Up() + { + Db.CreateTable(); + CreateBooking(Db, "First Booking!", RoomType.Queen, 10, 100, "employee@email.com"); + CreateBooking(Db, "Booking 2", RoomType.Double, 12, 120, "manager@email.com"); + CreateBooking(Db, "Booking the 3rd", RoomType.Suite, 13, 130, "employee@email.com"); + } + + public void CreateBooking(IDbConnection? db, + string name, RoomType type, int roomNo, decimal cost, string by) => + db.Insert(new Booking + { + Name = name, + RoomType = type, + RoomNumber = roomNo, + Cost = cost, + BookingStartDate = DateTime.UtcNow.AddDays(roomNo), + BookingEndDate = DateTime.UtcNow.AddDays(roomNo + 7), + CreatedBy = by, + CreatedDate = DateTime.UtcNow, + ModifiedBy = by, + ModifiedDate = DateTime.UtcNow, + }); + + public override void Down() + { + Db.DropTable(); + } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj index 6fa0bbaa678..1917accb536 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj +++ b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj @@ -1,31 +1,44 @@ - - net10.0 - enable - enable - OpenApi3Swashbuckle - - - - - - - - - - - - - - - - - - - - - + + net10.0 + enable + enable + OpenApi3Swashbuckle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs index 5b5044a7d8d..b546d8b64cc 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs @@ -1,29 +1,36 @@ +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using MyApp.Data; using OpenApi3Swashbuckle.ServiceInterface; -using ServiceStack; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; -services.AddServiceStack(typeof(MyServices).Assembly); +services.AddAuthorization(); +services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(); +services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo("App_Data")); + +services.AddDatabaseDeveloperPageExceptionFilter(); + +services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles() + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); -if (builder.Environment.IsDevelopment()) -{ - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); - services.AddServiceStackSwagger(); -} +services.AddServiceStack(typeof(MyServices).Assembly); var app = builder.Build(); -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} app.UseHttpsRedirection(); - +app.UseAuthorization(); app.UseServiceStack(new AppHost()); app.Run(); - diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationDbContext.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationDbContext.cs new file mode 100644 index 00000000000..6a4a94c20ea --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace MyApp.Data; + +public class ApplicationDbContext(DbContextOptions options) + : IdentityDbContext(options) +{ +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationUser.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationUser.cs new file mode 100644 index 00000000000..d4e19cf1f2e --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/ApplicationUser.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Identity; + +namespace MyApp.Data; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser +{ + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? DisplayName { get; set; } + public string? ProfileUrl { get; set; } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/CustomUserSession.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/CustomUserSession.cs new file mode 100644 index 00000000000..46cd4ffb4ee --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/Data/CustomUserSession.cs @@ -0,0 +1,51 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using ServiceStack; +using ServiceStack.Web; + +namespace MyApp.Data; + +public class CustomUserSession : AuthUserSession +{ + public override void PopulateFromClaims(IRequest httpReq, ClaimsPrincipal principal) + { + // Populate Session with data from Identity Auth Claims + ProfileUrl = principal.FindFirstValue(JwtClaimTypes.Picture); + } +} + +/// +/// Add additional claims to the Identity Auth Cookie +/// +public class AdditionalUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IApiKeySource apiKeySource, + IOptions optionsAccessor) + : UserClaimsPrincipalFactory(userManager, roleManager, optionsAccessor) +{ + public override async Task CreateAsync(ApplicationUser user) + { + var principal = await base.CreateAsync(user); + var identity = (ClaimsIdentity)principal.Identity!; + + var claims = new List(); + // Add additional claims here + if (user.ProfileUrl != null) + { + claims.Add(new Claim(JwtClaimTypes.Picture, user.ProfileUrl)); + } + + var latestApiKey = (await apiKeySource.GetApiKeysByUserIdAsync(user.Id)) + .OrderByDescending(x => x.CreatedDate) + .FirstOrDefault(); + if (latestApiKey != null) + { + claims.Add(new Claim(JwtClaimTypes.ApiKey, latestApiKey.Key)); + } + + identity.AddClaims(claims); + return principal; + } +} diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs index 56308699bba..51623f0656f 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs @@ -9,5 +9,15 @@ public class MyServices : Service { Result = $"Hello, {request.Name ?? "World"}!" }; + + public object Any(HelloSecure request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; + + public object Any(HelloApiKey request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; } diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs index 822deaddcbf..4dc5675c820 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs @@ -2,9 +2,10 @@ namespace OpenApi3Swashbuckle.ServiceModel; +[Tag("hello")] [Route("/hello", "GET")] [Route("/hello/{Name}", "GET")] -public class Hello : IReturn +public class Hello : IGet, IReturn { public string? Name { get; set; } } @@ -14,3 +15,18 @@ public class HelloResponse public string? Result { get; set; } } +[Tag("hello")] +[Route("/hellosecure", "GET")] +[ValidateIsAuthenticated] +public class HelloSecure : IGet, IReturn +{ + public string? Name { get; set; } +} + +[Tag("hello")] +[Route("/helloapikey", "GET")] +[ValidateApiKey] +public class HelloApiKey : IGet, IReturn +{ + public string? Name { get; set; } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/package.json b/ServiceStack/tests/OpenApi3Swashbuckle/package.json new file mode 100644 index 00000000000..ca6d7359a59 --- /dev/null +++ b/ServiceStack/tests/OpenApi3Swashbuckle/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "dev": "dotnet watch", + "migrate": "dotnet run --AppTasks=migrate", + "revert:last": "dotnet run --AppTasks=migrate.revert:last", + "revert:all": "dotnet run --AppTasks=migrate.revert:all", + "rerun:last": "npm run revert:last && npm run migrate" + } +} \ No newline at end of file From 9381a2ab3a7b07587cb98ffdd53a8168cb9fb2a6 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 16:25:41 +0800 Subject: [PATCH 056/140] Add support for security in Microsoft OpenApi swagger json --- .../OpenApiMetadata.cs | 54 +++++++------------ .../ServiceStackDocumentTransformer.cs | 12 ++--- .../ServiceStackOpenApiExtensions.cs | 8 ++- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs index 179b14a2709..22f46bdd13f 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs @@ -15,13 +15,6 @@ namespace ServiceStack.AspNetCore.OpenApi; public static class OpenApiSecurity { - public static OpenApiSecurityRequirement BasicAuth { get; } = new() - { - { - new OpenApiSecuritySchemeReference(BasicAuthenticationHandler.Scheme), - [] - } - }; public static OpenApiSecurityScheme BasicAuthScheme { get; set; } = new() { In = ParameterLocation.Header, @@ -31,13 +24,6 @@ public static class OpenApiSecurity Scheme = BasicAuthenticationHandler.Scheme, }; - public static OpenApiSecurityRequirement JwtBearer { get; } = new() - { - { - new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), - [] - } - }; public static OpenApiSecurityScheme JwtBearerScheme { get; set; } = new() { In = ParameterLocation.Header, @@ -48,13 +34,6 @@ public static class OpenApiSecurity Scheme = JwtBearerDefaults.AuthenticationScheme, }; - public static OpenApiSecurityRequirement ApiKey { get; } = new() - { - { - new OpenApiSecuritySchemeReference("ApiKey"), - [] - } - }; public static OpenApiSecurityScheme ApiKeyScheme { get; set; } = new() { Description = "API Key authorization header using the Bearer scheme in the format `Bearer `", @@ -63,6 +42,18 @@ public static class OpenApiSecurity Type = SecuritySchemeType.ApiKey, Scheme = "ApiKey" }; + + /// + /// Create a security requirement for the given scheme that will serialize correctly. + /// The hostDocument is required for the OpenApiSecuritySchemeReference to resolve its Target. + /// + public static OpenApiSecurityRequirement CreateSecurityRequirement(string schemeId, OpenApiDocument hostDocument) + { + return new OpenApiSecurityRequirement + { + { new OpenApiSecuritySchemeReference(schemeId, hostDocument), [] } + }; + } } public class OpenApiMetadata @@ -123,13 +114,11 @@ public class OpenApiMetadata public ConcurrentDictionary Schemas { get; } = new(); internal static List InlineSchemaTypesInNamespaces { get; set; } = new(); - + public OpenApiSecurityScheme? SecurityDefinition { get; set; } - public OpenApiSecurityRequirement? SecurityRequirement { get; set; } - + public OpenApiSecurityScheme? ApiKeySecurityDefinition { get; set; } - public OpenApiSecurityRequirement? ApiKeySecurityRequirement { get; set; } - + /// /// Exclude showing Request DTO APIs in Open API metadata and Swagger UI /// @@ -138,22 +127,19 @@ public class OpenApiMetadata public void AddBasicAuth() { SecurityDefinition = OpenApiSecurity.BasicAuthScheme; - SecurityRequirement = OpenApiSecurity.BasicAuth; } public void AddJwtBearer() { SecurityDefinition = OpenApiSecurity.JwtBearerScheme; - SecurityRequirement = OpenApiSecurity.JwtBearer; } public void AddApiKeys() { ApiKeySecurityDefinition = OpenApiSecurity.ApiKeyScheme; - ApiKeySecurityRequirement = OpenApiSecurity.ApiKey; } - public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route) + public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, string verb, string route, OpenApiDocument? hostDocument = null) { if (ExcludeRequestTypes.Contains(operation.RequestType)) return op; @@ -245,18 +231,18 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s if (operation.RequiresAuthentication) { - if (SecurityRequirement != null) + if (SecurityDefinition != null && hostDocument != null) { op.Security ??= new List(); - op.Security.Add(SecurityRequirement); + op.Security.Add(OpenApiSecurity.CreateSecurityRequirement(SecurityDefinition.Scheme, hostDocument)); } } if (operation.RequiresApiKey) { - if (ApiKeySecurityDefinition != null) + if (ApiKeySecurityDefinition != null && hostDocument != null) { op.Security ??= new List(); - op.Security.Add(ApiKeySecurityRequirement); + op.Security.Add(OpenApiSecurity.CreateSecurityRequirement(ApiKeySecurityDefinition.Scheme, hostDocument)); } } diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs index fa0d7410118..4374b16a300 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs @@ -18,18 +18,16 @@ public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerC return Task.CompletedTask; } + // Use AddComponent to properly register security schemes with the workspace + // This is required for OpenApiSecuritySchemeReference.Target to resolve correctly if (metadata.SecurityDefinition != null) { - document.Components ??= new OpenApiComponents(); - document.Components.SecuritySchemes ??= new Dictionary(); - document.Components.SecuritySchemes[metadata.SecurityDefinition.Scheme] = metadata.SecurityDefinition; + document.AddComponent(metadata.SecurityDefinition.Scheme, metadata.SecurityDefinition); } if (metadata.ApiKeySecurityDefinition != null) { - document.Components ??= new OpenApiComponents(); - document.Components.SecuritySchemes ??= new Dictionary(); - document.Components.SecuritySchemes[metadata.ApiKeySecurityDefinition.Scheme] = metadata.ApiKeySecurityDefinition; + document.AddComponent(metadata.ApiKeySecurityDefinition.Scheme, metadata.ApiKeySecurityDefinition); } // Ensure we have a Paths collection to populate @@ -119,7 +117,7 @@ public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerC OperationId = $"{requestType.Name}{verb}{swaggerPath.Replace("/", "_").Replace("{", "_").Replace("}", string.Empty)}", }; - openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath); + openApiOp = metadata.AddOperation(openApiOp, opMeta, verb, routePath, document); // Responses var responses = GetResponses(metadata, restPath, requestType); diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs index 31e73b0985f..6ea9b4ade67 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs @@ -91,8 +91,14 @@ public static AuthenticationBuilder AddBasicAuth(this IServiceCollection return new AuthenticationBuilder(services).AddBasicAuth(); } - public static void AddJwtAuth(this IServiceCollection services) { + public static IServiceCollection AddJwtAuth(this IServiceCollection services) { OpenApiMetadata.Instance.AddJwtBearer(); + return services; + } + + public static IServiceCollection AddApiKeys(this IServiceCollection services) { + OpenApiMetadata.Instance.AddApiKeys(); + return services; } internal static List ToOpenApiEnums(this IEnumerable? enums) => From f373fc26907a9bd96895e86605eb0ed11f4e69a3 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 16:26:00 +0800 Subject: [PATCH 057/140] Rename OpenApi3Swashbuckle to MyApp --- ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs | 6 +++--- .../tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj | 4 ++-- ServiceStack/tests/OpenApi3Swashbuckle/Program.cs | 2 +- .../OpenApi3Swashbuckle/ServiceInterface/MyServices.cs | 4 ++-- .../tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs index 43bd2c7164e..f57bd26cbe8 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.AppHost.cs @@ -1,9 +1,9 @@ using ServiceStack; -using OpenApi3Swashbuckle.ServiceInterface; +using MyApp.ServiceInterface; -[assembly: HostingStartup(typeof(OpenApi3Swashbuckle.AppHost))] +[assembly: HostingStartup(typeof(MyApp.AppHost))] -namespace OpenApi3Swashbuckle; +namespace MyApp; public class AppHost() : AppHostBase("OpenApi3Swashbuckle", typeof(MyServices).Assembly), IHostingStartup { diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj index 1917accb536..596d4425b53 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj +++ b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj @@ -4,11 +4,11 @@ net10.0 enable enable - OpenApi3Swashbuckle + MyApp - + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs index b546d8b64cc..d2611d45760 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using MyApp.Data; -using OpenApi3Swashbuckle.ServiceInterface; +using MyApp.ServiceInterface; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs index 51623f0656f..1240e95108b 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceInterface/MyServices.cs @@ -1,7 +1,7 @@ -using OpenApi3Swashbuckle.ServiceModel; +using MyApp.ServiceModel; using ServiceStack; -namespace OpenApi3Swashbuckle.ServiceInterface; +namespace MyApp.ServiceInterface; public class MyServices : Service { diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs index 4dc5675c820..9612b01b555 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/ServiceModel/Hello.cs @@ -1,6 +1,6 @@ using ServiceStack; -namespace OpenApi3Swashbuckle.ServiceModel; +namespace MyApp.ServiceModel; [Tag("hello")] [Route("/hello", "GET")] From b7d6440563015af3c09eb745338549f4798e1c97 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 16:26:53 +0800 Subject: [PATCH 058/140] Configure Auth to OpenApiScalar demo project --- .../tests/OpenApiScalar/Configure.ApiKeys.cs | 62 ++++ .../tests/OpenApiScalar/Configure.AppHost.cs | 9 +- .../tests/OpenApiScalar/Configure.Auth.cs | 19 ++ .../OpenApiScalar/Configure.Db.Migrations.cs | 140 +++++++++ .../tests/OpenApiScalar/Configure.Db.cs | 30 ++ .../tests/OpenApiScalar/Configure.OpenApi.cs | 37 +++ ...301000000_CreateIdentitySchema.Designer.cs | 292 ++++++++++++++++++ .../20240301000000_CreateIdentitySchema.cs | 230 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 289 +++++++++++++++++ .../OpenApiScalar/Migrations/Migration1000.cs | 73 +++++ .../tests/OpenApiScalar/OpenApiScalar.csproj | 77 +++-- ServiceStack/tests/OpenApiScalar/Program.cs | 37 ++- .../Data/ApplicationDbContext.cs | 9 + .../ServiceInterface/Data/ApplicationUser.cs | 12 + .../Data/CustomUserSession.cs | 51 +++ .../ServiceInterface/MyServices.cs | 14 +- .../tests/OpenApiScalar/ServiceModel/Hello.cs | 20 +- ServiceStack/tests/OpenApiScalar/package.json | 9 + 18 files changed, 1357 insertions(+), 53 deletions(-) create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.ApiKeys.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.Auth.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.Db.Migrations.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.Db.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.Designer.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 ServiceStack/tests/OpenApiScalar/Migrations/Migration1000.cs create mode 100644 ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationDbContext.cs create mode 100644 ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationUser.cs create mode 100644 ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/CustomUserSession.cs create mode 100644 ServiceStack/tests/OpenApiScalar/package.json diff --git a/ServiceStack/tests/OpenApiScalar/Configure.ApiKeys.cs b/ServiceStack/tests/OpenApiScalar/Configure.ApiKeys.cs new file mode 100644 index 00000000000..f4031f97077 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.ApiKeys.cs @@ -0,0 +1,62 @@ +using MyApp.Data; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using ServiceStack.Configuration; + +[assembly: HostingStartup(typeof(MyApp.ConfigureApiKeys))] + +namespace MyApp; + +public class ConfigureApiKeys : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => + { + services.AddPlugin(new ApiKeysFeature + { + // Optional: Available Scopes Admin Users can assign to any API Key + // Features = [ + // "Paid", + // "Tracking", + // ], + // Optional: Available Features Admin Users can assign to any API Key + // Scopes = [ + // "todo:read", + // "todo:write", + // ], + + // Optional: Limit available Scopes Users can assign to their own API Keys + // UserScopes = [ + // "todo:read", + // ], + // Optional: Limit available Features Users can assign to their own API Keys + // UserFeatures = [ + // "Tracking", + // ], + }); + }) + .ConfigureAppHost(appHost => + { + using var db = appHost.Resolve().Open(); + var feature = appHost.GetPlugin(); + feature.InitSchema(db); + + // Optional: Create API Key for specified Users on Startup + if (feature.ApiKeyCount(db) == 0 && db.TableExists(IdentityUsers.TableName)) + { + // admin@email.com ak-87949de37e894627a9f6173154e7cafa + // manager@email.com ak-09d5b92c2201486d9b8dcb3ed5b8b204 + var createApiKeysFor = new [] { "admin@email.com", "manager@email.com" }; + var users = IdentityUsers.GetByUserNames(db, createApiKeysFor); + foreach (var user in users) + { + List scopes = user.UserName == "admin@email.com" + ? [RoleNames.Admin] + : []; + feature.Insert(db, + new() { Name = "Seed API Key", UserId = user.Id, UserName = user.UserName, Scopes = scopes }); + } + } + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs b/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs index cf30da7e42b..f57bd26cbe8 100644 --- a/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs +++ b/ServiceStack/tests/OpenApiScalar/Configure.AppHost.cs @@ -1,11 +1,11 @@ using ServiceStack; -using OpenApiScalar.ServiceInterface; +using MyApp.ServiceInterface; -[assembly: HostingStartup(typeof(OpenApiScalar.AppHost))] +[assembly: HostingStartup(typeof(MyApp.AppHost))] -namespace OpenApiScalar; +namespace MyApp; -public class AppHost() : AppHostBase("OpenApiScalar", typeof(MyServices).Assembly), IHostingStartup +public class AppHost() : AppHostBase("OpenApi3Swashbuckle", typeof(MyServices).Assembly), IHostingStartup { public void Configure(IWebHostBuilder builder) => builder .ConfigureServices(services => @@ -22,3 +22,4 @@ public override void Configure() }); } } + diff --git a/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs b/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs new file mode 100644 index 00000000000..96d502653e1 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs @@ -0,0 +1,19 @@ +using ServiceStack.Auth; +using MyApp.Data; + +[assembly: HostingStartup(typeof(MyApp.ConfigureAuth))] + +namespace MyApp; + +public class ConfigureAuth : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => { + services.AddPlugin(new AuthFeature(IdentityAuth.For(options => { + options.SessionFactory = () => new CustomUserSession(); + options.CredentialsAuth(); + options.BasicAuth(); // Enable Basic Auth for ServiceStack + options.AdminUsersFeature(); + }))); + }); +} diff --git a/ServiceStack/tests/OpenApiScalar/Configure.Db.Migrations.cs b/ServiceStack/tests/OpenApiScalar/Configure.Db.Migrations.cs new file mode 100644 index 00000000000..3cc849aaf4c --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.Db.Migrations.cs @@ -0,0 +1,140 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using MyApp.Data; +using MyApp.Migrations; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDbMigrations))] + +namespace MyApp; + +// Code-First DB Migrations: https://docs.servicestack.net/ormlite/db-migrations +public class ConfigureDbMigrations : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureAppHost(appHost => { + var migrator = new Migrator(appHost.Resolve(), typeof(Migration1000).Assembly); + AppTasks.Register("migrate", _ => + { + var log = appHost.GetApplicationServices().GetRequiredService>(); + + log.LogInformation("Running EF Migrations..."); + var scopeFactory = appHost.GetApplicationServices().GetRequiredService(); + using (var scope = scopeFactory.CreateScope()) + { + using var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + if (db.Database.GetPendingMigrations().Any()) { + log.LogInformation("Running EF Migrations..."); + db.Database.Migrate(); + } + + // Only seed users if DB was just created + if (!db.Users.Any()) + { + log.LogInformation("Adding Seed Users..."); + AddSeedUsers(scope.ServiceProvider).Wait(); + } + } + + log.LogInformation("Running OrmLite Migrations..."); + migrator.Run(); + }); + AppTasks.Register("migrate.revert", args => migrator.Revert(args[0])); + AppTasks.Register("migrate.rerun", args => migrator.Rerun(args[0])); + AppTasks.Run(); + }); + + private async Task AddSeedUsers(IServiceProvider services) + { + //initializing custom roles + var roleManager = services.GetRequiredService>(); + var userManager = services.GetRequiredService>(); + string[] allRoles = ["Admin", "Manager", "Employee"]; + + void assertResult(IdentityResult result) + { + if (!result.Succeeded) + throw new Exception(result.Errors.First().Description); + } + + async Task EnsureUserAsync(ApplicationUser user, string password, string[]? roles = null) + { + var existingUser = await userManager.FindByEmailAsync(user.Email!); + if (existingUser != null) return; + + await userManager!.CreateAsync(user, password); + if (roles?.Length > 0) + { + var newUser = await userManager.FindByEmailAsync(user.Email!); + assertResult(await userManager.AddToRolesAsync(user, roles)); + } + } + + foreach (var roleName in allRoles) + { + var roleExist = await roleManager.RoleExistsAsync(roleName); + if (!roleExist) + { + //Create the roles and seed them to the database + assertResult(await roleManager.CreateAsync(new IdentityRole(roleName))); + } + } + + ApplicationUser[] users = [ + new() + { + DisplayName = "Test User", + Email = "test@email.com", + UserName = "test@email.com", + FirstName = "Test", + LastName = "User", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Test Employee", + Email = "employee@email.com", + UserName = "employee@email.com", + FirstName = "Test", + LastName = "Employee", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Test Manager", + Email = "manager@email.com", + UserName = "manager@email.com", + FirstName = "Test", + LastName = "Manager", + EmailConfirmed = true, + }, + new() + { + DisplayName = "Admin User", + Email = "admin@email.com", + UserName = "admin@email.com", + FirstName = "Admin", + LastName = "User", + EmailConfirmed = true, + }, + ]; + + for (int i = 0; i < users.Length; i++) + { + var user = users[i]; + user.ProfileUrl ??= SvgCreator.CreateSvgDataUri(char.ToUpper(user.UserName![0]), + bgColor:SvgCreator.GetDarkColor(i)); + var roles = user.UserName switch + { + "admin@email.com" => allRoles, + "manager@email.com" => ["Manager", "Employee"], + "employee@email.com" => ["Employee"], + _ => null, + }; + await EnsureUserAsync(user, "p@55wOrd", roles); + } + } +} diff --git a/ServiceStack/tests/OpenApiScalar/Configure.Db.cs b/ServiceStack/tests/OpenApiScalar/Configure.Db.cs new file mode 100644 index 00000000000..5295c35ca2e --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.Db.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using MyApp.Data; +using Microsoft.EntityFrameworkCore.Diagnostics; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDb))] + +namespace MyApp; + +public class ConfigureDb : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + var connectionString = context.Configuration.GetConnectionString("DefaultConnection") + ?? "DataSource=App_Data/app.db;Cache=Shared"; + + services.AddOrmLite(options => options.UseSqlite(connectionString)); + + // $ dotnet ef migrations add CreateIdentitySchema + // $ dotnet ef database update + services.AddDbContext(options => { + options.UseSqlite(connectionString); + options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); + }); + + // Enable built-in Database Admin UI at /admin-ui/database + services.AddPlugin(new AdminDatabaseFeature()); + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs b/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs new file mode 100644 index 00000000000..69bfdd77f02 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs @@ -0,0 +1,37 @@ +using Scalar.AspNetCore; + +[assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] + +namespace MyApp; + +public class ConfigureOpenApi : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + if (context.HostingEnvironment.IsDevelopment()) + { + services.AddOpenApi(); + services.AddServiceStackOpenApi(); + services.AddBasicAuth(); + services.AddApiKeys(); + // services.AddJwtAuth(); + + services.AddTransient(); + } + }); + + public class StartupFilter : IStartupFilter + { + public Action Configure(Action next) => app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapOpenApi(); + endpoints.MapScalarApiReference(); + }); + next(app); + }; + } +} + diff --git a/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.Designer.cs b/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 00000000000..0679247e72d --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,292 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240301000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FacebookUserId") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("GoogleProfilePageUrl") + .HasColumnType("TEXT"); + + b.Property("GoogleUserId") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MicrosoftUserId") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.cs b/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.cs new file mode 100644 index 00000000000..13addaaa7d7 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Migrations/20240301000000_CreateIdentitySchema.cs @@ -0,0 +1,230 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyApp.Migrations +{ + /// + public partial class CreateIdentitySchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + ProfileUrl = table.Column(type: "TEXT", nullable: true), + FacebookUserId = table.Column(type: "TEXT", nullable: true), + GoogleUserId = table.Column(type: "TEXT", nullable: true), + GoogleProfilePageUrl = table.Column(type: "TEXT", nullable: true), + MicrosoftUserId = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/ServiceStack/tests/OpenApiScalar/Migrations/ApplicationDbContextModelSnapshot.cs b/ServiceStack/tests/OpenApiScalar/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 00000000000..6e6727b2c71 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,289 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FacebookUserId") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("GoogleProfilePageUrl") + .HasColumnType("TEXT"); + + b.Property("GoogleUserId") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MicrosoftUserId") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/OpenApiScalar/Migrations/Migration1000.cs b/ServiceStack/tests/OpenApiScalar/Migrations/Migration1000.cs new file mode 100644 index 00000000000..5fa5002df4d --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/Migrations/Migration1000.cs @@ -0,0 +1,73 @@ +using System.Data; +using ServiceStack; +using ServiceStack.DataAnnotations; +using ServiceStack.OrmLite; +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization; +using MyApp; + +namespace MyApp.Migrations; + +public class Migration1000 : MigrationBase +{ + + [Notes("Captures a Persons Name & Room Booking information")] + public class Booking : AuditBase + { + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } + public RoomType RoomType { get; set; } + public int RoomNumber { get; set; } + [IntlDateTime(DateStyle.Long)] + public DateTime BookingStartDate { get; set; } + [IntlRelativeTime] + public DateTime? BookingEndDate { get; set; } + [IntlNumber(Currency="USD")] + public decimal Cost { get; set; } + public string? Notes { get; set; } + public bool? Cancelled { get; set; } + } + + public enum RoomType + { + Single, + Double, + Queen, + Twin, + Suite, + } + + + public override void Up() + { + Db.CreateTable(); + CreateBooking(Db, "First Booking!", RoomType.Queen, 10, 100, "employee@email.com"); + CreateBooking(Db, "Booking 2", RoomType.Double, 12, 120, "manager@email.com"); + CreateBooking(Db, "Booking the 3rd", RoomType.Suite, 13, 130, "employee@email.com"); + } + + public void CreateBooking(IDbConnection? db, + string name, RoomType type, int roomNo, decimal cost, string by) => + db.Insert(new Booking + { + Name = name, + RoomType = type, + RoomNumber = roomNo, + Cost = cost, + BookingStartDate = DateTime.UtcNow.AddDays(roomNo), + BookingEndDate = DateTime.UtcNow.AddDays(roomNo + 7), + CreatedBy = by, + CreatedDate = DateTime.UtcNow, + ModifiedBy = by, + ModifiedDate = DateTime.UtcNow, + }); + + public override void Down() + { + Db.DropTable(); + } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj index 845ec22e60d..d1bf888ff00 100644 --- a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj +++ b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj @@ -1,39 +1,52 @@ - - net10.0 - enable - enable - OpenApiScalar - - - - - - - - - - - - - - - - - - - - - - - - - + + net10.0 + enable + enable + MyApp + + + + + + + + + + + + + + - + + - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ServiceStack/tests/OpenApiScalar/Program.cs b/ServiceStack/tests/OpenApiScalar/Program.cs index 78053902cbc..29b38562031 100644 --- a/ServiceStack/tests/OpenApiScalar/Program.cs +++ b/ServiceStack/tests/OpenApiScalar/Program.cs @@ -1,25 +1,36 @@ -using OpenApiScalar; -using OpenApiScalar.ServiceInterface; -using Scalar.AspNetCore; -using ServiceStack; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using MyApp.Data; +using MyApp.ServiceInterface; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; -services.AddOpenApi(); -services.AddServiceStackOpenApi(); +services.AddAuthorization(); +services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(); +services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo("App_Data")); + +services.AddDatabaseDeveloperPageExceptionFilter(); + +services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles() + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); + services.AddServiceStack(typeof(MyServices).Assembly); var app = builder.Build(); app.UseHttpsRedirection(); +app.UseAuthorization(); app.UseServiceStack(new AppHost()); -if (app.Environment.IsDevelopment()) -{ - app.MapOpenApi(); - app.MapScalarApiReference(); -} - -app.Run(); +app.Run(); \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationDbContext.cs b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationDbContext.cs new file mode 100644 index 00000000000..6a4a94c20ea --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace MyApp.Data; + +public class ApplicationDbContext(DbContextOptions options) + : IdentityDbContext(options) +{ +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationUser.cs b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationUser.cs new file mode 100644 index 00000000000..d4e19cf1f2e --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/ApplicationUser.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Identity; + +namespace MyApp.Data; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser +{ + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? DisplayName { get; set; } + public string? ProfileUrl { get; set; } +} diff --git a/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/CustomUserSession.cs b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/CustomUserSession.cs new file mode 100644 index 00000000000..46cd4ffb4ee --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/ServiceInterface/Data/CustomUserSession.cs @@ -0,0 +1,51 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using ServiceStack; +using ServiceStack.Web; + +namespace MyApp.Data; + +public class CustomUserSession : AuthUserSession +{ + public override void PopulateFromClaims(IRequest httpReq, ClaimsPrincipal principal) + { + // Populate Session with data from Identity Auth Claims + ProfileUrl = principal.FindFirstValue(JwtClaimTypes.Picture); + } +} + +/// +/// Add additional claims to the Identity Auth Cookie +/// +public class AdditionalUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IApiKeySource apiKeySource, + IOptions optionsAccessor) + : UserClaimsPrincipalFactory(userManager, roleManager, optionsAccessor) +{ + public override async Task CreateAsync(ApplicationUser user) + { + var principal = await base.CreateAsync(user); + var identity = (ClaimsIdentity)principal.Identity!; + + var claims = new List(); + // Add additional claims here + if (user.ProfileUrl != null) + { + claims.Add(new Claim(JwtClaimTypes.Picture, user.ProfileUrl)); + } + + var latestApiKey = (await apiKeySource.GetApiKeysByUserIdAsync(user.Id)) + .OrderByDescending(x => x.CreatedDate) + .FirstOrDefault(); + if (latestApiKey != null) + { + claims.Add(new Claim(JwtClaimTypes.ApiKey, latestApiKey.Key)); + } + + identity.AddClaims(claims); + return principal; + } +} diff --git a/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs b/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs index 2684db0b575..1240e95108b 100644 --- a/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs +++ b/ServiceStack/tests/OpenApiScalar/ServiceInterface/MyServices.cs @@ -1,7 +1,7 @@ -using OpenApiScalar.ServiceModel; +using MyApp.ServiceModel; using ServiceStack; -namespace OpenApiScalar.ServiceInterface; +namespace MyApp.ServiceInterface; public class MyServices : Service { @@ -9,5 +9,15 @@ public class MyServices : Service { Result = $"Hello, {request.Name ?? "World"}!" }; + + public object Any(HelloSecure request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; + + public object Any(HelloApiKey request) => new HelloResponse + { + Result = $"Hello, {request.Name ?? "World"}!" + }; } diff --git a/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs b/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs index 67fcde34326..9612b01b555 100644 --- a/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs +++ b/ServiceStack/tests/OpenApiScalar/ServiceModel/Hello.cs @@ -1,10 +1,11 @@ using ServiceStack; -namespace OpenApiScalar.ServiceModel; +namespace MyApp.ServiceModel; +[Tag("hello")] [Route("/hello", "GET")] [Route("/hello/{Name}", "GET")] -public class Hello : IReturn +public class Hello : IGet, IReturn { public string? Name { get; set; } } @@ -14,3 +15,18 @@ public class HelloResponse public string? Result { get; set; } } +[Tag("hello")] +[Route("/hellosecure", "GET")] +[ValidateIsAuthenticated] +public class HelloSecure : IGet, IReturn +{ + public string? Name { get; set; } +} + +[Tag("hello")] +[Route("/helloapikey", "GET")] +[ValidateApiKey] +public class HelloApiKey : IGet, IReturn +{ + public string? Name { get; set; } +} \ No newline at end of file diff --git a/ServiceStack/tests/OpenApiScalar/package.json b/ServiceStack/tests/OpenApiScalar/package.json new file mode 100644 index 00000000000..ca6d7359a59 --- /dev/null +++ b/ServiceStack/tests/OpenApiScalar/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "dev": "dotnet watch", + "migrate": "dotnet run --AppTasks=migrate", + "revert:last": "dotnet run --AppTasks=migrate.revert:last", + "revert:all": "dotnet run --AppTasks=migrate.revert:all", + "rerun:last": "npm run revert:last && npm run migrate" + } +} \ No newline at end of file From 4b710f6bb9f343729b89ced5a42e09cc0e9815e2 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 16:30:50 +0800 Subject: [PATCH 059/140] Update AdhocNew.csproj --- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index d9f3d98870c..da002d5bd2d 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -21,7 +21,7 @@ - + From 3e515e87b97517c364eb2321cd2eae6f98428e55 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 26 Nov 2025 16:54:34 +0800 Subject: [PATCH 060/140] Downgrade Microsoft.AspNetCore.OpenApi to use its Microsoft.OpenApi to avoid breaking changes --- .../OpenApiMetadata.cs | 6 +-- ...eStack.AspNetCore.OpenApi.Microsoft.csproj | 2 +- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 9 +---- .../tests/AdhocNew/Configure.OpenApi.cs | 39 ++++++++++--------- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs index 22f46bdd13f..a4350e8c951 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs @@ -215,7 +215,7 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s var requestBody = new OpenApiRequestBody { }; - var content = requestBody.Content ??= new OrderedDictionary(); + var content = requestBody.Content ??= new OrderedDictionary(); content[MimeTypes.MultiPartFormData] = formType; if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) { @@ -1054,7 +1054,7 @@ internal OrderedDictionary GetMethodResponseCodes(IRest { Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" }; - var content = response.Content ??= new OrderedDictionary(); + var content = response.Content ??= new OrderedDictionary(); content[MimeTypes.Json] = new OpenApiMediaType { Schema = responseSchema, @@ -1069,7 +1069,7 @@ internal OrderedDictionary GetMethodResponseCodes(IRest { Description = attr.Description ?? apiSchemaDescription }; - var apiContent = apiResponse.Content ??= new OrderedDictionary(); + var apiContent = apiResponse.Content ??= new OrderedDictionary(); apiContent[MimeTypes.Json] = new OpenApiMediaType { Schema = attr.ResponseType != null diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj index 528099891ce..5457b778be1 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj @@ -8,7 +8,7 @@ - + diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index da002d5bd2d..bbfa442c223 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -7,6 +7,7 @@ MyApp aspnet-MyApp-7b2ab71a-0b50-423f-969d-e35a9402b1b5 true + $(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated @@ -25,7 +26,6 @@ - @@ -54,11 +54,4 @@ - - - - - - - diff --git a/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs b/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs index 58d97bceafe..a1f9e1bfc1c 100644 --- a/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs +++ b/ServiceStack/tests/AdhocNew/Configure.OpenApi.cs @@ -1,5 +1,4 @@ -using MyApp.Data; -using Scalar.AspNetCore; +using Scalar.AspNetCore; [assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] @@ -12,22 +11,26 @@ public void Configure(IWebHostBuilder builder) => builder if (context.HostingEnvironment.IsDevelopment()) { services.AddOpenApi(); - services.AddServiceStackOpenApi(configure: metadata => - { - metadata.AddBasicAuth(); - //metadata.AddJwtBearer(); - }); - } - }) - .Configure((context, app) => { - if (context.HostingEnvironment.IsDevelopment()) - { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapOpenApi(); - endpoints.MapScalarApiReference(); - }); + services.AddServiceStackOpenApi(); + services.AddBasicAuth(); + // services.AddJwtAuth(); + services.AddApiKeys(); + + services.AddTransient(); } }); + + public class StartupFilter : IStartupFilter + { + public Action Configure(Action next) => app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapOpenApi(); + endpoints.MapScalarApiReference(); + }); + next(app); + }; + } } \ No newline at end of file From 72eaff59b43335f90bfd6310440ebae0fcf7c10e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 01:33:48 +0800 Subject: [PATCH 061/140] Restore ServiceStack using --- ServiceStack/tests/OpenApiScalar/Configure.Auth.cs | 1 + ServiceStack/tests/OpenApiScalar/Configure.Db.cs | 1 + ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs | 2 +- ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj | 1 - ServiceStack/tests/OpenApiScalar/Program.cs | 1 + 5 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs b/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs index 96d502653e1..630c77f5bd2 100644 --- a/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs +++ b/ServiceStack/tests/OpenApiScalar/Configure.Auth.cs @@ -1,3 +1,4 @@ +using ServiceStack; using ServiceStack.Auth; using MyApp.Data; diff --git a/ServiceStack/tests/OpenApiScalar/Configure.Db.cs b/ServiceStack/tests/OpenApiScalar/Configure.Db.cs index 5295c35ca2e..e37b4fb79c0 100644 --- a/ServiceStack/tests/OpenApiScalar/Configure.Db.cs +++ b/ServiceStack/tests/OpenApiScalar/Configure.Db.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using ServiceStack; using ServiceStack.Data; using ServiceStack.OrmLite; using MyApp.Data; diff --git a/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs b/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs index 69bfdd77f02..66e460f0a23 100644 --- a/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs +++ b/ServiceStack/tests/OpenApiScalar/Configure.OpenApi.cs @@ -1,4 +1,5 @@ using Scalar.AspNetCore; +using ServiceStack; [assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] @@ -34,4 +35,3 @@ public Action Configure(Action next) = }; } } - diff --git a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj index d1bf888ff00..c5759fca8da 100644 --- a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj +++ b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj @@ -9,7 +9,6 @@ - diff --git a/ServiceStack/tests/OpenApiScalar/Program.cs b/ServiceStack/tests/OpenApiScalar/Program.cs index 29b38562031..4a799bb518d 100644 --- a/ServiceStack/tests/OpenApiScalar/Program.cs +++ b/ServiceStack/tests/OpenApiScalar/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; +using ServiceStack; using MyApp.Data; using MyApp.ServiceInterface; From fcd1c898ca31ae79f0c88438ff23611d3606925d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 01:34:02 +0800 Subject: [PATCH 062/140] Restore ServiceStack using --- ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs | 1 + ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs | 3 ++- ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs | 3 ++- .../tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj | 1 - ServiceStack/tests/OpenApi3Swashbuckle/Program.cs | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs index 96d502653e1..630c77f5bd2 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Auth.cs @@ -1,3 +1,4 @@ +using ServiceStack; using ServiceStack.Auth; using MyApp.Data; diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs index 5295c35ca2e..e2408b73f71 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.Db.cs @@ -1,8 +1,9 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using ServiceStack; using ServiceStack.Data; using ServiceStack.OrmLite; using MyApp.Data; -using Microsoft.EntityFrameworkCore.Diagnostics; [assembly: HostingStartup(typeof(MyApp.ConfigureDb))] diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs index 75aac656151..e27cf2c729a 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Configure.OpenApi.cs @@ -1,3 +1,5 @@ +using ServiceStack; + [assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))] namespace MyApp; @@ -29,4 +31,3 @@ public Action Configure(Action next) = }; } } - diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj index 596d4425b53..9fc840d634e 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj +++ b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj @@ -9,7 +9,6 @@ - diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs index d2611d45760..87f699e1b6b 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs +++ b/ServiceStack/tests/OpenApi3Swashbuckle/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; +using ServiceStack; using MyApp.Data; using MyApp.ServiceInterface; From 6803aab09371a5ab4ab425c855295c96cf14dd20 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 01:34:32 +0800 Subject: [PATCH 063/140] Downgrade to Microsoft.OpenApi v2.3.0 which is what Swashbuckle.AspNetCore depends on --- .../src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs | 6 +++--- .../ServiceStack.AspNetCore.OpenApi3.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs index 77238a5c906..6924c1019fc 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs @@ -215,7 +215,7 @@ public OpenApiOperation AddOperation(OpenApiOperation op, Operation operation, s var requestBody = new OpenApiRequestBody { }; - var content = requestBody.Content ??= new OrderedDictionary(); + var content = requestBody.Content ??= new Dictionary(); content[MimeTypes.MultiPartFormData] = formType; if (apiAttr?.BodyParameter != GenerateBodyParameter.Never) { @@ -1054,7 +1054,7 @@ internal OrderedDictionary GetMethodResponseCodes(IRest { Description = !string.IsNullOrEmpty(schemaDescription) ? schemaDescription : "Success" }; - var content = response.Content ??= new OrderedDictionary(); + var content = response.Content ??= new Dictionary(); content[MimeTypes.Json] = new OpenApiMediaType { Schema = responseSchema, @@ -1069,7 +1069,7 @@ internal OrderedDictionary GetMethodResponseCodes(IRest { Description = attr.Description ?? apiSchemaDescription }; - var apiContent = apiResponse.Content ??= new OrderedDictionary(); + var apiContent = apiResponse.Content ??= new Dictionary(); apiContent[MimeTypes.Json] = new OpenApiMediaType { Schema = attr.ResponseType != null diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj index 9c3b14b9279..1e426666f5a 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj @@ -8,7 +8,7 @@ - + From eac3339d65de108ba4562eb303ba5a359d6f4051 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 12:52:51 +0800 Subject: [PATCH 064/140] Update ServiceStack.Mvc.csproj --- ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj index 6757a2007e8..468be815547 100644 --- a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj +++ b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj @@ -2,7 +2,7 @@ ServiceStack.Mvc ServiceStack.Mvc - net472;net6.0;net8.0 + net472;net6.0;net8.0;net10.0 MVC Adapters for integrating with ServiceStack webservices MVC Adapter classes to provide tight integration and re-usable functionality between ServiceStack and MVC. From 899eee16f3b9b6d8440c175073d68716de4bdc22 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 12:57:07 +0800 Subject: [PATCH 065/140] Remove NETSTANDARD2_0 from non Client csproj's --- .../src/ServiceStack.Aws/ServiceStack.Aws.csproj | 3 --- .../ServiceStack.Azure/ServiceStack.Azure.csproj | 3 --- .../ServiceStack.OrmLite.PostgreSQL.csproj | 3 --- .../ServiceStack.OrmLite.SqlServer.Data.csproj | 3 --- .../ServiceStack.OrmLite.Sqlite.Data.csproj | 3 --- .../ServiceStack.OrmLite.Sqlite.csproj | 3 --- .../ServiceStack.OrmLite.csproj | 13 ------------- .../ServiceStack.Redis/ServiceStack.Redis.csproj | 3 --- .../ServiceStack.Common/ServiceStack.Common.csproj | 3 --- .../ServiceStack.GoogleCloud.csproj | 3 --- .../ServiceStack.Kestrel.csproj | 3 --- .../src/ServiceStack.Mvc/ServiceStack.Mvc.csproj | 3 --- .../ServiceStack.Server/ServiceStack.Server.csproj | 3 --- 13 files changed, 49 deletions(-) diff --git a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj b/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj index facff95ec52..a97e51b4eb6 100644 --- a/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj +++ b/ServiceStack.Aws/src/ServiceStack.Aws/ServiceStack.Aws.csproj @@ -17,9 +17,6 @@ ServiceStack;AWS;Amazon;WebServices;DynamoDb;S3;SQS;Cache;CacheClient - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj b/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj index d7200afd96b..8cd479cf25b 100644 --- a/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj +++ b/ServiceStack.Azure/src/ServiceStack.Azure/ServiceStack.Azure.csproj @@ -13,9 +13,6 @@ Azure;Windows;ServiceBus;Blob;Table;Storage;WebServices;Cache;CacheClient - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj index d652b04cf6c..7da2dc6be4d 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.PostgreSQL/ServiceStack.OrmLite.PostgreSQL.csproj @@ -23,9 +23,6 @@ - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj index 371c974a390..fa38a1f485e 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.SqlServer.Data/ServiceStack.OrmLite.SqlServer.Data.csproj @@ -23,9 +23,6 @@ - - - diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj index fa9765fa2e9..ba554d0f459 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite.Data/ServiceStack.OrmLite.Sqlite.Data.csproj @@ -12,9 +12,6 @@ SQLite;OrmLite;RDBMS;SQL;POCO;Code-First;ORM;Schema-less;Blobs $(DefineConstants);ASYNC - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0 diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj index 65ed6eb8717..5dc50577971 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite.Sqlite/ServiceStack.OrmLite.Sqlite.csproj @@ -15,9 +15,6 @@ $(DefineConstants);NETFX;NET45;NET472 - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj index 5f018be90ca..f495c807beb 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/ServiceStack.OrmLite.csproj @@ -16,9 +16,6 @@ $(DefineConstants);NETFX;NET45;NET472 - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER @@ -43,16 +40,6 @@ - - - - - - - - - - diff --git a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj b/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj index 8fa98f15846..07af7dad303 100644 --- a/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj +++ b/ServiceStack.Redis/src/ServiceStack.Redis/ServiceStack.Redis.csproj @@ -15,9 +15,6 @@ $(DefineConstants);NET472 - - $(DefineConstants);NETCORE - $(DefineConstants);NETCORE;NET6_0 diff --git a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj index d7a3e9dccc0..ece8ef5daf6 100644 --- a/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj +++ b/ServiceStack/src/ServiceStack.Common/ServiceStack.Common.csproj @@ -8,9 +8,6 @@ #Script, Virtual File System, SimpleContainer and Common library for ServiceStack projects. ServiceStack;Common;Framework;Clients;ServiceClients;Gateway - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj b/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj index 5aa317e9197..713778173ac 100644 --- a/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj +++ b/ServiceStack/src/ServiceStack.GoogleCloud/ServiceStack.GoogleCloud.csproj @@ -11,9 +11,6 @@ ServiceStack;AWS;Amazon;WebServices;DynamoDb;S3;SQS;Cache;CacheClient - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj b/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj index 9b6a9b71bec..bb8e6d6616c 100644 --- a/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj +++ b/ServiceStack/src/ServiceStack.Kestrel/ServiceStack.Kestrel.csproj @@ -12,9 +12,6 @@ ServiceStack;SelfHost;Host;Kestrel;HTTP;Server - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj index 468be815547..9600335a595 100644 --- a/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj +++ b/ServiceStack/src/ServiceStack.Mvc/ServiceStack.Mvc.csproj @@ -13,9 +13,6 @@ $(DefineConstants);NET472 - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER diff --git a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj b/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj index b080b774516..f04b695e7df 100644 --- a/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj +++ b/ServiceStack/src/ServiceStack.Server/ServiceStack.Server.csproj @@ -23,9 +23,6 @@ $(DefineConstants);NET472 - - $(DefineConstants);NETCORE;NETSTANDARD2_0 - $(DefineConstants);NETCORE;NET6_0_OR_GREATER From 601e95d8d1154f656c66caa8b3092d3ab19703ef Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 13:04:54 +0800 Subject: [PATCH 066/140] Replace Request.Items access with SetItem/SetTrue/IsSet --- .../src/ServiceStack.Mvc/RazorFormat.cs | 4 +- .../AutoQueryFeature.AutoCrud.cs | 4 +- .../ServiceStack/Auth/ApiKeyAuthProvider.cs | 6 +-- .../Auth/AuthProviderExtensions.cs | 4 +- .../ServiceStack/Auth/AuthenticateService.cs | 2 +- .../Auth/JwtAuthProviderReader.cs | 4 +- .../Auth/NetCoreIdentityAuthProvider.cs | 2 +- .../src/ServiceStack/AuthenticateAttribute.cs | 14 +++--- .../src/ServiceStack/BlazorServerUtils.cs | 2 +- .../ServiceStack/CacheResponseAttribute.cs | 2 +- .../CancellableRequestsFeature.cs | 3 +- .../ClientCanSwapTemplatesAttribute.cs | 4 +- .../ServiceStack/ConnectionInfoAttribute.cs | 4 +- .../ServiceStack/EncryptedMessagesFeature.cs | 12 ++--- .../src/ServiceStack/Formats/HtmlFormat.cs | 2 +- .../ServiceStack/Host/ServiceController.cs | 6 +-- .../src/ServiceStack/Host/ServiceRunner.cs | 4 +- ServiceStack/src/ServiceStack/HostContext.cs | 2 +- .../src/ServiceStack/HttpCacheFeature.cs | 4 +- .../src/ServiceStack/HttpExtensions.cs | 4 +- .../src/ServiceStack/HttpRequestExtensions.cs | 6 +-- .../HttpResponseExtensionsInternal.cs | 14 +++--- .../src/ServiceStack/Jobs/JobUtils.cs | 5 ++- .../src/ServiceStack/RequestExtensions.cs | 44 ++++++++++++++++--- .../src/ServiceStack/RunAsAdminFeature.cs | 2 +- .../src/ServiceStack/ServiceExtensions.cs | 8 ++-- .../ServiceStack/ServiceStackHost.Runtime.cs | 12 ++--- .../src/ServiceStack/ServiceStackHost.cs | 12 ++--- .../src/ServiceStack/ServiceStackScripts.cs | 7 ++- .../src/ServiceStack/SessionExtensions.cs | 8 ++-- .../src/ServiceStack/SharpPagesFeature.cs | 2 +- .../src/ServiceStack/TypeValidators.cs | 2 +- .../Validation/ValidationFeature.cs | 2 +- .../Validation/ValidationFilters.cs | 10 ++--- .../src/ServiceStack/WebSudoFeature.cs | 10 ++--- .../AuthTests.cs | 2 +- .../UseCases/BasicEncryptedMessagesTests.cs | 2 +- 37 files changed, 137 insertions(+), 100 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Mvc/RazorFormat.cs b/ServiceStack/src/ServiceStack.Mvc/RazorFormat.cs index a35af71e057..bc4f9085a1f 100644 --- a/ServiceStack/src/ServiceStack.Mvc/RazorFormat.cs +++ b/ServiceStack/src/ServiceStack.Mvc/RazorFormat.cs @@ -292,13 +292,13 @@ public async Task ProcessRequestAsync(IRequest req, object dto, Stream out if (explicitView == null && (req.Dto == null || dto == null)) return false; - if (req.Items.ContainsKey(RenderException)) + if (req.IsSet(RenderException)) return false; var errorStatus = dto.GetResponseStatus() ?? (dto is Exception ex ? ex.ToResponseStatus() : null); if (errorStatus?.ErrorCode != null) - req.Items[Keywords.ErrorStatus] = errorStatus; + req.SetItem(Keywords.ErrorStatus, errorStatus); var viewNames = new List(); if (explicitView != null) diff --git a/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.AutoCrud.cs b/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.AutoCrud.cs index 7b55bd2a919..1fd636448ea 100644 --- a/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.AutoCrud.cs +++ b/ServiceStack/src/ServiceStack.Server/AutoQueryFeature.AutoCrud.cs @@ -897,7 +897,7 @@ internal struct ExecValue(object? id, long? rowsUpdated) private object? ExecAndReturnResponse(CrudContext context, Func fn) { - var ignoreEvent = context.Request.Items.ContainsKey(Keywords.IgnoreEvent); + var ignoreEvent = context.Request.IsSet(Keywords.IgnoreEvent); var trans = context.Events != null && !ignoreEvent && !context.Db.InTransaction() ? context.Db.OpenTransaction() : null; @@ -958,7 +958,7 @@ internal struct ExecValue(object? id, long? rowsUpdated) private async Task ExecAndReturnResponseAsync
(CrudContext context, Func> fn) { - var ignoreEvent = context.Request.Items.ContainsKey(Keywords.IgnoreEvent); + var ignoreEvent = context.Request.IsSet(Keywords.IgnoreEvent); var trans = context.Events != null && !ignoreEvent && !context.Db.InTransaction() ? context.Db.OpenTransaction() : null; diff --git a/ServiceStack/src/ServiceStack/Auth/ApiKeyAuthProvider.cs b/ServiceStack/src/ServiceStack/Auth/ApiKeyAuthProvider.cs index 6ac45296deb..2718e10dafe 100644 --- a/ServiceStack/src/ServiceStack/Auth/ApiKeyAuthProvider.cs +++ b/ServiceStack/src/ServiceStack/Auth/ApiKeyAuthProvider.cs @@ -246,7 +246,7 @@ public override async Task AuthenticateAsync(IServiceBase authService, I if (response != null) return response; - authService.Request.Items[Keywords.ApiKey] = apiKey; + authService.Request.SetItem(Keywords.ApiKey, apiKey); return new AuthenticateResponse { @@ -321,7 +321,7 @@ public virtual async Task PreAuthenticateWithApiKeyAsync(IRequest req, IResponse var apiSessionKey = GetSessionKey(apiKey.Id); if (await HasCachedSessionAsync(req, apiSessionKey).ConfigAwait()) { - req.Items[Keywords.ApiKey] = apiKey; + req.SetItem(Keywords.ApiKey, apiKey); return; } @@ -350,7 +350,7 @@ public virtual async Task HasCachedSessionAsync(IRequest req, string apiSe if (session != null) { - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); return true; } } diff --git a/ServiceStack/src/ServiceStack/Auth/AuthProviderExtensions.cs b/ServiceStack/src/ServiceStack/Auth/AuthProviderExtensions.cs index 9c27bfaabc5..28a0cbed0b5 100644 --- a/ServiceStack/src/ServiceStack/Auth/AuthProviderExtensions.cs +++ b/ServiceStack/src/ServiceStack/Auth/AuthProviderExtensions.cs @@ -66,7 +66,7 @@ public static void SaveSession(this IAuthProvider provider, IServiceBase authSer } else { - authService.Request.Items[Keywords.Session] = session; + authService.Request.SetItem(Keywords.Session, session); } } @@ -79,7 +79,7 @@ public static async Task SaveSessionAsync(this IAuthProvider provider, IServiceB } else { - authService.Request.Items[Keywords.Session] = session; + authService.Request.SetItem(Keywords.Session, session); } } diff --git a/ServiceStack/src/ServiceStack/Auth/AuthenticateService.cs b/ServiceStack/src/ServiceStack/Auth/AuthenticateService.cs index d718b7be16f..bbc2366c4ca 100644 --- a/ServiceStack/src/ServiceStack/Auth/AuthenticateService.cs +++ b/ServiceStack/src/ServiceStack/Auth/AuthenticateService.cs @@ -373,7 +373,7 @@ public async Task PostAsync(Authenticate request) ReferrerUrl = referrerUrl, Session = session, AlreadyAuthenticated = alreadyAuthenticated, - DidAuthenticate = Request.Items.ContainsKey(Keywords.DidAuthenticate), + DidAuthenticate = Request.IsSet(Keywords.DidAuthenticate), }; foreach (var responseFilter in AuthResponseFilters) diff --git a/ServiceStack/src/ServiceStack/Auth/JwtAuthProviderReader.cs b/ServiceStack/src/ServiceStack/Auth/JwtAuthProviderReader.cs index abed83400b9..2f89ee78aa8 100644 --- a/ServiceStack/src/ServiceStack/Auth/JwtAuthProviderReader.cs +++ b/ServiceStack/src/ServiceStack/Auth/JwtAuthProviderReader.cs @@ -584,7 +584,7 @@ protected virtual async Task AuthenticateBearerTokenAsync(IRequest req, IR } var session = await CreateSessionFromPayloadAsync(req, jwtPayload); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); return true; } @@ -604,7 +604,7 @@ protected virtual async Task AuthenticateBearerTokenAsync(IRequest req, IR } var session = await CreateSessionFromPayloadAsync(req, jwtPayload); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); return true; } } diff --git a/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs b/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs index 3237b2638df..c8d62b8a31d 100644 --- a/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs +++ b/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs @@ -127,7 +127,7 @@ public virtual async Task PreAuthenticateAsync(IRequest req, IResponse res) return; session = await ConvertPrincipalToSessionAsync(req, claimsPrincipal).ConfigAwait(); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); } public async Task ConvertPrincipalToSessionAsync(IRequest req, ClaimsPrincipal claimsPrincipal, CancellationToken token=default) diff --git a/ServiceStack/src/ServiceStack/AuthenticateAttribute.cs b/ServiceStack/src/ServiceStack/AuthenticateAttribute.cs index 4cec2692650..6c6d3ef532c 100644 --- a/ServiceStack/src/ServiceStack/AuthenticateAttribute.cs +++ b/ServiceStack/src/ServiceStack/AuthenticateAttribute.cs @@ -111,10 +111,10 @@ public static bool Authenticate(IRequest req, object requestDto=null, IAuthSessi req.PopulateFromRequestIfHasSessionId(requestDto); - if (!req.Items.ContainsKey(Keywords.HasPreAuthenticated)) + if (!req.IsSet(Keywords.HasPreAuthenticated)) { var mockResponse = new BasicRequest().Response; - req.Items[Keywords.HasPreAuthenticated] = true; + req.SetTrue(Keywords.HasPreAuthenticated); foreach (var authWithRequest in authProviders.OfType()) { authWithRequest.PreAuthenticateAsync(req, mockResponse).Wait(); @@ -148,11 +148,11 @@ public static async Task AuthenticateAsync(IRequest req, object requestDto req.PopulateFromRequestIfHasSessionId(requestDto); - if (!req.Items.ContainsKey(Keywords.HasPreAuthenticated)) + if (!req.IsSet(Keywords.HasPreAuthenticated)) { //Unauthorized or invalid requests will terminate the response and return false var mockResponse = new BasicRequest().Response; - req.Items[Keywords.HasPreAuthenticated] = true; + req.SetTrue(Keywords.HasPreAuthenticated); foreach (var authWithRequest in authProviders.OfType()) { await authWithRequest.PreAuthenticateAsync(req, mockResponse).ConfigAwait(); @@ -210,9 +210,9 @@ internal static async Task PreAuthenticateAsync(IRequest req, IEnumerable()) { await authWithRequest.PreAuthenticateAsync(req, req.Response).ConfigAwait(); @@ -232,7 +232,7 @@ protected virtual Task HandleShortCircuitedErrors(IRequest req, IResponse res, o HttpStatusCode statusCode, string statusDescription=null) { if (HtmlRedirect != null) - req.Items[nameof(AuthFeature.HtmlRedirect)] = HtmlRedirect; + req.SetItem(nameof(AuthFeature.HtmlRedirect), HtmlRedirect); return HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto, statusCode, statusDescription); } diff --git a/ServiceStack/src/ServiceStack/BlazorServerUtils.cs b/ServiceStack/src/ServiceStack/BlazorServerUtils.cs index adcf36e6de4..39adf3f8238 100644 --- a/ServiceStack/src/ServiceStack/BlazorServerUtils.cs +++ b/ServiceStack/src/ServiceStack/BlazorServerUtils.cs @@ -118,7 +118,7 @@ public static GatewayRequest ToGatewayRequest(this HostState hostState) RawUrl = hostState.AbsoluteUri, PathInfo = "/", }); - to.Items[Keywords.Session] = hostState.Session.FromAuthUserSession(); + to.SetItem(Keywords.Session, hostState.Session.FromAuthUserSession()); foreach (var cookie in hostState.Cookies) { to.Cookies.Add(new(cookie.Name, cookie.ToCookie())); diff --git a/ServiceStack/src/ServiceStack/CacheResponseAttribute.cs b/ServiceStack/src/ServiceStack/CacheResponseAttribute.cs index 6e39328f2af..c4868644391 100644 --- a/ServiceStack/src/ServiceStack/CacheResponseAttribute.cs +++ b/ServiceStack/src/ServiceStack/CacheResponseAttribute.cs @@ -128,7 +128,7 @@ public override async Task ExecuteAsync(IRequest req, IResponse res, object requ if (await req.HandleValidCache(cacheInfo).ConfigAwait()) return; - req.Items[Keywords.CacheInfo] = cacheInfo; + req.SetItem(Keywords.CacheInfo, cacheInfo); } } diff --git a/ServiceStack/src/ServiceStack/CancellableRequestsFeature.cs b/ServiceStack/src/ServiceStack/CancellableRequestsFeature.cs index b0e531860da..6a2bae4f54f 100644 --- a/ServiceStack/src/ServiceStack/CancellableRequestsFeature.cs +++ b/ServiceStack/src/ServiceStack/CancellableRequestsFeature.cs @@ -78,7 +78,8 @@ public CancellableRequest(CancellableRequestsFeature feature, IRequest req, stri this.feature.UnregisterCancellableRequest(this.requestTag); - req.Items[nameof(CancellableRequest)] = feature.RequestsMap[tag] = this; + feature.RequestsMap[tag] = this; + req.SetItem(nameof(CancellableRequest), this); } public TimeSpan Elapsed => stopwatch.Elapsed; diff --git a/ServiceStack/src/ServiceStack/ClientCanSwapTemplatesAttribute.cs b/ServiceStack/src/ServiceStack/ClientCanSwapTemplatesAttribute.cs index f76f7f2dfe8..ab6e8cf6259 100644 --- a/ServiceStack/src/ServiceStack/ClientCanSwapTemplatesAttribute.cs +++ b/ServiceStack/src/ServiceStack/ClientCanSwapTemplatesAttribute.cs @@ -8,7 +8,7 @@ public class ClientCanSwapTemplatesAttribute : RequestFilterAttribute { public override void Execute(IRequest req, IResponse res, object requestDto) { - req.Items[Keywords.View] = req.GetParam(Keywords.View); - req.Items[Keywords.Template] = req.GetParam(Keywords.Template); + req.SetItem(Keywords.View, req.GetParam(Keywords.View)); + req.SetItem(Keywords.Template, req.GetParam(Keywords.Template)); } } \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack/ConnectionInfoAttribute.cs b/ServiceStack/src/ServiceStack/ConnectionInfoAttribute.cs index 2d129456a83..aa44b444091 100644 --- a/ServiceStack/src/ServiceStack/ConnectionInfoAttribute.cs +++ b/ServiceStack/src/ServiceStack/ConnectionInfoAttribute.cs @@ -10,11 +10,11 @@ public class ConnectionInfoAttribute : RequestFilterAttribute public override void Execute(IRequest req, IResponse res, object requestDto) { - req.Items[Keywords.DbInfo] = new ConnectionInfo + req.SetItem(Keywords.DbInfo, new ConnectionInfo { ConnectionString = ConnectionString, NamedConnection = NamedConnection, ProviderName = ProviderName, - }; + }); } } \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack/EncryptedMessagesFeature.cs b/ServiceStack/src/ServiceStack/EncryptedMessagesFeature.cs index da4613d1cd5..d9dd6e81098 100644 --- a/ServiceStack/src/ServiceStack/EncryptedMessagesFeature.cs +++ b/ServiceStack/src/ServiceStack/EncryptedMessagesFeature.cs @@ -145,7 +145,7 @@ public void Register(IAppHost appHost) .Each(entry => nonceCache.TryRemove(entry.Key, out _)); parts = parts[1].SplitOnFirst(' '); - req.Items[Keywords.InvokeVerb] = parts[0]; + req.SetItem(Keywords.InvokeVerb, parts[0]); parts = parts[1].SplitOnFirst(' '); var operationName = parts[0]; @@ -159,9 +159,9 @@ public void Register(IAppHost appHost) req.RequestAttributes |= RequestAttributes.Secure; req.RequestAttributes &= ~RequestAttributes.InSecure; - req.Items[RequestItemsCryptKey] = cryptKey; - req.Items[RequestItemsAuthKey] = authKey; - req.Items[RequestItemsIv] = iv; + req.SetItem(RequestItemsCryptKey, cryptKey); + req.SetItem(RequestItemsAuthKey, authKey); + req.SetItem(RequestItemsIv, iv); return request; } @@ -262,7 +262,7 @@ public static class EncryptedMessagesFeatureExtensions { public static bool IsEncryptedMessage(this IRequest req) { - return req.Items.ContainsKey(EncryptedMessagesFeature.RequestItemsCryptKey) - && req.Items.ContainsKey(EncryptedMessagesFeature.RequestItemsIv); + return req.IsSet(EncryptedMessagesFeature.RequestItemsCryptKey) + && req.IsSet(EncryptedMessagesFeature.RequestItemsIv); } } \ No newline at end of file diff --git a/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs b/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs index 12cdea7bf55..fe8f67120ab 100644 --- a/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs +++ b/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs @@ -69,7 +69,7 @@ public async Task SerializeToStreamAsync(IRequest req, object response, Stream o if (res.StatusCode >= 400) { var responseStatus = response.GetResponseStatus(); - req.Items[Keywords.ErrorStatus] = responseStatus; + req.SetItem(Keywords.ErrorStatus, responseStatus); } if (response is CompressedResult) diff --git a/ServiceStack/src/ServiceStack/Host/ServiceController.cs b/ServiceStack/src/ServiceStack/Host/ServiceController.cs index 6cabfdb93c8..f386d3f7709 100644 --- a/ServiceStack/src/ServiceStack/Host/ServiceController.cs +++ b/ServiceStack/src/ServiceStack/Host/ServiceController.cs @@ -870,7 +870,7 @@ private static ServiceExecFn CreateAutoBatchServiceExec(ServiceExecFn handlerFnA var firstDto = dtosList[0]; - req.Items[Keywords.AutoBatchIndex] = 0; + req.SetItem(Keywords.AutoBatchIndex, 0); var firstResponse = await handlerFnAsync(req, firstDto).ConfigAwait(); if (firstResponse is Exception) @@ -890,7 +890,7 @@ private static ServiceExecFn CreateAutoBatchServiceExec(ServiceExecFn handlerFnA for (var i = 1; i < dtosList.Count; i++) { var dto = dtosList[i]; - req.Items[Keywords.AutoBatchIndex] = i; + req.SetItem(Keywords.AutoBatchIndex, i); var response = await handlerFnAsync(req, dto).ConfigAwait(); //short-circuit on first error if (response is Exception) @@ -914,7 +914,7 @@ private static ServiceExecFn CreateAutoBatchServiceExec(ServiceExecFn handlerFnA { try { - req.Items[Keywords.AutoBatchIndex] = i; + req.SetItem(Keywords.AutoBatchIndex, i); var dto = dtosList[i]; var task = handlerFnAsync(req, dto); diff --git a/ServiceStack/src/ServiceStack/Host/ServiceRunner.cs b/ServiceStack/src/ServiceStack/Host/ServiceRunner.cs index fd850301735..e9bffde5973 100644 --- a/ServiceStack/src/ServiceStack/Host/ServiceRunner.cs +++ b/ServiceStack/src/ServiceStack/Host/ServiceRunner.cs @@ -207,12 +207,12 @@ public virtual async Task ExecuteAsync(IRequest req, object instance, TR private void LogRequest(IRequest req, object requestDto, object response) { - if (req.IsInProcessRequest() || req.Items.ContainsKey(Keywords.HasLogged)) + if (req.IsInProcessRequest() || req.IsSet(Keywords.HasLogged)) return; try { - req.Items[Keywords.HasLogged] = true; + req.SetTrue(Keywords.HasLogged); var logDto = !req.IsMultiRequest() ? requestDto : req.Dto; if (logDto != null) { diff --git a/ServiceStack/src/ServiceStack/HostContext.cs b/ServiceStack/src/ServiceStack/HostContext.cs index c4bbf3c771a..339090af9f0 100644 --- a/ServiceStack/src/ServiceStack/HostContext.cs +++ b/ServiceStack/src/ServiceStack/HostContext.cs @@ -259,7 +259,7 @@ public static Task RaiseGatewayException(IRequest httpReq, object request, Excep public static async Task RaiseAndHandleException(IRequest httpReq, IResponse httpRes, string operationName, Exception ex) { - if (!httpReq.Items.ContainsKey(nameof(ServiceStackHost.OnServiceException))) + if (!httpReq.IsSet(nameof(ServiceStackHost.OnServiceException))) await AssertAppHost().OnUncaughtException(httpReq, httpRes, operationName, ex).ConfigAwait(); if (httpRes.IsClosed) diff --git a/ServiceStack/src/ServiceStack/HttpCacheFeature.cs b/ServiceStack/src/ServiceStack/HttpCacheFeature.cs index 9691c6ed408..3198adf0bd8 100644 --- a/ServiceStack/src/ServiceStack/HttpCacheFeature.cs +++ b/ServiceStack/src/ServiceStack/HttpCacheFeature.cs @@ -116,9 +116,9 @@ private async Task CacheAndWriteResponse(CacheInfo cacheInfo, IRequest req if (response is HttpResult customResult) { if (customResult.View != null) - req.Items[Keywords.View] = customResult.View; + req.SetItem(Keywords.View, customResult.View); if (customResult.Template != null) - req.Items[Keywords.Template] = customResult.Template; + req.SetItem(Keywords.Template, customResult.Template); } using (httpResult?.ResultScope?.Invoke()) diff --git a/ServiceStack/src/ServiceStack/HttpExtensions.cs b/ServiceStack/src/ServiceStack/HttpExtensions.cs index ad4475d8118..32d1647265d 100644 --- a/ServiceStack/src/ServiceStack/HttpExtensions.cs +++ b/ServiceStack/src/ServiceStack/HttpExtensions.cs @@ -99,7 +99,7 @@ public static void EndHttpHandlerRequest(this IResponse httpRes, bool skipHeader afterHeaders?.Invoke(httpRes); var req = httpRes.Request; - if (req != null && !req.Items.ContainsKey(Keywords.HasLogged)) + if (!req.IsSet(Keywords.HasLogged)) { HostContext.AppHost.OnLogRequest(req, req.Dto, httpRes.Dto, req.GetElapsed()); } @@ -128,7 +128,7 @@ public static async Task EndHttpHandlerRequestAsync(this IResponse httpRes, bool } var req = httpRes.Request; - if (req != null && !req.Items.ContainsKey(Keywords.HasLogged)) + if (!req.IsSet(Keywords.HasLogged)) { HostContext.AppHost.OnLogRequest(req, req.Dto, httpRes.Dto, req.GetElapsed()); } diff --git a/ServiceStack/src/ServiceStack/HttpRequestExtensions.cs b/ServiceStack/src/ServiceStack/HttpRequestExtensions.cs index 5e782a0ad61..5385c5a9acf 100644 --- a/ServiceStack/src/ServiceStack/HttpRequestExtensions.cs +++ b/ServiceStack/src/ServiceStack/HttpRequestExtensions.cs @@ -610,7 +610,7 @@ public static object ResolveItem(this IRequest httpReq, return cachedItem; var item = resolveFn(httpReq); - httpReq.Items[itemKey] = item; + httpReq.SetItem(itemKey, item); return item; } @@ -1007,7 +1007,7 @@ public static void SetAutoBatchCompletedHeader(this IRequest req, int completed) public static void SetRoute(this IRequest req, RestPath route) { - req.Items[Keywords.Route] = route; + req.SetItem(Keywords.Route, route); } public static RestPath GetRoute(this IRequest req) @@ -1053,7 +1053,7 @@ public static void EachRequest(this IRequest httpReq, Action action) requests.Each((i, dto) => { - httpReq.Items[Keywords.AutoBatchIndex] = i; + httpReq.SetItem(Keywords.AutoBatchIndex, i); action(dto); }); diff --git a/ServiceStack/src/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/src/ServiceStack/HttpResponseExtensionsInternal.cs index 7bbbc8c69fa..dae9b2f0d1c 100644 --- a/ServiceStack/src/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/src/ServiceStack/HttpResponseExtensionsInternal.cs @@ -179,7 +179,9 @@ public static Task WriteToResponse(this IResponse httpRes, object result, if (result is Exception) { if (response.Request.Items.TryGetValue(Keywords.ErrorView, out var oErrorView)) - response.Request.Items[Keywords.View] = oErrorView; + { + response.Request.SetItem(Keywords.View, oErrorView); + } } var httpResult = result as IHttpResult; @@ -574,11 +576,11 @@ private static async Task HandleCustomErrorHandler(this IResponse httpRes, ?? HostContext.AppHost.GlobalHtmlErrorHttpHandler; if (errorHandler != null) { - httpReq.Items[Keywords.Model] = errorDto; - httpReq.Items[Keywords.ErrorStatus] = errorDto.GetResponseStatus(); + httpReq.SetItem(Keywords.Model, errorDto); + httpReq.SetItem(Keywords.ErrorStatus, errorDto.GetResponseStatus()); if (ex != null) { - httpReq.Items[Keywords.Error] = ex; + httpReq.SetItem(Keywords.Error, ex); } await errorHandler.ProcessRequestAsync(httpReq, httpRes, httpReq.OperationName); return true; @@ -589,9 +591,9 @@ private static async Task HandleCustomErrorHandler(this IResponse httpRes, public static bool ShouldWriteGlobalHeaders(IResponse httpRes) { - if (!httpRes.HasStarted && HostContext.Config != null && !httpRes.Items.ContainsKey(Keywords.HasGlobalHeaders)) + if (!httpRes.HasStarted && HostContext.Config != null && !httpRes.IsSet(Keywords.HasGlobalHeaders)) { - httpRes.Items[Keywords.HasGlobalHeaders] = bool.TrueString; + httpRes.SetTrue(Keywords.HasGlobalHeaders); return true; } return false; diff --git a/ServiceStack/src/ServiceStack/Jobs/JobUtils.cs b/ServiceStack/src/ServiceStack/Jobs/JobUtils.cs index 29c65803750..2dcbbdbfed9 100644 --- a/ServiceStack/src/ServiceStack/Jobs/JobUtils.cs +++ b/ServiceStack/src/ServiceStack/Jobs/JobUtils.cs @@ -176,7 +176,7 @@ public static JobSummary ToJobSummary(this BackgroundJob from) }; } - public static void SetCancellationToken(this IRequest req, CancellationToken token) => req.Items[nameof(CancellationToken)] = token; + public static void SetCancellationToken(this IRequest req, CancellationToken token) => req.SetItem(nameof(CancellationToken), token); public static CancellationToken GetCancellationToken(this IRequest? req) => req?.Items.TryGetValue(nameof(CancellationToken), out var oToken) == true @@ -186,7 +186,8 @@ public static CancellationToken GetCancellationToken(this IRequest? req) => public static BackgroundJob GetBackgroundJob(this IRequest? req) => req.TryGetBackgroundJob() ?? throw new Exception("BackgroundJob not found"); - public static void SetBackgroundJob(this IRequest req, BackgroundJob job) => req.Items[nameof(BackgroundJob)] = job; + public static void SetBackgroundJob(this IRequest req, BackgroundJob job) => req.SetItem(nameof(BackgroundJob), job); + public static BackgroundJob? TryGetBackgroundJob(this IRequest? req) { return req?.Items.TryGetValue(nameof(BackgroundJob), out var oJob) == true diff --git a/ServiceStack/src/ServiceStack/RequestExtensions.cs b/ServiceStack/src/ServiceStack/RequestExtensions.cs index a99fbc367e0..5f56d890262 100644 --- a/ServiceStack/src/ServiceStack/RequestExtensions.cs +++ b/ServiceStack/src/ServiceStack/RequestExtensions.cs @@ -224,9 +224,43 @@ public static async Task ToOptimizedResultUsingCacheAsync( [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetItem(this IRequest httpReq, string key, object value) { - if (httpReq == null) return; + httpReq?.Items[key] = value; + } + + /// + /// Mark an entry in the IRequest.Items Dictionary as true + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetTrue(this IRequest httpReq, string key) + { + httpReq?.Items[key] = bool.TrueString; + } + + /// + /// Mark an entry in the IResponse.Items Dictionary as true + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetTrue(this IResponse httpRes, string key) + { + httpRes?.Items[key] = bool.TrueString; + } + + /// + /// Whether an entry exists in the IRequest.Items Dictionary + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSet(this IRequest httpReq, string key) + { + return httpReq != null && httpReq.Items.ContainsKey(key); + } - httpReq.Items[key] = value; + /// + /// Whether an entry exists in the IResponse.Items Dictionary + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSet(this IResponse httpRes, string key) + { + return httpRes != null && httpRes.Items.ContainsKey(key); } /// @@ -323,7 +357,7 @@ public static void RegisterForDispose(this IRequest request, IDisposable disposa { key = typeName + (++i); } - request.Items[key] = disposable; + request.SetItem(key, disposable); #endif } @@ -375,7 +409,7 @@ public static string GetTraceId(this IRequest req) if (req.Items.TryGetValue(Keywords.TraceId, out var traceId)) return (string)traceId; var newId = Guid.NewGuid().ToString(); - req.Items[Keywords.TraceId] = newId; + req.SetItem(Keywords.TraceId, newId); return newId; } @@ -394,7 +428,7 @@ public static bool AllowConnection(this IRequest req, bool requireSecureConnecti public static void CompletedAuthentication(this IRequest req) { - req.Items[Keywords.DidAuthenticate] = true; + req.SetTrue(Keywords.DidAuthenticate); } public static Dictionary GetRequestParams(this IRequest request) => diff --git a/ServiceStack/src/ServiceStack/RunAsAdminFeature.cs b/ServiceStack/src/ServiceStack/RunAsAdminFeature.cs index 74254f10c9b..dc230a5849c 100644 --- a/ServiceStack/src/ServiceStack/RunAsAdminFeature.cs +++ b/ServiceStack/src/ServiceStack/RunAsAdminFeature.cs @@ -19,7 +19,7 @@ public void Register(IAppHost appHost) ])); appHost.PreRequestFilters.Add((req, res) => { - req.Items[Keywords.Session] = appHost.GetPlugin().AuthSecretSession; + req.SetItem(Keywords.Session, appHost.GetPlugin().AuthSecretSession); }); if (RedirectTo != null) diff --git a/ServiceStack/src/ServiceStack/ServiceExtensions.cs b/ServiceStack/src/ServiceStack/ServiceExtensions.cs index 43064e88680..b96fd848c86 100644 --- a/ServiceStack/src/ServiceStack/ServiceExtensions.cs +++ b/ServiceStack/src/ServiceStack/ServiceExtensions.cs @@ -155,13 +155,13 @@ public static async Task RemoveSessionAsync(this IRequest httpReq, string sessio httpReq.Items.Remove(Keywords.Session); - if (httpReq.Items.ContainsKey(Keywords.HasClearedSession)) + if (httpReq.IsSet(Keywords.HasClearedSession)) return; // already cleared session var sessionKey = SessionFeature.GetSessionKey(sessionId); await httpReq.GetCacheClientAsync().RemoveAsync(sessionKey, token).ConfigAwait(); - httpReq.Items[Keywords.HasClearedSession] = true; // reset pre-authenticated state + httpReq.SetTrue(Keywords.HasClearedSession); // reset pre-authenticated state } public static IAuthSession GetSession(this IServiceBase service, bool reload = false) @@ -252,7 +252,7 @@ internal static async Task GetSessionInternalAsync(this IRequest h oSession = null; var appHost = HostContext.AppHost; - if (oSession == null && !httpReq.Items.ContainsKey(Keywords.HasPreAuthenticated)) + if (oSession == null && !httpReq.IsSet(Keywords.HasPreAuthenticated)) { try { @@ -298,7 +298,7 @@ internal static async Task GetSessionInternalAsync(this IRequest h session = appHost.OnSessionFilter(httpReq, newSession, sessionId) ?? newSession; } - httpReq.Items[Keywords.Session] = session; + httpReq.SetItem(Keywords.Session, session); return session; } diff --git a/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs b/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs index 7005a7d23cf..d132106cc6a 100644 --- a/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs +++ b/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs @@ -81,8 +81,8 @@ public bool ApplyCustomHandlerRequestFilters(IRequest httpReq, IResponse httpRes /// public virtual async Task ApplyPreAuthenticateFiltersAsync(IRequest httpReq, IResponse httpRes) { - httpReq.Items[Keywords.HasPreAuthenticated] = bool.TrueString; - + httpReq.SetTrue(Keywords.HasPreAuthenticated); + foreach (var authProvider in AuthenticateService.AuthWithRequestProviders.Safe()) { await authProvider.PreAuthenticateAsync(httpReq, httpRes).ConfigAwaitNetCore(); @@ -152,7 +152,7 @@ public async Task ApplyRequestFiltersAsync(IRequest req, IResponse res, object r foreach (var dto in dtos) { - req.Items[Keywords.AutoBatchIndex] = i; + req.SetItem(Keywords.AutoBatchIndex, i); await ApplyRequestFiltersSingleAsync(req, res, dto).ConfigAwaitNetCore(); HostContext.AppHost.OnAfterAwait(req); if (res.IsClosed) @@ -275,7 +275,7 @@ public async Task ApplyResponseFiltersAsync(IRequest req, IResponse res, object foreach (var dto in batchResponse) { - req.Items[Keywords.AutoBatchIndex] = i; + req.SetItem(Keywords.AutoBatchIndex, i); await ApplyResponseFiltersSingleAsync(req, res, dto).ConfigAwaitNetCore(); HostContext.AppHost.OnAfterAwait(req); @@ -895,7 +895,7 @@ public virtual void OnSaveSession(IRequest httpReq, IAuthSession session, TimeSp return; session.LastModified = DateTime.UtcNow; - httpReq.Items[Keywords.Session] = session; + httpReq.SetItem(Keywords.Session, session); if (!HasPlugin()) return; @@ -912,7 +912,7 @@ public virtual Task OnSaveSessionAsync(IRequest httpReq, IAuthSession session, T return TypeConstants.EmptyTask; session.LastModified = DateTime.UtcNow; - httpReq.Items[Keywords.Session] = session; + httpReq.SetItem(Keywords.Session, session); if (!HasPlugin()) return Task.CompletedTask; diff --git a/ServiceStack/src/ServiceStack/ServiceStackHost.cs b/ServiceStack/src/ServiceStack/ServiceStackHost.cs index c1b7c37806e..e7de66aad75 100644 --- a/ServiceStack/src/ServiceStack/ServiceStackHost.cs +++ b/ServiceStack/src/ServiceStack/ServiceStackHost.cs @@ -846,7 +846,7 @@ public virtual object EvalScriptValue(IScriptValue scriptValue, IRequest req = n var evalCodeValue = EvalScript(new PageResult(cachedCodePage), req, args); if (!scriptValue.NoCache && req != null) - req.Items[evalCacheKey] = evalCodeValue; + req.SetItem(evalCacheKey, evalCodeValue); return evalCodeValue; } @@ -873,7 +873,7 @@ public virtual async Task EvalScriptValueAsync(IScriptValue scriptValue, var evalCodeValue = await EvalScriptAsync(new PageResult(cachedCodePage), req, args).ConfigAwait(); if (!scriptValue.NoCache && req != null) - req.Items[evalCacheKey] = evalCodeValue; + req.SetItem(evalCacheKey, evalCodeValue); return evalCodeValue; } @@ -968,7 +968,7 @@ public virtual object OnPostExecuteServiceFilter(IService service, object respon /// public virtual async Task OnGatewayException(IRequest httpReq, object request, Exception ex) { - httpReq.Items[nameof(OnGatewayException)] = bool.TrueString; + httpReq.SetTrue(nameof(OnGatewayException)); foreach (var errorHandler in GatewayExceptionHandlers) { @@ -986,7 +986,7 @@ public virtual async Task OnGatewayException(IRequest httpReq, object request, E /// public virtual async Task OnServiceException(IRequest httpReq, object request, Exception ex) { - httpReq.Items[nameof(OnServiceException)] = bool.TrueString; + httpReq.SetTrue(nameof(OnServiceException)); object lastError = null; foreach (var errorHandler in ServiceExceptionHandlers) @@ -1462,10 +1462,10 @@ public virtual void OnEndRequest(IRequest request = null) { if (request != null) { - if (request.Items.ContainsKey(nameof(OnEndRequest))) + if (request.IsSet(nameof(OnEndRequest))) return; - request.Items[nameof(OnEndRequest)] = bool.TrueString; + request.SetTrue(nameof(OnEndRequest)); } var disposables = RequestContext.Instance.Items.Values; diff --git a/ServiceStack/src/ServiceStack/ServiceStackScripts.cs b/ServiceStack/src/ServiceStack/ServiceStackScripts.cs index d48aaec2cb5..2606eb5557c 100644 --- a/ServiceStack/src/ServiceStack/ServiceStackScripts.cs +++ b/ServiceStack/src/ServiceStack/ServiceStackScripts.cs @@ -809,11 +809,10 @@ public static HashSet GetUserAttributes(this IRequest request) return (HashSet)oAttrs; var authSession = request.GetSession(); - var attrs = authSession?.GetUserAttributes(request) ?? new HashSet { - HostContext.DebugMode ? When.Development : When.Production - }; + var attrs = authSession?.GetUserAttributes(request) ?? + [HostContext.DebugMode ? When.Development : When.Production]; - request.Items[Keywords.Attributes] = attrs; + request.SetItem(Keywords.Attributes, attrs); return attrs; } diff --git a/ServiceStack/src/ServiceStack/SessionExtensions.cs b/ServiceStack/src/ServiceStack/SessionExtensions.cs index 3099eaf6b23..d331a7f90f7 100644 --- a/ServiceStack/src/ServiceStack/SessionExtensions.cs +++ b/ServiceStack/src/ServiceStack/SessionExtensions.cs @@ -39,7 +39,7 @@ public static void SetSessionId(this IRequest req, string sessionId) ? SessionFeature.PermanentSessionId : SessionFeature.SessionId; - req.Items[sessionKey] = sessionId; + req.SetItem(sessionKey, sessionId); } public static string GetSessionId(this IRequest req) @@ -161,7 +161,7 @@ public static string CreateSessionId(this IRequest req, IResponse res, string se HostContext.Config.UseSecureCookies && req.IsSecureConnection); } - req.Items[sessionKey] = sessionId; + req.SetItem(sessionKey, sessionId); return sessionId; } public static string CreateTemporarySessionId(this IRequest req, string sessionId) => @@ -225,7 +225,7 @@ public static HashSet AddSessionOptions(this IRequest req, params string var httpRes = req.Response as IHttpResponse; httpRes?.Cookies.AddPermanentCookie(SessionFeature.SessionOptionsKey, strOptions); - req.Items[SessionFeature.SessionOptionsKey] = strOptions; + req.SetItem(SessionFeature.SessionOptionsKey, strOptions); return existingOptions; } @@ -384,7 +384,7 @@ public static async Task GenerateNewSessionCookiesAsync(this IRequest req, IAuth ? permId : tempId; - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); } public static AuthUserSession ToAuthUserSession(this IAuthSession session) diff --git a/ServiceStack/src/ServiceStack/SharpPagesFeature.cs b/ServiceStack/src/ServiceStack/SharpPagesFeature.cs index a27f9f37004..e8aae281403 100644 --- a/ServiceStack/src/ServiceStack/SharpPagesFeature.cs +++ b/ServiceStack/src/ServiceStack/SharpPagesFeature.cs @@ -471,7 +471,7 @@ public async Task ProcessRequestAsync(IRequest req, object dto, Stream out ? ex.ToResponseStatus() : null); if (errorStatus?.ErrorCode != null) - req.Items[Keywords.ErrorStatus] = errorStatus; + req.SetItem(Keywords.ErrorStatus, errorStatus); var viewNames = new List(); if (explicitView != null) diff --git a/ServiceStack/src/ServiceStack/TypeValidators.cs b/ServiceStack/src/ServiceStack/TypeValidators.cs index 5d317d19fac..128906c82dd 100644 --- a/ServiceStack/src/ServiceStack/TypeValidators.cs +++ b/ServiceStack/src/ServiceStack/TypeValidators.cs @@ -261,7 +261,7 @@ public override async Task IsValidAsync(object dto, IRequest request) if (!isValid) return false; - request.Items[Keywords.ApiKey] = apiKey; + request.SetItem(Keywords.ApiKey, apiKey); return true; } } diff --git a/ServiceStack/src/ServiceStack/Validation/ValidationFeature.cs b/ServiceStack/src/ServiceStack/Validation/ValidationFeature.cs index c582e194efe..fce5633e3ee 100644 --- a/ServiceStack/src/ServiceStack/Validation/ValidationFeature.cs +++ b/ServiceStack/src/ServiceStack/Validation/ValidationFeature.cs @@ -297,7 +297,7 @@ public virtual async Task ValidateRequestAsync(object requestDto, IRequest req, } var errorResponse = DtoUtils.CreateErrorResponse(requestDto, ver); - req.Items[Keywords.ResponseStatus] = errorResponse.GetResponseStatus(); + req.SetItem(Keywords.ResponseStatus, errorResponse.GetResponseStatus()); } public async Task AssertRequiredRole(IRequest request, string authSecret=null) diff --git a/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs b/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs index 42c565f9ebf..9e295cd9ee1 100644 --- a/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs +++ b/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs @@ -99,7 +99,7 @@ await HostContext.RaiseServiceException(req, requestDto, validationResult.ToExce // If the response is null, it means the request is sent from the InProcessServiceGateway if (res == null) { - req.Items[Keywords.ResponseStatus] = errorResponse.GetResponseStatus(); + req.SetItem(Keywords.ResponseStatus, errorResponse.GetResponseStatus()); } else { @@ -116,7 +116,7 @@ await HostContext.RaiseServiceException(req, requestDto, validationResult.ToExce // If the response is null, it means the request is sent from the InProcessServiceGateway if (res == null) { - req.Items[Keywords.ResponseStatus] = errorResponse.GetResponseStatus(); + req.SetItem(Keywords.ResponseStatus, errorResponse.GetResponseStatus()); } else { @@ -134,11 +134,11 @@ public static Task GatewayResponseFiltersAsync(IRequest req, object requestDto) public static async Task ResponseFilterAsync(IRequest req, IResponse res, object requestDto) { IHasResponseStatus response; - if (requestDto is IHasResponseStatus) + if (requestDto is IHasResponseStatus dto) { - response = (IHasResponseStatus)requestDto; + response = dto; } - else if (req.Items.ContainsKey(Keywords.ResponseStatus) && req.Items[Keywords.ResponseStatus] is IHasResponseStatus) + else if (req.IsSet(Keywords.ResponseStatus) && req.Items[Keywords.ResponseStatus] is IHasResponseStatus) { response = (IHasResponseStatus)req.Items[Keywords.ResponseStatus]; } diff --git a/ServiceStack/src/ServiceStack/WebSudoFeature.cs b/ServiceStack/src/ServiceStack/WebSudoFeature.cs index a5d4faf6f79..7b2bd5ba9c7 100644 --- a/ServiceStack/src/ServiceStack/WebSudoFeature.cs +++ b/ServiceStack/src/ServiceStack/WebSudoFeature.cs @@ -45,7 +45,7 @@ private async Task OnRequestStartAsync(IRequest request, IResponse response, obj { var copy = AuthenticateService.CurrentSessionFactory().PopulateWith(session); - request.Items[SessionCopyRequestItemKey] = copy; + request.SetItem(SessionCopyRequestItemKey, copy); // clear details to allow credentials to be rechecked, // otherwise IsAuthorized will just return, bypassing the auth provider's Authenticate method @@ -57,8 +57,8 @@ private async Task OnRequestStartAsync(IRequest request, IResponse response, obj private async Task OnRequestEndAsync(IRequest request, IResponse response, object dto) { - if (!request.Items.ContainsKey(SessionCopyRequestItemKey)) return; - if (!(request.Items[SessionCopyRequestItemKey] is IWebSudoAuthSession copy)) return; + if (!request.IsSet(SessionCopyRequestItemKey)) return; + if (request.Items[SessionCopyRequestItemKey] is not IWebSudoAuthSession copy) return; var session = await request.GetSessionAsync().ConfigAwait(); if (!session.IsAuthenticated) @@ -73,8 +73,8 @@ private async Task OnRequestEndAsync(IRequest request, IResponse response, objec public override void OnCreated(IRequest httpReq, IAuthSession session) { - if (!httpReq.Items.ContainsKey(SessionCopyRequestItemKey)) return; - if (!(httpReq.Items[SessionCopyRequestItemKey] is IWebSudoAuthSession copy)) return; + if (!httpReq.IsSet(SessionCopyRequestItemKey)) return; + if (httpReq.Items[SessionCopyRequestItemKey] is not IWebSudoAuthSession copy) return; var id = session.Id; var created = session.CreatedAt; diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs index be536e42cc7..61ab673a580 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AuthTests.cs @@ -278,7 +278,7 @@ public class CustomAuthAttrService : Service { public CustomAuthAttrResponse Any(CustomAuthAttr request) { - if (!Request.Items.ContainsKey("TriedMyOwnAuthFirst")) + if (!Request.IsSet("TriedMyOwnAuthFirst")) throw new InvalidOperationException("TriedMyOwnAuthFirst not present."); return new CustomAuthAttrResponse { Result = request.Name }; diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/UseCases/BasicEncryptedMessagesTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/UseCases/BasicEncryptedMessagesTests.cs index ae76319781b..c9b3bb3e6b5 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/UseCases/BasicEncryptedMessagesTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/UseCases/BasicEncryptedMessagesTests.cs @@ -30,7 +30,7 @@ public override void Configure(Container container) }); ResponseConverters.Add((req, response) => { - if (!req.Items.ContainsKey("_encrypt")) + if (!req.IsSet("_encrypt")) return TypeConstants.EmptyTask; var encResponse = RsaUtils.Encrypt(response.ToJson(), SecureConfig.PublicKeyXml); From 1d42a9ab5436afd919c8b3097de0434c50ce4eab Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 13:05:11 +0800 Subject: [PATCH 067/140] Add info about what's required to disable build error --- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 1 + ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index bbfa442c223..294ea32f1a0 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -7,6 +7,7 @@ MyApp aspnet-MyApp-7b2ab71a-0b50-423f-969d-e35a9402b1b5 true + $(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated diff --git a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj index c5759fca8da..2e7872c8342 100644 --- a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj +++ b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj @@ -40,7 +40,8 @@ - + + From f59116228fce0cac7297474b20bb56227959c9ae Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 13:47:20 +0800 Subject: [PATCH 068/140] Move remaining req.Items to req.SetItem --- .../Auth/AuthSecretAuthProvider.cs | 4 ++-- .../Auth/IdentityApplicationAuthProvider.cs | 2 +- .../Auth/IdentityAuthServiceGatewayFactory.cs | 2 +- .../Auth/IdentityJwtAuthProvider.cs | 2 +- ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs | 2 +- .../src/ServiceStack.Server/ApiKeyCredentialsProvider.cs | 8 ++++---- ServiceStack/src/ServiceStack.Server/CrudEvents.cs | 2 +- ServiceStack/src/ServiceStack.Server/DbJobs.cs | 2 +- ServiceStack/src/ServiceStack/AuthUserSession.cs | 6 +++--- .../CacheResponseTests.cs | 4 ++-- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/Auth/AuthSecretAuthProvider.cs b/ServiceStack/src/ServiceStack.Extensions/Auth/AuthSecretAuthProvider.cs index 2b0c0a18505..8fa33bc33cb 100644 --- a/ServiceStack/src/ServiceStack.Extensions/Auth/AuthSecretAuthProvider.cs +++ b/ServiceStack/src/ServiceStack.Extensions/Auth/AuthSecretAuthProvider.cs @@ -39,7 +39,7 @@ public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Auth if (HostContext.Config.AdminAuthSecret != null && HostContext.Config.AdminAuthSecret == authSecret) { session = HostContext.AssertPlugin().AuthSecretSession; - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); return Task.FromResult((object?)new AuthenticateResponse { @@ -58,7 +58,7 @@ public Task PreAuthenticateAsync(IRequest req, IResponse res) var authSecret = req.GetAuthSecret() ?? req.GetBearerToken(); if (HostContext.Config.AdminAuthSecret != null && HostContext.Config.AdminAuthSecret == authSecret) { - req.Items[Keywords.Session] = HostContext.AssertPlugin().AuthSecretSession; + req.SetItem(Keywords.Session, HostContext.AssertPlugin().AuthSecretSession); } return Task.CompletedTask; } diff --git a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityApplicationAuthProvider.cs b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityApplicationAuthProvider.cs index 032d89a2a8a..a5cef035cc1 100644 --- a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityApplicationAuthProvider.cs +++ b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityApplicationAuthProvider.cs @@ -135,7 +135,7 @@ public virtual async Task PreAuthenticateAsync(IRequest req, IResponse res) session = SessionFeature.CreateNewSession(req, sessionId); await PopulateSessionAsync(req, session, claimsPrincipal, source); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); } public virtual void PopulateSession(IRequest req, IAuthSession session, ClaimsPrincipal claimsPrincipal, string? source = null) diff --git a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityAuthServiceGatewayFactory.cs b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityAuthServiceGatewayFactory.cs index 6d78194d6a1..8c1b957ae19 100644 --- a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityAuthServiceGatewayFactory.cs +++ b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityAuthServiceGatewayFactory.cs @@ -16,7 +16,7 @@ public IServiceGateway GetServiceGateway(IRequest request) user); } - request.Items[Keywords.Session] = session; + request.SetItem(Keywords.Session, session); var gateway = new InProcessServiceGateway(request); return gateway; } diff --git a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityJwtAuthProvider.cs b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityJwtAuthProvider.cs index 1ccc87b2e1a..f7fe2d421fd 100644 --- a/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityJwtAuthProvider.cs +++ b/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityJwtAuthProvider.cs @@ -328,7 +328,7 @@ public Task PreAuthenticateAsync(IRequest req, IResponse res) Options!.TokenValidationParameters, out SecurityToken validatedToken); } var session = CreateSessionFromClaims(req, user); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); } return Task.CompletedTask; } diff --git a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs index c23d0bd7355..1cb35f5acdf 100644 --- a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs +++ b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs @@ -303,7 +303,7 @@ async Task CreateRequestContextAsync(IServiceScope scope, object r var session = await userResolver.CreateAuthSessionAsync(reqCtx, user, token); if (session != null) { - reqCtx.Items[Keywords.Session] = session; + reqCtx.SetItem(Keywords.Session, session); } } return reqCtx; diff --git a/ServiceStack/src/ServiceStack.Server/ApiKeyCredentialsProvider.cs b/ServiceStack/src/ServiceStack.Server/ApiKeyCredentialsProvider.cs index 9056258de6f..cb11330d09d 100644 --- a/ServiceStack/src/ServiceStack.Server/ApiKeyCredentialsProvider.cs +++ b/ServiceStack/src/ServiceStack.Server/ApiKeyCredentialsProvider.cs @@ -61,12 +61,12 @@ public override void Register(IAppHost appHost, AuthFeature authFeature) { if (appHost.Config.AdminAuthSecret == authSession.RequestTokenSecret) { - req.Items[Keywords.AuthSecret] = appHost.Config.AdminAuthSecret; - req.Items[Keywords.Authorization] = "Bearer " + appHost.Config.AdminAuthSecret; + req.SetItem(Keywords.AuthSecret, appHost.Config.AdminAuthSecret); + req.SetItem(Keywords.Authorization, "Bearer " + appHost.Config.AdminAuthSecret); } if (ValidApiKeys.TryGetValue(authSession.RequestTokenSecret, out var _)) { - req.Items[Keywords.Authorization] = "Bearer " + authSession.RequestTokenSecret; + req.SetItem(Keywords.Authorization, "Bearer " + authSession.RequestTokenSecret); } } }); @@ -160,7 +160,7 @@ public override void Register(IAppHost appHost, AuthFeature authFeature) async Task SaveSessionAsync(IAuthSession session, IRequest httpReq, CancellationToken token) { session.LastModified = DateTime.UtcNow; - httpReq.Items[Keywords.Session] = session; + httpReq.SetItem(Keywords.Session, session); var sessionKey = SessionFeature.GetSessionKey(session.Id ?? httpReq.GetOrCreateSessionId()); await httpReq.GetCacheClientAsync().CacheSetAsync(sessionKey, session, SessionFeature.DefaultSessionExpiry, token); diff --git a/ServiceStack/src/ServiceStack.Server/CrudEvents.cs b/ServiceStack/src/ServiceStack.Server/CrudEvents.cs index 333bc76de12..4ccc6dbd5a3 100644 --- a/ServiceStack/src/ServiceStack.Server/CrudEvents.cs +++ b/ServiceStack/src/ServiceStack.Server/CrudEvents.cs @@ -131,7 +131,7 @@ public virtual async Task ExecuteAsync(T crudEvent) session.Roles ??= result.Roles?.ToList(); session.Permissions ??= result.Permissions?.ToList(); - req.Items[Keywords.Session] = session; + req.SetItem(Keywords.Session, session); } req.Items[Keywords.IgnoreEvent] = bool.TrueString; //don't record AutoCrudEvent diff --git a/ServiceStack/src/ServiceStack.Server/DbJobs.cs b/ServiceStack/src/ServiceStack.Server/DbJobs.cs index 4d7bd6b366c..fbbcbfece5d 100644 --- a/ServiceStack/src/ServiceStack.Server/DbJobs.cs +++ b/ServiceStack/src/ServiceStack.Server/DbJobs.cs @@ -307,7 +307,7 @@ async Task CreateRequestContextAsync(IServiceScope scope, object r var session = await userResolver.CreateAuthSessionAsync(reqCtx, user, token); if (session != null) { - reqCtx.Items[Keywords.Session] = session; + reqCtx.SetItem(Keywords.Session, session); } } return reqCtx; diff --git a/ServiceStack/src/ServiceStack/AuthUserSession.cs b/ServiceStack/src/ServiceStack/AuthUserSession.cs index 7c1293986cc..e596f108ef9 100644 --- a/ServiceStack/src/ServiceStack/AuthUserSession.cs +++ b/ServiceStack/src/ServiceStack/AuthUserSession.cs @@ -314,12 +314,12 @@ public virtual async Task> GetPermissionsAsync(IAuthReposito public virtual async Task> AsClaimsAsync(IAuthRepositoryAsync authRepo) { - List claims = new() { + List claims = [ new Claim(ClaimTypes.NameIdentifier, UserAuthId), new Claim(ClaimTypes.Name, DisplayName), new Claim(ClaimTypes.Email, Email == null || UserAuthName.Contains('@') ? UserAuthName : Email), - new Claim(JwtClaimTypes.Picture, ProfileUrl ?? JwtClaimTypes.DefaultProfileUrl), - }; + new Claim(JwtClaimTypes.Picture, ProfileUrl ?? JwtClaimTypes.DefaultProfileUrl) + ]; var roles = (FromToken ? Roles diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CacheResponseTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CacheResponseTests.cs index b8990a65c97..1c3cf93fbbf 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CacheResponseTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CacheResponseTests.cs @@ -241,12 +241,12 @@ public override void Configure(Container container) if (roleHeader == null) return; - req.Items[Keywords.Session] = new AuthUserSession + req.SetItem(Keywords.Session, new AuthUserSession { UserAuthId = "1", UserAuthName = "test", Roles = [roleHeader] - }; + }); }); ServiceExceptionHandlers.Add((req, dto, ex) => From b7e4b918e39db3ab67dd947d096cb9e3963a46c5 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 13:49:10 +0800 Subject: [PATCH 069/140] Use a clone to preserve UserName / UserId when using API Keys, also don't override session if Admin User is already authenticated --- .../src/ServiceStack.Server/ApiKeysFeature.cs | 29 +++++++++++++++---- ServiceStack/src/ServiceStack/HostConfig.cs | 13 +++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Server/ApiKeysFeature.cs b/ServiceStack/src/ServiceStack.Server/ApiKeysFeature.cs index 91f9998578e..91932d8c976 100644 --- a/ServiceStack/src/ServiceStack.Server/ApiKeysFeature.cs +++ b/ServiceStack/src/ServiceStack.Server/ApiKeysFeature.cs @@ -219,6 +219,19 @@ public PartialApiKey ToPartialApiKey(ApiKey apiKey) return to; } + public Auth.IAuthSession ToAuthUserSession(IApiKey apiKey) + { + var clone = HostContext.Config.CloneAuthSecretSession(); + clone.UserAuthId = apiKey.UserAuthId; + clone.AuthProvider = Keywords.ApiKeyParam; + if (apiKey is ApiKey x) + { + clone.UserName = x.UserName ?? clone.UserName; + clone.UserAuthName = x.UserName ?? clone.UserAuthName; + } + return clone; + } + public async Task RequestFilterAsync(IRequest req, IResponse res, object requestDto) { var apiKeyToken = GetApiKeyToken(req); @@ -227,7 +240,7 @@ public async Task RequestFilterAsync(IRequest req, IResponse res, object request var authSecret = HostContext.Config.AdminAuthSecret; if (authSecret != null && authSecret == apiKeyToken) { - req.Items[Keywords.Session] = HostContext.Config.AuthSecretSession; + req.SetItem(Keywords.Session, HostContext.Config.AuthSecretSession); return; } @@ -237,10 +250,13 @@ public async Task RequestFilterAsync(IRequest req, IResponse res, object request { if (entry.dateTime + CacheDuration > DateTime.UtcNow) { - req.Items[Keywords.ApiKey] = entry.apiKey; + req.SetItem(Keywords.ApiKey, entry.apiKey); if (entry.apiKey.HasScope(RoleNames.Admin)) { - req.Items[Keywords.Session] = HostContext.Config.AuthSecretSession; + if (!req.GetClaimsPrincipal().HasRole(RoleNames.Admin)) + { + req.SetItem(Keywords.Session, ToAuthUserSession(entry.apiKey)); + } } RecordUsage(entry.apiKey); return; @@ -252,10 +268,13 @@ public async Task RequestFilterAsync(IRequest req, IResponse res, object request var apiKey = await source.GetApiKeyAsync(apiKeyToken); if (apiKey != null) { - req.Items[Keywords.ApiKey] = apiKey; + req.SetItem(Keywords.ApiKey, apiKey); if (apiKey.HasScope(RoleNames.Admin)) { - req.Items[Keywords.Session] = HostContext.Config.AuthSecretSession; + if (!req.GetClaimsPrincipal().HasRole(RoleNames.Admin)) + { + req.SetItem(Keywords.Session, ToAuthUserSession(apiKey)); + } } if (CacheDuration != null) { diff --git a/ServiceStack/src/ServiceStack/HostConfig.cs b/ServiceStack/src/ServiceStack/HostConfig.cs index e67d81189e6..133f9b88153 100644 --- a/ServiceStack/src/ServiceStack/HostConfig.cs +++ b/ServiceStack/src/ServiceStack/HostConfig.cs @@ -429,6 +429,19 @@ public ILogFactory LogFactory public IAuthSession AuthSecretSession { get; set; } + public IAuthSession CloneAuthSecretSession() => new AuthUserSession + { + Id = AuthSecretSession.Id, + DisplayName = AuthSecretSession.DisplayName, + UserName = AuthSecretSession.UserName, + UserAuthName = AuthSecretSession.UserAuthName, + AuthProvider = AuthSecretSession.AuthProvider, + IsAuthenticated = AuthSecretSession.IsAuthenticated, + Roles = [..AuthSecretSession.Roles], + Permissions = [..AuthSecretSession.Permissions], + UserAuthId = AuthSecretSession.UserAuthId, + }; + public FallbackRestPathDelegate FallbackRestPath { get; set; } private HashSet razorNamespaces; From 9370c6958cffebb75af854e4c0452250b6f5d564 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 14:04:55 +0800 Subject: [PATCH 070/140] fix tests --- ServiceStack/src/ServiceStack/HttpExtensions.cs | 4 ++-- .../ServiceStack.WebHost.Endpoints.Tests/CompressionTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ServiceStack/src/ServiceStack/HttpExtensions.cs b/ServiceStack/src/ServiceStack/HttpExtensions.cs index 32d1647265d..bcbf8f5d72b 100644 --- a/ServiceStack/src/ServiceStack/HttpExtensions.cs +++ b/ServiceStack/src/ServiceStack/HttpExtensions.cs @@ -99,7 +99,7 @@ public static void EndHttpHandlerRequest(this IResponse httpRes, bool skipHeader afterHeaders?.Invoke(httpRes); var req = httpRes.Request; - if (!req.IsSet(Keywords.HasLogged)) + if (req != null && !req.IsSet(Keywords.HasLogged)) { HostContext.AppHost.OnLogRequest(req, req.Dto, httpRes.Dto, req.GetElapsed()); } @@ -128,7 +128,7 @@ public static async Task EndHttpHandlerRequestAsync(this IResponse httpRes, bool } var req = httpRes.Request; - if (!req.IsSet(Keywords.HasLogged)) + if (req != null && !req.IsSet(Keywords.HasLogged)) { HostContext.AppHost.OnLogRequest(req, req.Dto, httpRes.Dto, req.GetElapsed()); } diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CompressionTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CompressionTests.cs index 78286320464..566c2fe5f4b 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CompressionTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/CompressionTests.cs @@ -86,10 +86,10 @@ public void Test_response_with_CompressedResult() var compressedResult = new CompressedResult(simpleDtoZip); - var reponseWasAutoHandled = mockResponse.WriteToResponse( + var responseWasAutoHandled = mockResponse.WriteToResponse( compressedResult, CompressionTypes.Deflate); - Assert.That(reponseWasAutoHandled.Result, Is.True); + Assert.That(responseWasAutoHandled.Result, Is.True); //var bytesToWriteToResponseStream = new byte[simpleDtoZip.Length - 4]; //Array.Copy(simpleDtoZip, CompressedResult.Adler32ChecksumLength, bytesToWriteToResponseStream, 0, bytesToWriteToResponseStream.Length); From 2f3d806c6cc0d3d0dc3f47815d60d5b7025ee709 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 27 Nov 2025 14:15:22 +0800 Subject: [PATCH 071/140] Rename to ServiceStack.OpenApi.Microsoft and ServiceStack.OpenApi.Swashbuckle --- ServiceStack/build/build.proj | 8 ++++---- .../ServiceStackOpenApiExtensions.cs | 4 ++-- .../ConfigureServiceStackOpenApi.cs | 0 .../OpenApiMetadata.cs | 0 .../OpenApiType.cs | 0 .../OpenApiTypeFormat.cs | 0 .../OpenApiUtils.cs | 0 .../README.md | 0 .../ServiceStack.OpenApi.Microsoft.csproj} | 4 ++-- .../ServiceStackDocumentTransformer.cs | 0 .../ServiceStackOpenApiExtensions.cs | 0 .../ConfigureServiceStackSwagger.cs | 0 .../OpenApiMetadata.cs | 0 .../OpenApiType.cs | 0 .../OpenApiTypeFormat.cs | 0 .../ServiceStack.OpenApi.Swashbuckle.csproj} | 2 +- .../ServiceStackDocumentFilter.cs | 0 .../ServiceStackOpenApiExtensions.cs | 0 .../SwaggerUtils.cs | 0 ServiceStack/src/ServiceStack.sln | 4 ++-- ServiceStack/tests/AdhocNew/AdhocNew.csproj | 2 +- ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj | 2 +- .../tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj | 2 +- ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj | 2 +- 24 files changed, 15 insertions(+), 15 deletions(-) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/ConfigureServiceStackOpenApi.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/OpenApiMetadata.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/OpenApiType.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/OpenApiTypeFormat.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/OpenApiUtils.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/README.md (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj => ServiceStack.OpenApi.Microsoft/ServiceStack.OpenApi.Microsoft.csproj} (95%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/ServiceStackDocumentTransformer.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi.Microsoft => ServiceStack.OpenApi.Microsoft}/ServiceStackOpenApiExtensions.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/ConfigureServiceStackSwagger.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/OpenApiMetadata.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/OpenApiType.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/OpenApiTypeFormat.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj => ServiceStack.OpenApi.Swashbuckle/ServiceStack.OpenApi.Swashbuckle.csproj} (99%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/ServiceStackDocumentFilter.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/ServiceStackOpenApiExtensions.cs (100%) rename ServiceStack/src/{ServiceStack.AspNetCore.OpenApi3 => ServiceStack.OpenApi.Swashbuckle}/SwaggerUtils.cs (100%) diff --git a/ServiceStack/build/build.proj b/ServiceStack/build/build.proj index 71192135a5d..3b85be673ec 100644 --- a/ServiceStack/build/build.proj +++ b/ServiceStack/build/build.proj @@ -144,13 +144,13 @@ Targets="Build;Pack" Properties="Configuration=$(Configuration)" /> - - + - - + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs index 99f350fe6fd..d51e1e67574 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs +++ b/ServiceStack/src/ServiceStack.AspNetCore.OpenApi/ServiceStackOpenApiExtensions.cs @@ -67,10 +67,10 @@ Possible package version conflict detected. To use the latest versions of these packages switch to: - + Which instead uses: - + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ConfigureServiceStackOpenApi.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/ConfigureServiceStackOpenApi.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ConfigureServiceStackOpenApi.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/ConfigureServiceStackOpenApi.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiMetadata.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiMetadata.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiMetadata.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiType.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiType.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiType.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiTypeFormat.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiTypeFormat.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiTypeFormat.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiUtils.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/OpenApiUtils.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/OpenApiUtils.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/README.md similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/README.md rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/README.md diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStack.OpenApi.Microsoft.csproj similarity index 95% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStack.OpenApi.Microsoft.csproj index 5457b778be1..f951f6c7bdc 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStack.AspNetCore.OpenApi.Microsoft.csproj +++ b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStack.OpenApi.Microsoft.csproj @@ -8,11 +8,11 @@ - + - + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStackDocumentTransformer.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs rename to ServiceStack/src/ServiceStack.OpenApi.Microsoft/ServiceStackOpenApiExtensions.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ConfigureServiceStackSwagger.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ConfigureServiceStackSwagger.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ConfigureServiceStackSwagger.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiMetadata.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiMetadata.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiMetadata.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiType.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiType.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiType.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiTypeFormat.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/OpenApiTypeFormat.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/OpenApiTypeFormat.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStack.OpenApi.Swashbuckle.csproj similarity index 99% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStack.OpenApi.Swashbuckle.csproj index 1e426666f5a..683712592a7 100644 --- a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStack.AspNetCore.OpenApi3.csproj +++ b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStack.OpenApi.Swashbuckle.csproj @@ -8,7 +8,7 @@ - + diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStackDocumentFilter.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackDocumentFilter.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStackDocumentFilter.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStackOpenApiExtensions.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/ServiceStackOpenApiExtensions.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/ServiceStackOpenApiExtensions.cs diff --git a/ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs b/ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/SwaggerUtils.cs similarity index 100% rename from ServiceStack/src/ServiceStack.AspNetCore.OpenApi3/SwaggerUtils.cs rename to ServiceStack/src/ServiceStack.OpenApi.Swashbuckle/SwaggerUtils.cs diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index 1f384d3bb47..a0d91774a9e 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -133,13 +133,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdhocNew", "..\tests\AdhocN EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Desktop", "ServiceStack.Desktop\ServiceStack.Desktop.csproj", "{6FFE8429-51F7-45C5-9402-FBA0FC216B85}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.AspNetCore.OpenApi3", "ServiceStack.AspNetCore.OpenApi3\ServiceStack.AspNetCore.OpenApi3.csproj", "{D712182C-B0C3-4797-A576-3AF725F13660}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OpenApi.Swashbuckle", "ServiceStack.OpenApi.Swashbuckle\ServiceStack.OpenApi.Swashbuckle.csproj", "{D712182C-B0C3-4797-A576-3AF725F13660}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi3Swashbuckle", "..\tests\OpenApi3Swashbuckle\OpenApi3Swashbuckle.csproj", "{ABE5BD4B-D33A-4E1C-AFC1-1087323491DD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiScalar", "..\tests\OpenApiScalar\OpenApiScalar.csproj", "{D0024195-711E-4B32-8F39-40D34BFDB57B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.AspNetCore.OpenApi.Microsoft", "ServiceStack.AspNetCore.OpenApi.Microsoft\ServiceStack.AspNetCore.OpenApi.Microsoft.csproj", "{03EDA213-6C39-4DB9-BF03-DEB67BDF3286}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OpenApi.Microsoft", "ServiceStack.OpenApi.Microsoft\ServiceStack.OpenApi.Microsoft.csproj", "{03EDA213-6C39-4DB9-BF03-DEB67BDF3286}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/ServiceStack/tests/AdhocNew/AdhocNew.csproj b/ServiceStack/tests/AdhocNew/AdhocNew.csproj index 294ea32f1a0..9e27d7fda1b 100644 --- a/ServiceStack/tests/AdhocNew/AdhocNew.csproj +++ b/ServiceStack/tests/AdhocNew/AdhocNew.csproj @@ -46,7 +46,7 @@ - + diff --git a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj index 582a91ce2a6..8c92132d869 100644 --- a/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj +++ b/ServiceStack/tests/NorthwindBlazor/NorthwindBlazor.csproj @@ -31,7 +31,7 @@ - + diff --git a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj index 9fc840d634e..c0bff236d0a 100644 --- a/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj +++ b/ServiceStack/tests/OpenApi3Swashbuckle/OpenApi3Swashbuckle.csproj @@ -23,7 +23,7 @@ - + diff --git a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj index 2e7872c8342..f2b2b65d80a 100644 --- a/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj +++ b/ServiceStack/tests/OpenApiScalar/OpenApiScalar.csproj @@ -24,7 +24,7 @@ - + From d0b6acf38cc45c16d229df66b82183644e0aee3c Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 28 Nov 2025 18:46:57 +0800 Subject: [PATCH 072/140] Add GetUserId and GetRequireUserId to resolve UserId from API Key or ClaimsPrincipal --- .../src/ServiceStack/ServerClaimUtils.cs | 28 +++++++++++++++++++ .../AutoQueryTests.cs | 2 ++ 2 files changed, 30 insertions(+) create mode 100644 ServiceStack/src/ServiceStack/ServerClaimUtils.cs diff --git a/ServiceStack/src/ServiceStack/ServerClaimUtils.cs b/ServiceStack/src/ServiceStack/ServerClaimUtils.cs new file mode 100644 index 00000000000..399f6766729 --- /dev/null +++ b/ServiceStack/src/ServiceStack/ServerClaimUtils.cs @@ -0,0 +1,28 @@ +#nullable enable +using ServiceStack.Web; + +namespace ServiceStack; + +public static class ServerClaimUtils +{ + /// + /// Retrieves the User Id associated with the request from either API Key or ClaimsPrincipal. + /// + public static string? GetUserId(this IRequest? req) + { + var apiKeyUserId = req.GetApiKey()?.UserAuthId; + if (apiKeyUserId != null) + return apiKeyUserId; + var user = req.GetClaimsPrincipal(); + return user.IsAuthenticated() + ? user.GetUserId() + : null; + } + + /// + /// Retrieves the required User Id associated with the request from either API Key or ClaimsPrincipal. + /// Throws an unauthorized exception if the User Id is not available. + /// + public static string GetRequiredUserId(this IRequest? req) => req.GetUserId() + ?? throw HttpError.Unauthorized("User Authentication required"); +} \ No newline at end of file diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs index b5f6f63c062..14e688afd59 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs @@ -841,6 +841,8 @@ public async Task Any(QueryMovies query) { using var db = AutoQuery.GetDb(query, base.Request); var q = AutoQuery.CreateQuery(query, base.Request, db); + var userId = Request.GetClaimsPrincipal().GetUserId(); + q.Where(x => x.Rating == "PG-13"); return await AutoQuery.ExecuteAsync(query, q, base.Request, db); } } From 3073f748b8bd42d83562d128caf5a4c35f1a3d33 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 28 Nov 2025 19:45:04 +0800 Subject: [PATCH 073/140] Update AutoQueryTests.cs --- .../ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs index 14e688afd59..d02299824f4 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs @@ -547,6 +547,7 @@ public class Movie public DateTime ReleaseDate { get; set; } public string TagLine { get; set; } public List Genres { get; set; } + public string CreatedBy { get; set; } } public class StreamMovies : QueryDb @@ -841,8 +842,6 @@ public async Task Any(QueryMovies query) { using var db = AutoQuery.GetDb(query, base.Request); var q = AutoQuery.CreateQuery(query, base.Request, db); - var userId = Request.GetClaimsPrincipal().GetUserId(); - q.Where(x => x.Rating == "PG-13"); return await AutoQuery.ExecuteAsync(query, q, base.Request, db); } } From e5ab24c25c3025047953d015d5a895a87c9beee0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 28 Nov 2025 21:23:42 +0800 Subject: [PATCH 074/140] Update AutoQueryTests.cs --- .../tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs index d02299824f4..b5f6f63c062 100644 --- a/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs +++ b/ServiceStack/tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryTests.cs @@ -547,7 +547,6 @@ public class Movie public DateTime ReleaseDate { get; set; } public string TagLine { get; set; } public List Genres { get; set; } - public string CreatedBy { get; set; } } public class StreamMovies : QueryDb From acf8ac2021f9b38717f1a50464738d6f2f894a40 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 29 Nov 2025 11:01:05 +0800 Subject: [PATCH 075/140] Add link to Chat UI --- .../modules/admin-ui/components/AdminChat.mjs | 7 +++++++ .../tests/NorthwindAuto/admin-ui/components/AdminChat.mjs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs index f687ac1b618..7c731383fce 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/src/ServiceStack.AI.Chat/modules/admin-ui/components/AdminChat.mjs @@ -790,6 +790,13 @@ export const AdminChat = { + +
+ + Chat + + +
diff --git a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs index f687ac1b618..7c731383fce 100644 --- a/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs +++ b/ServiceStack/tests/NorthwindAuto/admin-ui/components/AdminChat.mjs @@ -790,6 +790,13 @@ export const AdminChat = { + +
+ + Chat + + +
From 40ef6710d9c4293763dcdf1419ae8fd9a6ac9a65 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 1 Dec 2025 10:40:28 +0800 Subject: [PATCH 076/140] bump to 10.0.0 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/src/ServiceStack.sln | 7 +++---- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 22 files changed, 24 insertions(+), 25 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 37f394c8b92..77c3fed7ad2 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 8.101m; + public static decimal ServiceStackVersion = 10.0m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index a0d91774a9e..6d79d7d8ad1 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -7,10 +7,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4EBD29DD EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{EB65E771-6C97-4D64-8E11-FD88AE48A41A}" ProjectSection(SolutionItems) = preProject - ..\build\build.bat = ..\build\build.bat - ..\build\build.proj = ..\build\build.proj - Directory.Build.props = Directory.Build.props - ..\tests\Directory.Build.props = ..\tests\Directory.Build.props + ..\..\build\src\Directory.Build.props = ..\..\build\src\Directory.Build.props + ..\..\build\tests\Directory.Build.props = ..\..\build\tests\Directory.Build.props + ..\..\build\sync.sh = ..\..\build\sync.sh EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{515043B6-1FFD-4CBF-8916-23B82C67CF8C}" diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index c5875f9267f..484dc2d8c52 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index 07b209fe645..4f41502d781 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 8.10.1 + 10.0.0 latest false From fab5e375477a541802c9a3710ace799b584280b8 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 1 Dec 2025 13:07:28 +0800 Subject: [PATCH 077/140] Sync AI Chat --- .../chat/ProviderIcon.mjs | 1 + .../src/ServiceStack.AI.Chat/chat/index.html | 2 +- .../src/ServiceStack.AI.Chat/chat/llms.json | 113 +++++++++--------- .../wwwroot/chat/ProviderIcon.mjs | 1 + .../NorthwindAuto/wwwroot/chat/index.html | 2 +- .../NorthwindAuto/wwwroot/chat/llms.json | 113 +++++++++--------- 6 files changed, 112 insertions(+), 120 deletions(-) diff --git a/ServiceStack/src/ServiceStack.AI.Chat/chat/ProviderIcon.mjs b/ServiceStack/src/ServiceStack.AI.Chat/chat/ProviderIcon.mjs index ad3321d3097..07b6839940a 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/chat/ProviderIcon.mjs +++ b/ServiceStack/src/ServiceStack.AI.Chat/chat/ProviderIcon.mjs @@ -12,6 +12,7 @@ export default { + `, props: { diff --git a/ServiceStack/src/ServiceStack.AI.Chat/chat/index.html b/ServiceStack/src/ServiceStack.AI.Chat/chat/index.html index 4b8db2ff97d..1baa0c8ddf8 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/chat/index.html +++ b/ServiceStack/src/ServiceStack.AI.Chat/chat/index.html @@ -1,6 +1,6 @@ - llms.py + AI Chat diff --git a/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json b/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json index 90c94d1b459..e1e2d716f4d 100644 --- a/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json +++ b/ServiceStack/src/ServiceStack.AI.Chat/chat/llms.json @@ -11,7 +11,7 @@ "defaults": { "headers": { "Content-Type": "application/json", - "User-Agent": "llms.py/1.0" + "User-Agent": "llmspy.org/1.0" }, "text": { "model": "kimi-k2", @@ -220,37 +220,18 @@ "api_key": "$OPENROUTER_API_KEY", "models": { "kat-coder-pro": "kwaipilot/kat-coder-pro:free", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", "tongyi-deepresearch:30b": "alibaba/tongyi-deepresearch-30b-a3b:free", - "longcat-flash-chat": "meituan/longcat-flash-chat:free", "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", - "deepseek-v3.1:671b": "deepseek/deepseek-chat-v3.1:free", + "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", "gpt-oss:20b": "openai/gpt-oss-20b:free", "glm-4.5-air": "z-ai/glm-4.5-air:free", - "qwen3-coder": "qwen/qwen3-coder:free", "kimi-k2": "moonshotai/kimi-k2:free", - "venice-mistral:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", "gemma3n:2b": "google/gemma-3n-e2b-it:free", - "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", - "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", "gemma3n:4b": "google/gemma-3n-e4b-it:free", - "qwen3:30b": "qwen/qwen3-30b-a3b:free", - "qwen3:14b": "qwen/qwen3-14b:free", - "qwen3:235b": "qwen/qwen3-235b-a22b:free", - "mai-ds-r1": "microsoft/mai-ds-r1:free", - "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", - "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", - "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", "gemma3:4b": "google/gemma-3-4b-it:free", "gemma3:12b": "google/gemma-3-12b-it:free", "gemma3:27b": "google/gemma-3-27b-it:free", - "deepseek-r1": "deepseek/deepseek-r1-0528:free", - "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free", - "llama3.2:3b": "meta-llama/llama-3.2-3b-instruct:free", - "mistral-nemo:12b": "mistralai/mistral-nemo:free", - "qwen3:4b": "qwen/qwen3-4b:free", "mistral:7b": "mistralai/mistral-7b-instruct:free" }, "default_pricing": { @@ -338,6 +319,7 @@ "base_url": "https://api.anthropic.com", "api_key": "$ANTHROPIC_API_KEY", "models": { + "claude-opus-4.5": "claude-opus-4.5", "claude-sonnet-4-5": "claude-sonnet-4-5", "claude-sonnet-4-0": "claude-sonnet-4-0", "claude-3-7-sonnet": "claude-3-7-sonnet-latest", @@ -346,13 +328,9 @@ "claude-3-haiku": "claude-3-haiku-20240307" }, "pricing": { - "claude-opus-4-1": { - "input": "0.000015", - "output": "0.000075" - }, - "claude-opus-4": { - "input": "0.000015", - "output": "0.000075" + "claude-opus-4-5": { + "input": "0.000005", + "output": "0.000025" }, "claude-sonnet-4-5": { "input": "0.000003", @@ -700,6 +678,22 @@ }, "enable_thinking": false }, + "minimax": { + "enabled": true, + "type": "OpenAiProvider", + "base_url": "https://api.minimax.io/v1", + "api_key": "$MINIMAX_API_KEY", + "models": { + "minimax-m2": "MiniMax-M2" + }, + "temperature": 1.0, + "pricing": { + "minimax/minimax-m2": { + "input": "0.0000003", + "output": "0.0000012" + } + } + }, "z.ai": { "enabled": true, "type": "OpenAiProvider", @@ -878,6 +872,7 @@ "nova-lite": "amazon/nova-lite-v1", "nova-pro": "amazon/nova-pro-v1", "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl", + "claude-opus-4.5": "anthropic/claude-opus-4.5", "claude-sonnet-4-5": "anthropic/claude-sonnet-4.5", "claude-sonnet-4-0": "anthropic/claude-sonnet-4", "gemini-flash-latest": "google/gemini-2.5-flash", @@ -941,8 +936,8 @@ "output": "0.0000035" }, "meta-llama/llama-3.3-70b-instruct": { - "input": "0.00000013", - "output": "0.00000038" + "input": "0.000000104", + "output": "0.000000312" }, "microsoft/phi-4": { "input": "0.00000006", @@ -984,6 +979,10 @@ "input": "0.0000002", "output": "0.0000006" }, + "anthropic/claude-opus-4.5": { + "input": "0.000005", + "output": "0.000025" + }, "anthropic/claude-sonnet-4.5": { "input": "0.000003", "output": "0.000015" @@ -1013,8 +1012,8 @@ "output": "0.0000001" }, "google/gemma-3-27b-it": { - "input": "0.00000009", - "output": "0.00000016" + "input": "0.00000007", + "output": "0.0000005" }, "openai/gpt-5.1": { "input": "0.00000125", @@ -1053,8 +1052,8 @@ "output": "0.00001" }, "openai/gpt-oss-120b": { - "input": "0.00000005", - "output": "0.00000024" + "input": "0.00000004", + "output": "0.0000002" }, "openai/gpt-oss-20b": { "input": "0.00000003", @@ -1105,31 +1104,31 @@ "output": "0.0000019" }, "z-ai/glm-4.5v": { - "input": "0.0000006", - "output": "0.0000018" + "input": "0.00000048", + "output": "0.00000144" }, "z-ai/glm-4.5": { "input": "0.00000035", "output": "0.0000015" }, "z-ai/glm-4.5-air": { - "input": "0.00000013", - "output": "0.00000085" + "input": "0.000000104", + "output": "0.00000068" }, "minimax/minimax-m2": { "input": "0.000000255", "output": "0.00000102" }, "moonshotai/kimi-k2": { - "input": "0.0000005", - "output": "0.0000024" + "input": "0.000000456", + "output": "0.00000184" }, "moonshotai/kimi-k2-thinking": { - "input": "0.00000055", - "output": "0.00000225" + "input": "0.00000045", + "output": "0.00000235" }, "moonshotai/kimi-linear-48b-a3b-instruct": { - "input": "0.0000003", + "input": "0.0000005", "output": "0.0000006" }, "deepseek/deepseek-chat": { @@ -1137,8 +1136,8 @@ "output": "0.0000012" }, "deepseek/deepseek-v3.2-exp": { - "input": "0.00000027", - "output": "0.0000004" + "input": "0.000000216", + "output": "0.000000328" }, "deepseek/deepseek-chat-v3.1": { "input": "0.0000002", @@ -1149,20 +1148,20 @@ "output": "0.0000012" }, "deepseek/deepseek-v3.1-terminus": { - "input": "0.00000027", - "output": "0.000001" + "input": "0.000000216", + "output": "0.0000008" }, "qwen/qwen3-8b": { - "input": "0.000000035", - "output": "0.000000138" + "input": "0.000000028", + "output": "0.0000001104" }, "qwen/qwen3-30b-a3b": { "input": "0.00000006", "output": "0.00000022" }, "qwen/qwen3-32b": { - "input": "0.00000005", - "output": "0.0000002" + "input": "0.00000008", + "output": "0.00000024" }, "qwen/qwen3-235b-a22b": { "input": "0.00000018", @@ -1189,24 +1188,20 @@ "output": "0.000006" }, "qwen/qwen3-vl-235b-a22b-instruct": { - "input": "0.00000022", - "output": "0.00000088" + "input": "0.00000021", + "output": "0.0000019" }, "qwen/qwen3-vl-235b-a22b-thinking": { "input": "0.0000003", "output": "0.0000012" }, - "inclusionai/ling-1t": { - "input": "0.00000057", - "output": "0.00000228" - }, "meta-llama/llama-4-scout": { "input": "0.00000008", "output": "0.0000003" }, "meta-llama/llama-4-maverick": { - "input": "0.00000015", - "output": "0.0000006" + "input": "0.000000136", + "output": "0.00000068" } } }, diff --git a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/ProviderIcon.mjs b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/ProviderIcon.mjs index ad3321d3097..07b6839940a 100644 --- a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/ProviderIcon.mjs +++ b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/ProviderIcon.mjs @@ -12,6 +12,7 @@ export default { + `, props: { diff --git a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/index.html b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/index.html index 4b8db2ff97d..1baa0c8ddf8 100644 --- a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/index.html +++ b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/index.html @@ -1,6 +1,6 @@ - llms.py + AI Chat diff --git a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json index 90c94d1b459..e1e2d716f4d 100644 --- a/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json +++ b/ServiceStack/tests/NorthwindAuto/wwwroot/chat/llms.json @@ -11,7 +11,7 @@ "defaults": { "headers": { "Content-Type": "application/json", - "User-Agent": "llms.py/1.0" + "User-Agent": "llmspy.org/1.0" }, "text": { "model": "kimi-k2", @@ -220,37 +220,18 @@ "api_key": "$OPENROUTER_API_KEY", "models": { "kat-coder-pro": "kwaipilot/kat-coder-pro:free", - "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl:free", "tongyi-deepresearch:30b": "alibaba/tongyi-deepresearch-30b-a3b:free", - "longcat-flash-chat": "meituan/longcat-flash-chat:free", "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free", - "deepseek-v3.1:671b": "deepseek/deepseek-chat-v3.1:free", + "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", "gpt-oss:20b": "openai/gpt-oss-20b:free", "glm-4.5-air": "z-ai/glm-4.5-air:free", - "qwen3-coder": "qwen/qwen3-coder:free", "kimi-k2": "moonshotai/kimi-k2:free", - "venice-mistral:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", "gemma3n:2b": "google/gemma-3n-e2b-it:free", - "deepseek-r1t2-chimera": "tngtech/deepseek-r1t2-chimera:free", - "mistral-small3.2:24b": "mistralai/mistral-small-3.2-24b-instruct:free", - "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free", "gemma3n:4b": "google/gemma-3n-e4b-it:free", - "qwen3:30b": "qwen/qwen3-30b-a3b:free", - "qwen3:14b": "qwen/qwen3-14b:free", - "qwen3:235b": "qwen/qwen3-235b-a22b:free", - "mai-ds-r1": "microsoft/mai-ds-r1:free", - "qwen-2.5:72b": "qwen/qwen-2.5-72b-instruct:free", - "qwen2.5vl": "qwen/qwen2.5-vl-32b-instruct:free", - "qwen2.5vl:32b": "qwen/qwen2.5-vl-32b-instruct:free", "gemma3:4b": "google/gemma-3-4b-it:free", "gemma3:12b": "google/gemma-3-12b-it:free", "gemma3:27b": "google/gemma-3-27b-it:free", - "deepseek-r1": "deepseek/deepseek-r1-0528:free", - "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free", "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free", - "llama3.2:3b": "meta-llama/llama-3.2-3b-instruct:free", - "mistral-nemo:12b": "mistralai/mistral-nemo:free", - "qwen3:4b": "qwen/qwen3-4b:free", "mistral:7b": "mistralai/mistral-7b-instruct:free" }, "default_pricing": { @@ -338,6 +319,7 @@ "base_url": "https://api.anthropic.com", "api_key": "$ANTHROPIC_API_KEY", "models": { + "claude-opus-4.5": "claude-opus-4.5", "claude-sonnet-4-5": "claude-sonnet-4-5", "claude-sonnet-4-0": "claude-sonnet-4-0", "claude-3-7-sonnet": "claude-3-7-sonnet-latest", @@ -346,13 +328,9 @@ "claude-3-haiku": "claude-3-haiku-20240307" }, "pricing": { - "claude-opus-4-1": { - "input": "0.000015", - "output": "0.000075" - }, - "claude-opus-4": { - "input": "0.000015", - "output": "0.000075" + "claude-opus-4-5": { + "input": "0.000005", + "output": "0.000025" }, "claude-sonnet-4-5": { "input": "0.000003", @@ -700,6 +678,22 @@ }, "enable_thinking": false }, + "minimax": { + "enabled": true, + "type": "OpenAiProvider", + "base_url": "https://api.minimax.io/v1", + "api_key": "$MINIMAX_API_KEY", + "models": { + "minimax-m2": "MiniMax-M2" + }, + "temperature": 1.0, + "pricing": { + "minimax/minimax-m2": { + "input": "0.0000003", + "output": "0.0000012" + } + } + }, "z.ai": { "enabled": true, "type": "OpenAiProvider", @@ -878,6 +872,7 @@ "nova-lite": "amazon/nova-lite-v1", "nova-pro": "amazon/nova-pro-v1", "nemotron-nano-vl:12b": "nvidia/nemotron-nano-12b-v2-vl", + "claude-opus-4.5": "anthropic/claude-opus-4.5", "claude-sonnet-4-5": "anthropic/claude-sonnet-4.5", "claude-sonnet-4-0": "anthropic/claude-sonnet-4", "gemini-flash-latest": "google/gemini-2.5-flash", @@ -941,8 +936,8 @@ "output": "0.0000035" }, "meta-llama/llama-3.3-70b-instruct": { - "input": "0.00000013", - "output": "0.00000038" + "input": "0.000000104", + "output": "0.000000312" }, "microsoft/phi-4": { "input": "0.00000006", @@ -984,6 +979,10 @@ "input": "0.0000002", "output": "0.0000006" }, + "anthropic/claude-opus-4.5": { + "input": "0.000005", + "output": "0.000025" + }, "anthropic/claude-sonnet-4.5": { "input": "0.000003", "output": "0.000015" @@ -1013,8 +1012,8 @@ "output": "0.0000001" }, "google/gemma-3-27b-it": { - "input": "0.00000009", - "output": "0.00000016" + "input": "0.00000007", + "output": "0.0000005" }, "openai/gpt-5.1": { "input": "0.00000125", @@ -1053,8 +1052,8 @@ "output": "0.00001" }, "openai/gpt-oss-120b": { - "input": "0.00000005", - "output": "0.00000024" + "input": "0.00000004", + "output": "0.0000002" }, "openai/gpt-oss-20b": { "input": "0.00000003", @@ -1105,31 +1104,31 @@ "output": "0.0000019" }, "z-ai/glm-4.5v": { - "input": "0.0000006", - "output": "0.0000018" + "input": "0.00000048", + "output": "0.00000144" }, "z-ai/glm-4.5": { "input": "0.00000035", "output": "0.0000015" }, "z-ai/glm-4.5-air": { - "input": "0.00000013", - "output": "0.00000085" + "input": "0.000000104", + "output": "0.00000068" }, "minimax/minimax-m2": { "input": "0.000000255", "output": "0.00000102" }, "moonshotai/kimi-k2": { - "input": "0.0000005", - "output": "0.0000024" + "input": "0.000000456", + "output": "0.00000184" }, "moonshotai/kimi-k2-thinking": { - "input": "0.00000055", - "output": "0.00000225" + "input": "0.00000045", + "output": "0.00000235" }, "moonshotai/kimi-linear-48b-a3b-instruct": { - "input": "0.0000003", + "input": "0.0000005", "output": "0.0000006" }, "deepseek/deepseek-chat": { @@ -1137,8 +1136,8 @@ "output": "0.0000012" }, "deepseek/deepseek-v3.2-exp": { - "input": "0.00000027", - "output": "0.0000004" + "input": "0.000000216", + "output": "0.000000328" }, "deepseek/deepseek-chat-v3.1": { "input": "0.0000002", @@ -1149,20 +1148,20 @@ "output": "0.0000012" }, "deepseek/deepseek-v3.1-terminus": { - "input": "0.00000027", - "output": "0.000001" + "input": "0.000000216", + "output": "0.0000008" }, "qwen/qwen3-8b": { - "input": "0.000000035", - "output": "0.000000138" + "input": "0.000000028", + "output": "0.0000001104" }, "qwen/qwen3-30b-a3b": { "input": "0.00000006", "output": "0.00000022" }, "qwen/qwen3-32b": { - "input": "0.00000005", - "output": "0.0000002" + "input": "0.00000008", + "output": "0.00000024" }, "qwen/qwen3-235b-a22b": { "input": "0.00000018", @@ -1189,24 +1188,20 @@ "output": "0.000006" }, "qwen/qwen3-vl-235b-a22b-instruct": { - "input": "0.00000022", - "output": "0.00000088" + "input": "0.00000021", + "output": "0.0000019" }, "qwen/qwen3-vl-235b-a22b-thinking": { "input": "0.0000003", "output": "0.0000012" }, - "inclusionai/ling-1t": { - "input": "0.00000057", - "output": "0.00000228" - }, "meta-llama/llama-4-scout": { "input": "0.00000008", "output": "0.0000003" }, "meta-llama/llama-4-maverick": { - "input": "0.00000015", - "output": "0.0000006" + "input": "0.000000136", + "output": "0.00000068" } } }, From 2bc841da2c79928aaeda88de5af96934b7f621dc Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 2 Dec 2025 12:10:56 +0800 Subject: [PATCH 078/140] prevent an invalid batch from breaking the entire analytics report --- .../ServiceStack.Jobs/SqliteRequestLogger.cs | 105 +++++++++++++----- .../ServiceStack.Server/DbRequestLogger.cs | 100 ++++++++++++----- 2 files changed, 146 insertions(+), 59 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs index 02b9ea5564e..2e4fb184c03 100644 --- a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs @@ -65,6 +65,7 @@ public class SqliteRequestLogger : InMemoryRollingRequestLogger, IRequiresSchema public IDbConnectionFactory DbFactory { get; set; } = null!; public bool EnableWriterLock { get; set; } = true; public IAppHostNetCore AppHost { get; set; } = null!; + public ILogger? Logger { get; set; } public SqliteRequestLogger() { @@ -227,7 +228,11 @@ public void Register(IAppHost appHost) DbFactory ??= appHost.TryResolve() ?? new OrmLiteConnectionFactory("Data Source=:memory:", DialectProvider); - AppHost ??= (IAppHostNetCore)appHost; + AppHost ??= (IAppHostNetCore)appHost; +#if NET8_0_OR_GREATER + Logger ??= appHost.TryResolve(); +#endif + _ = GetDbDir().AssertDir(); if (IgnoreRequestTypes.Length > 0) @@ -442,11 +447,19 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", + lastPk, e.Message); + } foreach (var requestLog in batch) { @@ -493,12 +506,20 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.OperationName == op) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.OperationName == op) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", + lastPk, op, e.Message); + } foreach (var requestLog in batch) { @@ -557,12 +578,20 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.UserAuthId == userId) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.UserAuthId == userId) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", + lastPk, userId, e.Message); + } foreach (var requestLog in batch) { @@ -624,12 +653,20 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(Headers + " LIKE {0}", $"%Bearer {apiKey}%") - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(Headers + " LIKE {0}", $"%Bearer {apiKey}%") + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", + lastPk, e.Message); + } foreach (var requestLog in batch) { @@ -690,12 +727,20 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.IpAddress == ip) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.IpAddress == ip) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", + lastPk, ip, e.Message); + } foreach (var requestLog in batch) { diff --git a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs index 4165736f378..e95b9f59a21 100644 --- a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs @@ -278,6 +278,7 @@ public class DbRequestLogger : InMemoryRollingRequestLogger, IRequiresSchema, public int MaxLimit { get; set; } = 5000; public bool AutoInitSchema { get; set; } = true; public IAppHostNetCore AppHost { get; set; } = null!; + public ILogger? Logger { get; set; } public DbRequestLogger() { @@ -426,6 +427,7 @@ public void Register(IAppHost appHost) ?? throw new Exception($"{nameof(IDbConnectionFactory)} is not registered"); DbProvider ??= DbLoggingProvider.Create(DbFactory, NamedConnection); AppHost ??= (IAppHostNetCore)appHost; + Logger ??= appHost.TryResolve(); ConfigureDialect?.Invoke(Dialect); @@ -574,11 +576,19 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", + lastPk, e.Message); + } foreach (var requestLog in batch) { @@ -625,12 +635,20 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.OperationName == op) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.OperationName == op) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", + lastPk, op, e.Message); + } foreach (var requestLog in batch) { @@ -689,12 +707,20 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.UserAuthId == userId) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.UserAuthId == userId) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", + lastPk, userId, e.Message); + } foreach (var requestLog in batch) { @@ -758,12 +784,20 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(headers + " LIKE {0}", $"%Bearer {apiKey}%") - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(headers + " LIKE {0}", $"%Bearer {apiKey}%") + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", + lastPk, e.Message); + } foreach (var requestLog in batch) { @@ -824,12 +858,20 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s var ret = CreateAnalyticsReports(); do { - batch = db.Select( - db.From() - .Where(x => x.Id > lastPk) - .And(x => x.IpAddress == ip) - .OrderBy(x => x.Id) - .Limit(config.BatchSize)); + try + { + batch = db.Select( + db.From() + .Where(x => x.Id > lastPk) + .And(x => x.IpAddress == ip) + .OrderBy(x => x.Id) + .Limit(config.BatchSize)); + } + catch (Exception e) + { + Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", + lastPk, ip, e.Message); + } foreach (var requestLog in batch) { From 224a240ccee5c888ac9de85ee85eb1961c56765e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 2 Dec 2025 13:14:36 +0800 Subject: [PATCH 079/140] incr lastPk to avoid infinite loops --- ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs | 5 +++++ ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs index 2e4fb184c03..c4b596d89d0 100644 --- a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs @@ -459,6 +459,7 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon { Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -519,6 +520,7 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, { Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", lastPk, op, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -591,6 +593,7 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, { Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", lastPk, userId, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -666,6 +669,7 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont { Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -740,6 +744,7 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s { Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", lastPk, ip, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) diff --git a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs index e95b9f59a21..33fe1bf99d5 100644 --- a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs @@ -588,6 +588,7 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon { Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -648,6 +649,7 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, { Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", lastPk, op, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -720,6 +722,7 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, { Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", lastPk, userId, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -797,6 +800,7 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont { Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) @@ -871,6 +875,7 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s { Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", lastPk, ip, e.Message); + lastPk += config.BatchSize; // avoid infinite loops } foreach (var requestLog in batch) From 562d100bdb819ea63718dfc9e749e4d4c1b7dc6a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 2 Dec 2025 15:45:45 +0800 Subject: [PATCH 080/140] short-circuit loop after failed request --- ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs | 5 +++++ ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs index c4b596d89d0..5c0daa7ef8f 100644 --- a/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Jobs/SqliteRequestLogger.cs @@ -460,6 +460,7 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -521,6 +522,7 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", lastPk, op, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -594,6 +596,7 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", lastPk, userId, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -670,6 +673,7 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -745,6 +749,7 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", lastPk, ip, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) diff --git a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs index 33fe1bf99d5..49d7ea8964c 100644 --- a/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs +++ b/ServiceStack/src/ServiceStack.Server/DbRequestLogger.cs @@ -589,6 +589,7 @@ public AnalyticsReports GetAnalyticsReports(AnalyticsConfig config, DateTime mon Logger?.LogWarning(e, "GetAnalyticsReports(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -650,6 +651,7 @@ public AnalyticsReports GetApiAnalytics(AnalyticsConfig config, DateTime month, Logger?.LogWarning(e, "GetApiAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND OperationName = {Op}: {Message}", lastPk, op, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -723,6 +725,7 @@ public AnalyticsReports GetUserAnalytics(AnalyticsConfig config, DateTime month, Logger?.LogWarning(e, "GetUserAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND UserAuthId = {UserId}: {Message}", lastPk, userId, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -801,6 +804,7 @@ public AnalyticsReports GetApiKeyAnalytics(AnalyticsConfig config, DateTime mont Logger?.LogWarning(e, "GetApiKeyAnalytics(): SELECT RequestLog WHERE Id > {LastPk}: {Message}", lastPk, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) @@ -876,6 +880,7 @@ public AnalyticsReports GetIpAnalytics(AnalyticsConfig config, DateTime month, s Logger?.LogWarning(e, "GetIpAnalytics(): SELECT RequestLog WHERE Id > {LastPk} AND IpAddress = {Ip}: {Message}", lastPk, ip, e.Message); lastPk += config.BatchSize; // avoid infinite loops + continue; } foreach (var requestLog in batch) From 5fc1003bac8c112ec15920d86b3f600970f576f2 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 3 Dec 2025 12:23:47 +0800 Subject: [PATCH 081/140] bump to v10.0.1 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 77c3fed7ad2..9e8ead15131 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 10.0m; + public static decimal ServiceStackVersion = 10.01m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index 484dc2d8c52..cf458ce7ae0 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index 4f41502d781..624da513732 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.0 + 10.0.1 latest false From ff64fb1ea2d3079c29ea78b8c28a5a01fb97d1bc Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 3 Dec 2025 16:34:59 +0800 Subject: [PATCH 082/140] Fix TryStartNode for windows --- .../src/ServiceStack.Extensions/NodeProxy.cs | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 41331437763..84c617efc26 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -217,43 +217,60 @@ public bool RemoveCacheEntry(string key) public bool TryStartNode(string workingDirectory, out Process process) { - process = new Process + // Convert relative path to absolute path for Windows compatibility + var absoluteWorkingDir = Path.GetFullPath(workingDirectory); + + // On Windows, npm is a batch file, so we need to use cmd.exe + var isWindows = OperatingSystem.IsWindows(); + var fileName = isWindows ? "cmd.exe" : "npm"; + var arguments = isWindows ? "/c npm run dev" : "run dev"; + + process = new Process { StartInfo = new() { - FileName = "npm", - Arguments = "run dev", - WorkingDirectory = workingDirectory, + FileName = fileName, + Arguments = arguments, + WorkingDirectory = absoluteWorkingDir, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true - }, + }, EnableRaisingEvents = true, }; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; - if (LogDebug) + process.OutputDataReceived += (s, e) => { + if (e.Data != null) + { + Log?.LogDebug(e.Data); + } + }; + process.ErrorDataReceived += (s, e) => { + if (e.Data != null) + { + Log?.LogError(e.Data); + } + }; + + try { - process.OutputDataReceived += (s, e) => { - if (e.Data != null) - { - Log?.LogDebug(e.Data); - } - }; - process.ErrorDataReceived += (s, e) => { - if (e.Data != null) - { - Log?.LogError(e.Data); - } - }; + Log?.LogInformation($"Starting Node.js dev server in: {absoluteWorkingDir}"); + if (!process.Start()) + { + Log?.LogError("Failed to start Node.js process"); + return false; + } + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + Log?.LogInformation("Node.js dev server started successfully"); + return true; } - if (!process.Start()) + catch (Exception ex) { + Log?.LogError(ex, "Error starting Node.js process"); return false; } - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - return true; } static bool IsHopByHopHeader(string headerName) From 44ce364347805f32fdaba76ec8d56327ab1061b2 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Dec 2025 13:24:40 +0800 Subject: [PATCH 083/140] Make the Node Process more configurable --- .../src/ServiceStack.Extensions/NodeProxy.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 84c617efc26..56bb9fff4bb 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -18,6 +18,13 @@ public class NodeProxy { public HttpClient Client { get; set; } public ILogger? Log { get; set; } + + public string ProcessFileName { get; set; } + public string ProcessArguments { get; set; } + public Action? ConfigureProcess { get; set; } + public Action? ConfigureLinuxProcess { get; set; } + public Action? ConfigureMacProcess { get; set; } + public Action? ConfigureWindowsProcess { get; set; } /// /// Maximum size in bytes for an individual file to be cached (default: 5 MB) @@ -127,6 +134,11 @@ private void Init(HttpClient client) { Client = client; ShouldCache = DefaultShouldCache; + + // On Windows, npm is a batch file, so we need to use cmd.exe + var isWindows = OperatingSystem.IsWindows(); + ProcessFileName = isWindows ? "cmd.exe" : "npm"; + ProcessArguments = isWindows ? "/c npm run dev" : "run dev"; } public bool LogDebug => Log != null && Log.IsEnabled(LogLevel.Debug); @@ -220,16 +232,11 @@ public bool TryStartNode(string workingDirectory, out Process process) // Convert relative path to absolute path for Windows compatibility var absoluteWorkingDir = Path.GetFullPath(workingDirectory); - // On Windows, npm is a batch file, so we need to use cmd.exe - var isWindows = OperatingSystem.IsWindows(); - var fileName = isWindows ? "cmd.exe" : "npm"; - var arguments = isWindows ? "/c npm run dev" : "run dev"; - process = new Process { StartInfo = new() { - FileName = fileName, - Arguments = arguments, + FileName = ProcessFileName, + Arguments = ProcessArguments, WorkingDirectory = absoluteWorkingDir, UseShellExecute = false, RedirectStandardOutput = true, @@ -255,6 +262,23 @@ public bool TryStartNode(string workingDirectory, out Process process) try { + if (ConfigureProcess != null) + { + ConfigureProcess(process); + } + if (ConfigureWindowsProcess != null && OperatingSystem.IsWindows()) + { + ConfigureWindowsProcess(process); + } + else if (ConfigureMacProcess != null && OperatingSystem.IsMacOS()) + { + ConfigureMacProcess(process); + } + else if (ConfigureLinuxProcess != null && OperatingSystem.IsLinux()) + { + ConfigureLinuxProcess(process); + } + Log?.LogInformation($"Starting Node.js dev server in: {absoluteWorkingDir}"); if (!process.Start()) { From 3b551b56942157c9d9c55303af279768c2ffd38b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Dec 2025 14:41:29 +0800 Subject: [PATCH 084/140] Update github-push.yml --- .github/workflows/github-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-push.yml b/.github/workflows/github-push.yml index 69b1498175d..ab62c6daaaa 100644 --- a/.github/workflows/github-push.yml +++ b/.github/workflows/github-push.yml @@ -47,7 +47,7 @@ jobs: # Check if more than 48 packages if [[ ${number_of_packages} -gt 48 ]]; then echo "Pushing to GitHub Packages" - dotnet nuget push '*.nupkg' --source https://nuget.pkg.github.com/ServiceStack/index.json --api-key ${{ secrets.GITHUB_TOKEN }} + dotnet nuget push '*.nupkg' --source https://nuget.pkg.github.com/ServiceStack/index.json --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate else echo 'Less files than expected, skipping push' exit 1 From 103a0bb4c52bff119352d253ffd8e237313d614c Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Dec 2025 21:54:05 +0800 Subject: [PATCH 085/140] Improve nullable reference typing --- .../src/ServiceStack.Client/MetadataTypes.cs | 4 + .../NativeTypes/CSharp/CSharpGenerator.cs | 17 +- .../NativeTypes/Dart/DartGenerator.cs | 11 +- .../NativeTypes/Go/GoGenerator.cs | 19 +- .../NativeTypes/Php/PhpGenerator.cs | 39 +--- .../NativeTypes/Python/PythonGenerator.cs | 19 +- .../NativeTypes/Ruby/RubyGenerator.cs | 15 +- .../NativeTypes/Rust/RustGenerator.cs | 19 +- .../NativeTypes/TypeScript/MjsGenerator.cs | 2 +- .../TypeScript/TypeScriptGenerator.cs | 37 +--- .../NativeTypes/Zig/ZigGenerator.cs | 19 +- .../NorthwindAuto/App_Data/chinook.sqlite | Bin 886784 -> 886784 bytes .../ServiceInterface/OptionalService.cs | 33 ++++ .../tests/NorthwindAuto/ServiceModel/Todos.cs | 7 +- .../NativeTypesTests.cs | 169 +++++++++++------- 15 files changed, 203 insertions(+), 207 deletions(-) create mode 100644 ServiceStack/tests/NorthwindAuto/ServiceInterface/OptionalService.cs diff --git a/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs b/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs index 4d5679c2e26..474701b4060 100644 --- a/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs +++ b/ServiceStack/src/ServiceStack.Client/MetadataTypes.cs @@ -1465,6 +1465,10 @@ public static bool IsSystemType(this MetadataPropertyType prop) => public static string GetSerializedAlias(this MetadataPropertyType prop) => prop.DataMember?.Name?.SafeVarName(); + + public static bool IsRequired(this MetadataPropertyType prop) => + prop.IsRequired == true + || (prop.IsValueType == true && prop.Type != "Nullable`1"); public static MetadataPropertyType GetPrimaryKey(this List props) => props.FirstOrDefault(c => c.IsPrimaryKey == true); diff --git a/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs index bd7d9fe948b..013d71527d0 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs @@ -312,10 +312,12 @@ private string AppendType(ref StringBuilderWrapper sb, MetadataType type, string var memberValue = type.GetEnumMemberValue(i); if (memberValue != null) { - AppendAttributes(sb, new List { - new MetadataAttribute { + AppendAttributes(sb, [ + new MetadataAttribute + { Name = "EnumMember", - Args = [ + Args = + [ new() { Name = "Value", @@ -324,7 +326,7 @@ private string AppendType(ref StringBuilderWrapper sb, MetadataType type, string } ] } - }); + ]); } sb.AppendLine(value == null ? $"{name}," @@ -449,10 +451,9 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu if (Config.AddNullableAnnotations) { - if (prop.IsRequired != true && (prop.PropertyInfo?.PropertyType.IsValueType) != true) - { - propType = GetPropertyType(prop).EnsureSuffix('?'); - } + propType = prop.IsRequired() + ? GetPropertyType(prop).TrimEnd('?') + : GetPropertyType(prop).EnsureSuffix('?'); } wasAdded = AppendComments(sb, prop.Description); diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs index 3d215e62ae1..12f54592e9f 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Dart/DartGenerator.cs @@ -32,6 +32,12 @@ public DartGenerator(MetadataTypesConfig config) public static Action PrePropertyFilter { get; set; } public static Action PostPropertyFilter { get; set; } + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(DartGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !(prop.IsRequired() && !prop.IsInterface()); + } + public static List DefaultImports = new() { // "dart:collection", Required for inheriting List / ListBase // "dart:typed_data", Required for byte[] / Uint8List @@ -1025,10 +1031,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu sb.Emit(prop, Lang.Dart); PrePropertyFilter?.Invoke(sb, prop, type); - var isRequired = prop.Type != "Nullable`1" - && (prop.IsRequired == true) - && Config.AddNullableAnnotations - && !prop.IsInterface(); + var isRequired = !IsPropertyOptional(this, type, prop); string initializer = ""; if (isRequired) { diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs index 3d99e8a77a5..9fb22446242 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Go/GoGenerator.cs @@ -170,8 +170,11 @@ public GoGenerator(MetadataTypesConfig config) /// /// Whether property should be marked optional (pointer types in Go) /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(GoGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !prop.IsRequired(); + } public void Init(MetadataTypes metadata) { @@ -475,7 +478,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; // In Go, use pointer types for optional/nullable properties - var usePointer = IsPropertyOptional(this, type, prop) ?? isNullable; + var usePointer = IsPropertyOptional(this, type, prop); if (usePointer && !propType.StartsWith("*") && !propType.StartsWith("[]") && !propType.StartsWith("map[")) { propType = "*" + propType; @@ -509,16 +512,6 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } } - public static bool? DefaultIsPropertyOptional(GoGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0) return false; diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs index 41bdda824f8..fb8632a8dc6 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Php/PhpGenerator.cs @@ -32,7 +32,7 @@ public PhpGenerator(MetadataTypesConfig config) public static Action PrePropertyFilter { get; set; } public static Action PostPropertyFilter { get; set; } - + public static List DefaultImports = new() { }; @@ -228,25 +228,10 @@ public PhpGenerator(MetadataTypesConfig config) /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; - - /// - /// Helper to make Nullable properties - /// - public static bool UseNullableProperties + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(PhpGenerator generator, MetadataType type, MetadataPropertyType prop) { - set - { - if (value) - { - IsPropertyOptional = (gen, type, prop) => false; - PropertyTypeFilter = (gen, type, prop) => - prop.IsRequired == true - ? gen.GetPropertyType(prop, out _) - : gen.GetPropertyType(prop, out _) + "|null"; - } - } + return !prop.IsRequired(); } public void Init(MetadataTypes metadata) @@ -627,7 +612,7 @@ private string AppendType(ref StringBuilderWrapper sb, MetadataType type, string { var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - if (IsPropertyOptional(this, type, prop) ?? optionalProperty) + if (IsPropertyOptional(this, type, prop)) { propType += "|null"; } @@ -852,7 +837,7 @@ string QuotedGenericArg(string type) basePropNames.Add(propName); var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - var isOptional = IsPropertyOptional(this, type, prop) ?? optionalProperty; + var isOptional = IsPropertyOptional(this, type, prop); var defaultValue = (!isOptional ? GetDefaultInitializer(propType) : null) ?? "null"; sb.Emit(prop, Lang.Php); @@ -891,7 +876,7 @@ string QuotedGenericArg(string type) if (wasAdded) sb.AppendLine(); var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - var isOptional = IsPropertyOptional(this, type, prop) ?? optionalProperty; + var isOptional = IsPropertyOptional(this, type, prop); var defaultValue = (!isOptional ? GetDefaultInitializer(propType) : null) ?? "null"; wasAdded = AppendComments(sb, prop.Description); @@ -1058,16 +1043,6 @@ private string PhpPropType(MetadataPropertyType prop, string propType) return propType; } - public static bool? DefaultIsPropertyOptional(PhpGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0) return false; diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs index b0a3efba25e..21436918656 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Python/PythonGenerator.cs @@ -280,8 +280,11 @@ public static bool IgnoreAllAttributes /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(PythonGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !prop.IsRequired(); + } public void Init(MetadataTypes metadata) { @@ -778,7 +781,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu ? " = field(default_factory=dict)" : " = field(default_factory=list)"; } - else if (IsPropertyOptional(this, type, prop) ?? optionalProperty) + else if (IsPropertyOptional(this, type, prop)) { propType = asOptional(propType); } @@ -820,16 +823,6 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } } - public static bool? DefaultIsPropertyOptional(PythonGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0 || IgnoreAllAttributes) diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Ruby/RubyGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Ruby/RubyGenerator.cs index 8e57e8b119c..67cc55147c1 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Ruby/RubyGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Ruby/RubyGenerator.cs @@ -256,8 +256,11 @@ public static bool IgnoreAllAttributes /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(RubyGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !prop.IsRequired(); + } public void Init(MetadataTypes metadata) { @@ -281,14 +284,6 @@ public void Init(MetadataTypes metadata) ConflictTypeNames.Add(typeof(QueryData<,>).Name); } - public static bool? DefaultIsPropertyOptional(RubyGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (prop.IsRequired == true) - return false; - - return null; - } - public string GetCode(MetadataTypes metadata, IRequest request, INativeTypesMetadata nativeTypes) { var formatter = request.TryResolve(); diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs index c1ddb04a0cd..681f0d1a064 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Rust/RustGenerator.cs @@ -196,8 +196,11 @@ public RustGenerator(MetadataTypesConfig config) /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(RustGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !prop.IsRequired(); + } /// /// Helper to make Nullable properties @@ -508,7 +511,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - var isOptional = IsPropertyOptional(this, type, prop) ?? optionalProperty; + var isOptional = IsPropertyOptional(this, type, prop); wasAdded = AppendComments(sb, prop.Description); wasAdded = AppendDataMember(sb, prop.DataMember, dataMemberIndex++) || wasAdded; @@ -569,16 +572,6 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } } - public static bool? DefaultIsPropertyOptional(RustGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0) return false; diff --git a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/MjsGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/MjsGenerator.cs index c2e6ff8542a..cac93b59ba7 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/MjsGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/MjsGenerator.cs @@ -394,7 +394,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu { var propType = Gen.GetPropertyType(prop, out var optionalProperty); propType = TypeScriptGenerator.PropertyTypeFilter?.Invoke(Gen, type, prop) ?? propType; - var optional = TypeScriptGenerator.IsPropertyOptional(Gen, type, prop) ?? optionalProperty + var optional = TypeScriptGenerator.IsPropertyOptional(Gen, type, prop) ? "?" : ""; if (Config.AddDescriptionAsComments && !string.IsNullOrEmpty(prop.Description)) diff --git a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs index bfbb7a2ef93..0434336fe01 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs @@ -144,32 +144,19 @@ public TypeScriptGenerator(MetadataTypesConfig config) public string DictionaryDeclaration { get; set; } = "export class Dictionary { [Key: string]: T; }"; - public HashSet AddedDeclarations { get; set; } = new HashSet(); + public HashSet AddedDeclarations { get; set; } = []; public static Func PropertyTypeFilter { get; set; } /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; - - /// - /// Helper to make Nullable properties - /// - public static bool UseNullableProperties + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(TypeScriptGenerator generator, MetadataType type, MetadataPropertyType prop) { - set - { - if (value) - { - IsPropertyOptional = (gen, type, prop) => false; - PropertyTypeFilter = (gen, type, prop) => - prop.IsRequired == true - ? gen.GetPropertyType(prop, out _) - : gen.GetPropertyType(prop, out _) + "|null"; - } - } + if (generator.Config.MakePropertiesOptional) + return true; + return !prop.IsRequired(); } public void Init(MetadataTypes metadata) @@ -616,7 +603,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - var optional = IsPropertyOptional(this, type, prop) ?? optionalProperty + var optional = IsPropertyOptional(this, type, prop) ? "?" : ""; @@ -648,16 +635,6 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } } - public static bool? DefaultIsPropertyOptional(TypeScriptGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0) return false; diff --git a/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs index a6db14a886a..95f7f411e14 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/Zig/ZigGenerator.cs @@ -198,8 +198,11 @@ public ZigGenerator(MetadataTypesConfig config) /// /// Whether property should be marked optional /// - public static Func IsPropertyOptional { get; set; } = - DefaultIsPropertyOptional; + public static Func IsPropertyOptional { get; set; } = DefaultIsPropertyOptional; + public static bool DefaultIsPropertyOptional(ZigGenerator generator, MetadataType type, MetadataPropertyType prop) + { + return !prop.IsRequired(); + } public void Init(MetadataTypes metadata) { @@ -524,7 +527,7 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu var propType = GetPropertyType(prop, out var optionalProperty); propType = PropertyTypeFilter?.Invoke(this, type, prop) ?? propType; - var optional = IsPropertyOptional(this, type, prop) ?? optionalProperty; + var optional = IsPropertyOptional(this, type, prop); wasAdded = AppendComments(sb, prop.Description); wasAdded = AppendDataMember(sb, prop.DataMember, dataMemberIndex++) || wasAdded; @@ -550,16 +553,6 @@ public void AddProperties(StringBuilderWrapper sb, MetadataType type, bool inclu } } - public static bool? DefaultIsPropertyOptional(ZigGenerator generator, MetadataType type, MetadataPropertyType prop) - { - if (generator.Config.MakePropertiesOptional) - return true; - - return prop.IsRequired == null - ? null - : !prop.IsRequired.Value; - } - public bool AppendAttributes(StringBuilderWrapper sb, List attributes) { if (attributes == null || attributes.Count == 0) return false; diff --git a/ServiceStack/tests/NorthwindAuto/App_Data/chinook.sqlite b/ServiceStack/tests/NorthwindAuto/App_Data/chinook.sqlite index 89b61754dcc03af56c1c9638d2f2206719209adf..080b2761f930f7d767c37afc5aea9f0b3fc3f451 100644 GIT binary patch delta 87 zcmZpeVAe3fOeQ$cC$l6~AuYcsH?c&)n1O|XiHX5ML4kpRL2;stGpizlUYllPN^1&Z qYYJ0q3Ug};OKS>iYYJOy3VUk`M{5daYYJCu3U_M?&(;*)Iqd)$lNj3o delta 87 zcmZpeVAe3fOeQ$cC$l6~AuYcsH?c&)n1O|Xk&(ecL4kpRL1ChdGphoFUfQe1l-3l+ q))c1J6z0|xmev&3))cnZ6!z8>j@A^;))cPR6z +{ + public int Int { get; set; } + public int? NInt { get; set; } + [ValidateNotNull] + public int? NRequiredInt { get; set; } + public string String { get; set; } + public string? NString { get; set; } + [ValidateNotEmpty] + public string? NRequiredString { get; set; } + + public OptionalClass OptionalClass { get; set; } + public OptionalClass? NOptionalClass { get; set; } + [ValidateNotNull] + public OptionalClass? NRequiredOptionalClass { get; set; } + + public OptionalEnum OptionalEnum { get; set; } + public OptionalEnum? NOptionalEnum { get; set; } + [ValidateNotNull] + public OptionalEnum? NRequiredOptionalEnum { get; set; } +} + +public class OptionalService : Service +{ + public object Any(OptionalTest request) => request; +} diff --git a/ServiceStack/tests/NorthwindAuto/ServiceModel/Todos.cs b/ServiceStack/tests/NorthwindAuto/ServiceModel/Todos.cs index 08e10db7820..7ca62a726a2 100644 --- a/ServiceStack/tests/NorthwindAuto/ServiceModel/Todos.cs +++ b/ServiceStack/tests/NorthwindAuto/ServiceModel/Todos.cs @@ -18,7 +18,7 @@ public class Todo { [AutoIncrement] public long Id { get; set; } - public string Text { get; set; } = string.Empty; + public required string Text { get; set; } public bool IsFinished { get; set; } } @@ -60,8 +60,8 @@ public class UpdateTodo : IUpdateDb, IReturn [Route("/todos/{Id}", "DELETE")] public class DeleteTodos : IDeleteDb, IReturnVoid { - public long Id { get; set; } - public List Ids { get; set; } = new(); + public long? Id { get; set; } + public List? Ids { get; set; } = []; } [ValidateApiKey] @@ -69,5 +69,4 @@ public class DeleteTodos : IDeleteDb, IReturnVoid public class DeleteTodo : IDeleteDb, IReturnVoid { public long Id { get; set; } - public List Ids { get; set; } = new(); } diff --git a/ServiceStack/tests/ServiceStack.Common.Tests/NativeTypesTests.cs b/ServiceStack/tests/ServiceStack.Common.Tests/NativeTypesTests.cs index 4be7e9d2011..dff9de9d3cf 100644 --- a/ServiceStack/tests/ServiceStack.Common.Tests/NativeTypesTests.cs +++ b/ServiceStack/tests/ServiceStack.Common.Tests/NativeTypesTests.cs @@ -254,7 +254,7 @@ public void Custom_ValueTypes_defaults_to_use_opaque_strings_csharp() StringAssert.Contains("class DtoRequestWithStructProperty", src); StringAssert.Contains("public virtual string StructType { get; set; }", src); - StringAssert.Contains("public virtual string NullableStructType { get; set; }", src); + StringAssert.Contains("public virtual string? NullableStructType { get; set; }", src); } [Test] @@ -396,6 +396,79 @@ public void Does_generate_python_in_correct_order() var firstRef = src.IndexOf("ToolCall", StringComparison.Ordinal); Assert.That(toolsIndex, Is.LessThan(firstRef)); } + + [Test] + public void Does_generate_typescript_optionals() + { + var src = (string) appHost.ExecuteService(new TypesTypeScript { IncludeTypes = ["OptionalTest"] }); + Assert.That(src, Does.Contain("int: number;")); + Assert.That(src, Does.Contain("nInt?: number;")); + Assert.That(src, Does.Contain("nRequiredInt: number;")); + Assert.That(src, Does.Contain("string: string;")); + Assert.That(src, Does.Contain("nString?: string;")); + Assert.That(src, Does.Contain("nRequiredString: string;")); + Assert.That(src, Does.Contain("optionalClass: OptionalClass;")); + Assert.That(src, Does.Contain("nOptionalClass?: OptionalClass;")); + Assert.That(src, Does.Contain("nRequiredOptionalClass: OptionalClass;")); + Assert.That(src, Does.Contain("optionalEnum: OptionalEnum;")); + Assert.That(src, Does.Contain("nOptionalEnum?: OptionalEnum;")); + Assert.That(src, Does.Contain("nRequiredOptionalEnum: OptionalEnum;")); + } + + [Test] + public void Does_generate_csharp_optionals() + { + var src = (string) appHost.ExecuteService(new TypesCSharp { IncludeTypes = ["OptionalTest"] }); + Assert.That(src, Does.Contain("int Int { get; set; }")); + Assert.That(src, Does.Contain("int? NInt { get; set; }")); + Assert.That(src, Does.Contain("int NRequiredInt { get; set; }")); + Assert.That(src, Does.Contain("string String { get; set; }")); + Assert.That(src, Does.Contain("string? NString { get; set; }")); + Assert.That(src, Does.Contain("string NRequiredString { get; set; }")); + Assert.That(src, Does.Contain("OptionalClass OptionalClass { get; set; }")); + Assert.That(src, Does.Contain("OptionalClass? NOptionalClass { get; set; }")); + Assert.That(src, Does.Contain("OptionalClass NRequiredOptionalClass { get; set; }")); + Assert.That(src, Does.Contain("OptionalEnum OptionalEnum { get; set; }")); + Assert.That(src, Does.Contain("OptionalEnum? NOptionalEnum { get; set; }")); + Assert.That(src, Does.Contain("OptionalEnum NRequiredOptionalEnum { get; set; }")); + } + + [Test] + public void Does_generate_dart_optionals() + { + var src = (string) appHost.ExecuteService(new TypesDart { IncludeTypes = ["OptionalTest"] }); + Assert.That(src, Does.Contain("int Int = 0;")); + Assert.That(src, Does.Contain("int? nInt;")); + Assert.That(src, Does.Contain("int nRequiredInt = 0;")); + Assert.That(src, Does.Contain("String string = \"\";")); + Assert.That(src, Does.Contain("String? nString;")); + Assert.That(src, Does.Contain("String nRequiredString = \"\";")); + Assert.That(src, Does.Contain("OptionalClass optionalClass;")); + Assert.That(src, Does.Contain("OptionalClass? nOptionalClass;")); + Assert.That(src, Does.Contain("OptionalClass nRequiredOptionalClass;")); + Assert.That(src, Does.Contain("OptionalEnum optionalEnum;")); + Assert.That(src, Does.Contain("OptionalEnum? nOptionalEnum;")); + Assert.That(src, Does.Contain("OptionalEnum nRequiredOptionalEnum;")); + } + + [Test] + public void Does_generate_swift_optionals() + { + // TODO: Investigate making it more typed + var src = (string) appHost.ExecuteService(new TypesPython { IncludeTypes = ["OptionalTest"] }); + Assert.That(src, Does.Contain("int_: int")); + Assert.That(src, Does.Contain("n_int: Optional[int] = None")); + Assert.That(src, Does.Contain("n_required_int: Optional[int] = None")); + Assert.That(src, Does.Contain("string: Optional[str] = None")); + Assert.That(src, Does.Contain("n_string: Optional[str] = None")); + Assert.That(src, Does.Contain("n_required_string: Optional[str] = None")); + Assert.That(src, Does.Contain("optional_class: Optional[OptionalClass] = None")); + Assert.That(src, Does.Contain("n_optional_class: Optional[OptionalClass] = None")); + Assert.That(src, Does.Contain("n_required_optional_class: Optional[OptionalClass] = None")); + Assert.That(src, Does.Contain("optional_enum: Optional[OptionalEnum] = None")); + Assert.That(src, Does.Contain("n_optional_enum: Optional[OptionalEnum] = None")); + Assert.That(src, Does.Contain("n_required_optional_enum: Optional[OptionalEnum] = None")); + } } public class NativeTypesTestService : Service @@ -420,6 +493,35 @@ public void Any(UpdateItem request) {} public void Any(DeleteItem request) {} } +public record class OptionalClass(int Id); +public enum OptionalEnum { Value1 } + +public class OptionalTest : IReturn +{ + public int Int { get; set; } + public int? NInt { get; set; } + [ValidateNotNull] + public int? NRequiredInt { get; set; } + public string String { get; set; } + public string? NString { get; set; } + [ValidateNotEmpty] + public string? NRequiredString { get; set; } + + public OptionalClass OptionalClass { get; set; } + public OptionalClass? NOptionalClass { get; set; } + [ValidateNotNull] + public OptionalClass? NRequiredOptionalClass { get; set; } + + public OptionalEnum OptionalEnum { get; set; } + public OptionalEnum? NOptionalEnum { get; set; } + [ValidateNotNull] + public OptionalEnum? NRequiredOptionalEnum { get; set; } +} +public class OptionalService : Service +{ + public object Any(OptionalTest request) => request; +} + public class Dto : IReturn { public EmbeddedResponse ReferencedType { get; set; } @@ -434,7 +536,6 @@ public class DtoResponse public class EmbeddedResponse { } public class EmbeddedRequest { } - [Route("/Request1/", "GET")] public partial class GetRequest1 : IReturn>, IGet { } @@ -466,109 +567,81 @@ public struct StructType [Route("/v1/chat/completions", "POST")] public class OpenAiChatCompletion : OpenAiChat, IPost, IReturn { - public string? RefId { get; set; } - - public string? Provider { get; set; } - - public string? Tag { get; set; } } [DataContract] public class OpenAiChat { - [DataMember(Name = "messages")] public List Messages { get; set; } - [DataMember(Name = "model")] public string Model { get; set; } - [DataMember(Name = "frequency_penalty")] public double? FrequencyPenalty { get; set; } - [DataMember(Name = "logit_bias")] public Dictionary? LogitBias { get; set; } - [DataMember(Name = "logprobs")] public bool? LogProbs { get; set; } - [DataMember(Name = "top_logprobs")] public int? TopLogProbs { get; set; } - [DataMember(Name = "max_tokens")] public int? MaxTokens { get; set; } - [DataMember(Name = "n")] public int? N { get; set; } - [DataMember(Name = "presence_penalty")] public double? PresencePenalty { get; set; } - [DataMember(Name = "response_format")] public OpenAiResponseFormat? ResponseFormat { get; set; } - [DataMember(Name = "seed")] public int? Seed { get; set; } - [DataMember(Name = "stop")] public List? Stop { get; set; } - [DataMember(Name = "stream")] public bool? Stream { get; set; } - [DataMember(Name = "temperature")] public double? Temperature { get; set; } - [DataMember(Name = "top_p")] public double? TopP { get; set; } - [DataMember(Name = "tools")] public List? Tools { get; set; } - [DataMember(Name = "user")] public string? User { get; set; } } - [DataContract] public class OpenAiMessage { - [DataMember(Name = "content")] public string Content { get; set; } - [DataMember(Name = "role")] public string Role { get; set; } - [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "tool_calls")] public ToolCall[]? ToolCalls { get; set; } - [DataMember(Name = "tool_call_id")] public string? ToolCallId { get; set; } } @@ -576,7 +649,6 @@ public class OpenAiMessage [DataContract] public class OpenAiTools { - [DataMember(Name = "type")] public OpenAiToolType Type { get; set; } } @@ -590,15 +662,12 @@ public enum OpenAiToolType [DataContract] public class OpenAiToolFunction { - [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "description")] public string? Description { get; set; } - [DataMember(Name = "parameters")] public Dictionary? Parameters { get; set; } } @@ -624,30 +693,23 @@ public enum ResponseFormat [DataContract] public class OpenAiChatResponse { - [DataMember(Name = "id")] public string Id { get; set; } - [DataMember(Name = "choices")] public List Choices { get; set; } - [DataMember(Name = "created")] public long Created { get; set; } - [DataMember(Name = "model")] public string Model { get; set; } - [DataMember(Name = "system_fingerprint")] public string SystemFingerprint { get; set; } - [DataMember(Name = "object")] public string Object { get; set; } - [DataMember(Name = "usage")] public OpenAiUsage Usage { get; set; } @@ -656,18 +718,14 @@ public class OpenAiChatResponse public ResponseStatus? ResponseStatus { get; set; } } - [DataContract] public class OpenAiUsage { - [DataMember(Name = "completion_tokens")] public int CompletionTokens { get; set; } - [DataMember(Name = "prompt_tokens")] public int PromptTokens { get; set; } - [DataMember(Name = "total_tokens")] public int TotalTokens { get; set; } @@ -675,14 +733,11 @@ public class OpenAiUsage public class Choice { - [DataMember(Name = "finish_reason")] public string FinishReason { get; set; } - [DataMember(Name = "index")] public int Index { get; set; } - [DataMember(Name = "message")] public ChoiceMessage Message { get; set; } @@ -691,80 +746,62 @@ public class Choice [DataContract] public class ChoiceMessage { - [DataMember(Name = "content")] public string Content { get; set; } - [DataMember(Name = "tool_calls")] public ToolCall[] ToolCalls { get; set; } - [DataMember(Name = "role")] public string Role { get; set; } } - [DataContract] public class ToolCall { - [DataMember(Name = "id")] public string Id { get; set; } - [DataMember(Name = "type")] public string Type { get; set; } - [DataMember(Name = "function")] public string Function { get; set; } } - [DataContract] public class ToolFunction { - [DataMember(Name = "name")] public string Name { get; set; } - [DataMember(Name = "arguments")] public string Arguments { get; set; } } - [DataContract] public class Logprobs { - [DataMember(Name = "content")] public LogprobItem[] Content { get; set; } } - [DataContract] public class LogprobItem { - [DataMember(Name = "token")] public string Token { get; set; } - [DataMember(Name = "logprob")] public double Logprob { get; set; } - [DataMember(Name = "bytes")] public byte[] Bytes { get; set; } - [DataMember(Name = "top_logprobs")] public LogprobItem[] TopLogprobs { get; set; } } - public class Items { public List Results { get; set; } From 5d070aeedde12abfed1969425d05fae8d759e0d1 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Dec 2025 22:25:14 +0800 Subject: [PATCH 086/140] npm to v10.0.2 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 9e8ead15131..54c83527011 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 10.01m; + public static decimal ServiceStackVersion = 10.02m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index cf458ce7ae0..56dafe6340b 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index 624da513732..6e8b7bd735c 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.1 + 10.0.2 latest false From 1d2fa1b2699f4d9761d5a8afe059d56e59b97ff0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Dec 2025 22:50:04 +0800 Subject: [PATCH 087/140] bump to v10.0.3 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 54c83527011..26f351ba39b 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 10.02m; + public static decimal ServiceStackVersion = 10.03m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index 56dafe6340b..cd064866d5c 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index 6e8b7bd735c..ac2c5b19601 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.2 + 10.0.3 latest false From f08e33a048351883a6216a4d0b308f9a4e9ff580 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 5 Dec 2025 02:57:29 +0800 Subject: [PATCH 088/140] Add more NodeProxy helpers --- .../src/ServiceStack.Extensions/NodeProxy.cs | 69 ++++++++++++++++++- ServiceStack/src/ServiceStack/HostContext.cs | 3 + 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 56bb9fff4bb..10109a07c8d 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -226,6 +226,27 @@ public bool RemoveCacheEntry(string key) return false; } } + + public bool IsPortAvailable() => HostContext.IsPortAvailable(Client.BaseAddress!.Port); + + public bool WaitUntilAvailable(TimeSpan timeout) + { + var baseUrl = Client.BaseAddress!.ToString(); + var startedAt = DateTime.UtcNow; + while (DateTime.UtcNow - startedAt < timeout) + { + try + { + var response = baseUrl.GetStringFromUrl(); + return true; + } + catch (Exception ex) + { + Thread.Sleep(200); + } + } + return false; + } public bool TryStartNode(string workingDirectory, out Process process) { @@ -622,7 +643,7 @@ await destination.SendAsync( } /// - /// Run Next.js dev server if not already running + /// Run Next.js dev server if not already running by checking for lock file /// public static System.Diagnostics.Process? RunNodeProcess(this WebApplication app, NodeProxy proxy, @@ -673,6 +694,52 @@ await destination.SendAsync( return null; } + + + /// + /// Run Next.js dev server if not already running by checking for port availability + /// + public static System.Diagnostics.Process? RunNodeProcess(this WebApplication app, + NodeProxy proxy, + string workingDirectory, + bool registerExitHandler=true) + { + var process = app.StartNodeProcess(proxy, workingDirectory); + if (process != null) + { + proxy.Log?.LogInformation("Started Next.js dev server"); + } + else + { + proxy.Log?.LogInformation("Next.js dev server already running"); + } + return process; + } + + public static System.Diagnostics.Process? StartNodeProcess(this WebApplication app, + NodeProxy proxy, + string workingDirectory) + { + if (proxy.IsPortAvailable()) + { + if (!proxy.TryStartNode(workingDirectory, out var process)) + return null; + + process.Exited += (s, e) => { + proxy.Log?.LogDebug("Exited: " + process.ExitCode); + }; + + app.Lifetime.ApplicationStopping.Register(() => { + if (!process.HasExited) + { + proxy.Log?.LogDebug("Terminating process: " + process.Id); + process.Kill(entireProcessTree: true); + } + }); + return process; + } + return null; + } public static IEndpointConventionBuilder MapFallbackToNode(this WebApplication app, NodeProxy proxy) { diff --git a/ServiceStack/src/ServiceStack/HostContext.cs b/ServiceStack/src/ServiceStack/HostContext.cs index 339090af9f0..e4af5debbe8 100644 --- a/ServiceStack/src/ServiceStack/HostContext.cs +++ b/ServiceStack/src/ServiceStack/HostContext.cs @@ -349,6 +349,9 @@ public static int FindFreeTcpPort(int startingFrom=5000, int endingAt=65535) return -1; } + + public static bool IsPortAvailable(int port) => System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties() + .GetActiveTcpListeners().All(x => x.Port != port); public static void ConfigureServices(Action configure) { From 9ab73ddde9208d1866800b127b59591c3e7ccb55 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 5 Dec 2025 10:15:39 +0800 Subject: [PATCH 089/140] Improve the experience of the NodeProxy connecting error page --- .../src/ServiceStack.Extensions/NodeProxy.cs | 239 +++++++++++++++++- 1 file changed, 238 insertions(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 10109a07c8d..8e4c79deb24 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Sockets; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -452,6 +453,230 @@ public async Task HttpToNode(HttpContext context) await response.Content.CopyToAsync(context.Response.Body, context.RequestAborted); } + + public string ConnectingHtml { get; set; } = """ + + + + + + Connecting to Development Server + + + +
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+

Starting Development Server

+

Preparing your development environment

+
+
+
+
+
+
+ + Connecting to Node.js server... +
+
+ + + + """; } public static class ProxyExtensions @@ -743,6 +968,18 @@ await destination.SendAsync( public static IEndpointConventionBuilder MapFallbackToNode(this WebApplication app, NodeProxy proxy) { - return app.MapFallback(proxy.HttpToNode); + return app.MapFallback(async (HttpContext context) => + { + try + { + await proxy.HttpToNode(context); + } + catch (SocketException) + { + context.Response.StatusCode = 503; + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(proxy.ConnectingHtml); + } + }); } } From 6acce556ce6e53c28aaff8b1b6d89b461fff0f9b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 5 Dec 2025 11:42:04 +0800 Subject: [PATCH 090/140] Also handle exceptions from NodeProxy Hmr connections --- .../src/ServiceStack.Extensions/NodeProxy.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 8e4c79deb24..68e7573bf89 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -745,7 +745,16 @@ public static IEndpointConventionBuilder MapNextHmr(this WebApplication app, Nod { if (context.WebSockets.IsWebSocketRequest) { - await WebSocketToNode(context, proxy.Client.BaseAddress!); + try + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + catch (WebSocketException ex) when (ex.InnerException is HttpRequestException { InnerException: SocketException }) + { + context.Response.StatusCode = 503; + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(proxy.ConnectingHtml); + } } else { @@ -767,7 +776,16 @@ public static void MapViteHmr(this WebApplication app, NodeProxy proxy) // Check if this is a WebSocket upgrade request if (context.WebSockets.IsWebSocketRequest) { - await WebSocketToNode(context, proxy.Client.BaseAddress!); + try + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + catch (WebSocketException ex) when (ex.InnerException is HttpRequestException { InnerException: SocketException }) + { + context.Response.StatusCode = 503; + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(proxy.ConnectingHtml); + } } else { @@ -974,7 +992,7 @@ public static IEndpointConventionBuilder MapFallbackToNode(this WebApplication a { await proxy.HttpToNode(context); } - catch (SocketException) + catch (HttpRequestException ex) when (ex.InnerException is SocketException) { context.Response.StatusCode = 503; context.Response.ContentType = "text/html"; From 52751cad4dd08a2392ca278dd0b8a256c93e9553 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 5 Dec 2025 13:24:28 +0800 Subject: [PATCH 091/140] bump to v10.0.4 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 26f351ba39b..2174c0d678c 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 10.03m; + public static decimal ServiceStackVersion = 10.04m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index cd064866d5c..417747fad41 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index ac2c5b19601..d0962326265 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.3 + 10.0.4 latest false From ec61ada0df9d4b975fd50c8b2262ba5bf36a0316 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 5 Dec 2025 14:46:41 +0800 Subject: [PATCH 092/140] bump to v10.0.5 --- ServiceStack.Aws/src/Directory.Build.props | 2 +- ServiceStack.Aws/tests/Directory.Build.props | 2 +- ServiceStack.Azure/src/Directory.Build.props | 2 +- ServiceStack.Azure/tests/Directory.Build.props | 2 +- ServiceStack.Blazor/src/Directory.Build.props | 2 +- ServiceStack.Blazor/tests/Directory.Build.props | 2 +- ServiceStack.Logging/src/Directory.Build.props | 2 +- ServiceStack.Logging/tests/Directory.Build.props | 2 +- ServiceStack.OrmLite/src/Directory.Build.props | 2 +- ServiceStack.OrmLite/tests/Directory.Build.props | 2 +- ServiceStack.Redis/src/Directory.Build.props | 2 +- ServiceStack.Redis/tests/Directory.Build.props | 2 +- ServiceStack.Stripe/src/Directory.Build.props | 2 +- ServiceStack.Stripe/tests/Directory.Build.props | 2 +- ServiceStack.Text/src/Directory.Build.props | 2 +- ServiceStack.Text/src/ServiceStack.Text/Env.cs | 2 +- ServiceStack.Text/tests/Directory.Build.props | 2 +- ServiceStack/src/Directory.Build.props | 2 +- ServiceStack/tests/Directory.Build.props | 2 +- build/src/Directory.Build.props | 2 +- build/tests/Directory.Build.props | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ServiceStack.Aws/src/Directory.Build.props b/ServiceStack.Aws/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Aws/src/Directory.Build.props +++ b/ServiceStack.Aws/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Aws/tests/Directory.Build.props b/ServiceStack.Aws/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Aws/tests/Directory.Build.props +++ b/ServiceStack.Aws/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Azure/src/Directory.Build.props b/ServiceStack.Azure/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Azure/src/Directory.Build.props +++ b/ServiceStack.Azure/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Azure/tests/Directory.Build.props b/ServiceStack.Azure/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Azure/tests/Directory.Build.props +++ b/ServiceStack.Azure/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Blazor/src/Directory.Build.props b/ServiceStack.Blazor/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Blazor/src/Directory.Build.props +++ b/ServiceStack.Blazor/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Blazor/tests/Directory.Build.props b/ServiceStack.Blazor/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Blazor/tests/Directory.Build.props +++ b/ServiceStack.Blazor/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Logging/src/Directory.Build.props b/ServiceStack.Logging/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Logging/src/Directory.Build.props +++ b/ServiceStack.Logging/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Logging/tests/Directory.Build.props b/ServiceStack.Logging/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Logging/tests/Directory.Build.props +++ b/ServiceStack.Logging/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.OrmLite/src/Directory.Build.props b/ServiceStack.OrmLite/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.OrmLite/src/Directory.Build.props +++ b/ServiceStack.OrmLite/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.OrmLite/tests/Directory.Build.props b/ServiceStack.OrmLite/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.OrmLite/tests/Directory.Build.props +++ b/ServiceStack.OrmLite/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Redis/src/Directory.Build.props b/ServiceStack.Redis/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Redis/src/Directory.Build.props +++ b/ServiceStack.Redis/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Redis/tests/Directory.Build.props b/ServiceStack.Redis/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Redis/tests/Directory.Build.props +++ b/ServiceStack.Redis/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Stripe/src/Directory.Build.props b/ServiceStack.Stripe/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Stripe/src/Directory.Build.props +++ b/ServiceStack.Stripe/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Stripe/tests/Directory.Build.props b/ServiceStack.Stripe/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Stripe/tests/Directory.Build.props +++ b/ServiceStack.Stripe/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack.Text/src/Directory.Build.props b/ServiceStack.Text/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack.Text/src/Directory.Build.props +++ b/ServiceStack.Text/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack.Text/src/ServiceStack.Text/Env.cs b/ServiceStack.Text/src/ServiceStack.Text/Env.cs index 2174c0d678c..37412b08668 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/Env.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/Env.cs @@ -136,7 +136,7 @@ internal static void UpdateServerUserAgent() public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 10.04m; + public static decimal ServiceStackVersion = 10.05m; public static bool IsLinux { get; set; } diff --git a/ServiceStack.Text/tests/Directory.Build.props b/ServiceStack.Text/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack.Text/tests/Directory.Build.props +++ b/ServiceStack.Text/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/ServiceStack/src/Directory.Build.props b/ServiceStack/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/ServiceStack/src/Directory.Build.props +++ b/ServiceStack/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/ServiceStack/tests/Directory.Build.props b/ServiceStack/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/ServiceStack/tests/Directory.Build.props +++ b/ServiceStack/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false diff --git a/build/src/Directory.Build.props b/build/src/Directory.Build.props index 417747fad41..aae47204fd0 100644 --- a/build/src/Directory.Build.props +++ b/build/src/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 ServiceStack ServiceStack, Inc. © 2008-2022 ServiceStack, Inc diff --git a/build/tests/Directory.Build.props b/build/tests/Directory.Build.props index d0962326265..08a27543d31 100644 --- a/build/tests/Directory.Build.props +++ b/build/tests/Directory.Build.props @@ -1,7 +1,7 @@ - 10.0.4 + 10.0.5 latest false From 5c05783bd2017cac32732c6be605b60b84daaa65 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 13 Dec 2025 13:44:27 +0800 Subject: [PATCH 093/140] Set InsertTsNoCheck=true by default to avoid user-specific type errors for generated code --- .../NativeTypes/TypeScript/TypeScriptGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs index 0434336fe01..e7103545e0c 100644 --- a/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs +++ b/ServiceStack/src/ServiceStack/NativeTypes/TypeScript/TypeScriptGenerator.cs @@ -119,8 +119,8 @@ public TypeScriptGenerator(MetadataTypesConfig config) public static Func, List> FilterTypes { get; set; } = DefaultFilterTypes; public static List DefaultFilterTypes(List types) => types.OrderTypesByDeps(); - - public static bool InsertTsNoCheck { get; set; } + + public static bool InsertTsNoCheck { get; set; } = true; /// /// Add Code to top of generated code From 7784e8497a0f4cd814212a949df3cf6ebe0327e0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 30 Dec 2025 18:20:53 +0800 Subject: [PATCH 094/140] Update LicenseUtils.cs --- ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs index fc4143f3405..ca08d4242a4 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs @@ -181,7 +181,7 @@ public static void AssertEvaluationLicense() "See https://servicestack.net to upgrade to a valid license.").Trace(); } - private static readonly int[] revokedSubs = [4018, 4019, 4041, 4331, 4581]; + private static readonly int[] revokedSubs = [4018, 4019, 4041, 4331, 4581, 23509, 22948, 20975, 19989]; private class __ActivatedLicense { From 7dc7774d5ada6ffb8f62328907543f1d3d0ed7cc Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 30 Dec 2025 18:22:22 +0800 Subject: [PATCH 095/140] Update LicenseUtils.cs --- ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs index ca08d4242a4..dbfa650907e 100644 --- a/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs +++ b/ServiceStack.Text/src/ServiceStack.Text/LicenseUtils.cs @@ -181,7 +181,7 @@ public static void AssertEvaluationLicense() "See https://servicestack.net to upgrade to a valid license.").Trace(); } - private static readonly int[] revokedSubs = [4018, 4019, 4041, 4331, 4581, 23509, 22948, 20975, 19989]; + private static readonly int[] revokedSubs = [4018, 4019, 4041, 4331, 4581, 23509]; private class __ActivatedLicense { From 941d0c9ba2158f43ca831f90da35a14cbfb34387 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sun, 4 Jan 2026 17:10:57 +0800 Subject: [PATCH 096/140] don't log if StartTimestamp is not set --- ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteLog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteLog.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteLog.cs index 69a3e26268c..130e981b43e 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteLog.cs +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteLog.cs @@ -17,7 +17,7 @@ internal static T WithLog(this IDbCommand cmd, T result, ILog log = null) #if NET8_0_OR_GREATER if (log.IsDebugEnabled) { - if (cmd is OrmLite.OrmLiteCommand ormCmd) + if (cmd is OrmLite.OrmLiteCommand { StartTimestamp: > 0 } ormCmd) { var elapsed = ormCmd.GetElapsedTime(); if (elapsed == TimeSpan.Zero) From 750e6f460b79de5faefa5a66b4f4ad44d26f9eed Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 12 Jan 2026 10:03:39 +0800 Subject: [PATCH 097/140] Improve WebSocket handling in NodeProxy --- .../src/ServiceStack.Extensions/NodeProxy.cs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs index 68e7573bf89..33c7ab662b7 100644 --- a/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs +++ b/ServiceStack/src/ServiceStack.Extensions/NodeProxy.cs @@ -332,6 +332,14 @@ public async Task HttpToNode(HttpContext context) { var request = context.Request; + // WebSocket requests should not be handled by HTTP proxy + if (context.WebSockets.IsWebSocketRequest) + { + throw new InvalidOperationException( + "WebSocket requests should be handled by WebSocketToNode, not HttpToNode. " + + "Ensure MapViteHmr or MapNextHmr middleware is registered before MapFallbackToNode."); + } + var cacheKey = request.Path.Value ?? string.Empty; // Handle ?clear commands even if this request itself isn't cacheable @@ -702,9 +710,27 @@ public static void MapNotFoundToNode(this WebApplication app, NodeProxy proxy, s return; } - // Clear the 404 and let Next handle it + // Clear the 404 and let Node handle it context.Response.Clear(); - await proxy.HttpToNode(context); + + // Handle WebSocket requests + if (context.WebSockets.IsWebSocketRequest) + { + try + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + catch (WebSocketException ex) when (ex.InnerException is HttpRequestException { InnerException: SocketException }) + { + context.Response.StatusCode = 503; + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(proxy.ConnectingHtml); + } + } + else + { + await proxy.HttpToNode(context); + } } }); } @@ -988,6 +1014,23 @@ public static IEndpointConventionBuilder MapFallbackToNode(this WebApplication a { return app.MapFallback(async (HttpContext context) => { + // Handle WebSocket requests + if (context.WebSockets.IsWebSocketRequest) + { + try + { + await WebSocketToNode(context, proxy.Client.BaseAddress!); + } + catch (WebSocketException ex) when (ex.InnerException is HttpRequestException { InnerException: SocketException }) + { + context.Response.StatusCode = 503; + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(proxy.ConnectingHtml); + } + return; + } + + // Handle HTTP requests try { await proxy.HttpToNode(context); From 73ba65598842e9d4058fb66ec90472de81c0faca Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 17 Jan 2026 19:30:29 +0800 Subject: [PATCH 098/140] Don't try to convert to int userId in NetCoreIdentityAuthProvider --- .../src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs b/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs index c8d62b8a31d..6e67e27d3f4 100644 --- a/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs +++ b/ServiceStack/src/ServiceStack/Auth/NetCoreIdentityAuthProvider.cs @@ -280,7 +280,7 @@ public async Task ConvertSessionToPrincipalAsync(IRequest req, { await using (authRepo as IAsyncDisposable) { - var roles = await authRepo.GetRolesAsync(session.UserAuthId.ToInt(), token: token).ConfigAwait(); + var roles = await authRepo.GetRolesAsync(session.UserAuthId, token: token).ConfigAwait(); foreach (var role in roles) { claims.Add(new Claim(RoleClaimType, role, Issuer)); From beb1ec0610755a20270bf725d2a3ff3e0a258ac0 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 19 Jan 2026 23:44:43 +0800 Subject: [PATCH 099/140] Add API to queue an API Background Job --- .../ServiceInterface/CheckUrlsCommand.cs | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/ServiceStack/tests/NorthwindAuto/ServiceInterface/CheckUrlsCommand.cs b/ServiceStack/tests/NorthwindAuto/ServiceInterface/CheckUrlsCommand.cs index 96303264f73..dadee8b7f3e 100644 --- a/ServiceStack/tests/NorthwindAuto/ServiceInterface/CheckUrlsCommand.cs +++ b/ServiceStack/tests/NorthwindAuto/ServiceInterface/CheckUrlsCommand.cs @@ -13,14 +13,14 @@ public class QueueCheckUrls : IReturn [Input(Type = "textarea")] public string Urls { get; set; } } -public class CheckUrlServices(IBackgroundJobs jobs) : Service +public class CheckUrlServices(IBackgroundJobs jobs, IHttpClientFactory httpClientFactory) : Service { public object Any(QueueCheckUrls request) { var jobRef = jobs.EnqueueCommand(new CheckUrls { Urls = request.Urls.Split("\n").ToList() - },new() + }, new() { Worker = nameof(CheckUrlsCommand), Callback = nameof(CheckUrlsReportCommand) @@ -31,17 +31,58 @@ public object Any(QueueCheckUrls request) JobRef = jobRef }; } + + public async Task Any(CheckUrl request) + { + var url = request.Url.Trim(); + using var client = httpClientFactory.CreateClient(); + var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url)); + response.EnsureSuccessStatusCode(); + return new CheckUrlResponse { Url = url, Result = true }; + } + + public object Any(QueueCheckUrlApi request) + { + var jobRef = jobs.EnqueueApi(new CheckUrl { Url = request.Url }, + new() { + Worker = nameof(CheckUrlsCommand), + Callback = nameof(CheckUrlsReportCommand) + }); + + return new QueueCheckUrlsResponse + { + JobRef = jobRef + }; + } } public class CheckUrls { public List Urls { get; set; } = []; } + public class CheckUrlsResult { public Dictionary UrlStatuses { get; set; } = []; } +public class CheckUrl : IReturn +{ + [ValidateNotEmpty] public required string Url { get; set; } +} + +public class CheckUrlResponse : IHasResponseStatus +{ + public string Url { get; set; } + public bool Result { get; set; } + public ResponseStatus? ResponseStatus { get; set; } +} + +public class QueueCheckUrlApi : IReturn +{ + [ValidateNotEmpty] public required string Url { get; set; } +} + // Command implementation public class CheckUrlsCommand( IHttpClientFactory httpClientFactory, @@ -54,13 +95,14 @@ protected override async Task RunAsync(CheckUrls request, Cance { UrlStatuses = new Dictionary() }; - - var job = Request.GetBackgroundJob(); - + + var job = Request.TryGetBackgroundJob(); + // Create a JobLogger to log messages to the background job var log = Request.CreateJobLogger(jobs, logger); log.LogInformation("Checking {Count} URLs", request.Urls.Count); + using var client = httpClientFactory.CreateClient(); // Set a timeout of 3 seconds for each request client.Timeout = TimeSpan.FromSeconds(3); @@ -95,8 +137,8 @@ protected override async Task RunAsync(CheckUrls request, Cance // BatchId = batchId, // }); } - - log.LogInformation("Finished checking URLs, {Up} up, {Down} down", + + log.LogInformation("Finished checking URLs, {Up} up, {Down} down", result.UrlStatuses.Values.Count(x => x), result.UrlStatuses.Values.Count(x => !x)); return result; @@ -107,7 +149,6 @@ public class CheckUrlsReportCommand( ILogger logger, IBackgroundJobs jobs) : AsyncCommand { - protected override async Task RunAsync(CheckUrlsResult request, CancellationToken token) { var log = Request.CreateJobLogger(jobs, logger); From 44427b5e1afd4355eb96e0678f9bf5eb53078e72 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 19 Jan 2026 23:44:58 +0800 Subject: [PATCH 100/140] Use Request when Querying Api Jobs --- .../src/ServiceStack.Jobs/BackgroundJobs.cs | 14 +++++++------- ServiceStack/src/ServiceStack.Server/DbJobs.cs | 13 +++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs index 1cb35f5acdf..cd00ad924c5 100644 --- a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs +++ b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs @@ -983,8 +983,8 @@ private void LoadJobQueue() .Where(j => Sql.In(j.Id, db.From() .Where(x => x.State == BackgroundJobState.Completed - && x.DurationMs > 0 - && x.RequestType == CommandResult.Command) + && x.DurationMs > 0 + && x.RequestType == CommandResult.Command) .GroupBy(x => new { x.Id, x.Command, x.Worker }) .SelectDistinct(x => x.Id))) .GroupBy(x => new { x.Command, x.Worker }) @@ -999,13 +999,13 @@ private void LoadJobQueue() .Where(j => Sql.In(j.Id, db.From() .Where(x => x.State == BackgroundJobState.Completed - && x.DurationMs > 0 - && x.RequestType == CommandResult.Api) - .GroupBy(x => new { x.Id, x.Command, x.Worker }) + && x.DurationMs > 0 + && x.RequestType == CommandResult.Api) + .GroupBy(x => new { x.Id, x.Request, x.Worker }) .SelectDistinct(x => x.Id))) - .GroupBy(x => new { x.Command, x.Worker }) + .GroupBy(x => new { x.Request, x.Worker }) .Select(x => new { - Command = Sql.Custom($"IIF({columns.Worker} is null, {columns.Command}, {columns.Command} || '.' || {columns.Worker})"), + Request = Sql.Custom($"IIF({columns.Worker} is null, {columns.Request}, {columns.Request} || '.' || {columns.Worker})"), DurationMs = Sql.Custom($"CASE WHEN SUM({columns.DurationMs}) > {int.MaxValue} THEN {int.MaxValue} ELSE SUM({columns.DurationMs}) END"), })); lastApiDurations = new(apiDurations); diff --git a/ServiceStack/src/ServiceStack.Server/DbJobs.cs b/ServiceStack/src/ServiceStack.Server/DbJobs.cs index fbbcbfece5d..fd60639a586 100644 --- a/ServiceStack/src/ServiceStack.Server/DbJobs.cs +++ b/ServiceStack/src/ServiceStack.Server/DbJobs.cs @@ -981,8 +981,8 @@ private void LoadJobQueue() using var db = feature.OpenDb(); var requestId = Guid.NewGuid().ToString("N"); var now = DateTime.UtcNow; - var sqlCommandWorker = feature.Dialect.SqlConcat([columns.Command, "'.'", columns.Worker]); + var sqlCommandWorker = feature.Dialect.SqlConcat([columns.Command, "'.'", columns.Worker]); var commandDurations = db.Dictionary( db.From() .Where(j => Sql.In(j.Id, @@ -999,18 +999,19 @@ private void LoadJobQueue() })); lastCommandDurations = new(commandDurations); + var sqlRequestWorker = feature.Dialect.SqlConcat([columns.Request, "'.'", columns.Worker]); var apiDurations = db.Dictionary( db.From() .Where(j => Sql.In(j.Id, db.From() .Where(x => x.State == BackgroundJobState.Completed - && x.DurationMs > 0 - && x.RequestType == CommandResult.Api) - .GroupBy(x => new { x.Id, x.Command, x.Worker }) + && x.DurationMs > 0 + && x.RequestType == CommandResult.Api) + .GroupBy(x => new { x.Id, x.Request, x.Worker }) .SelectDistinct(x => x.Id))) - .GroupBy(x => new { x.Command, x.Worker }) + .GroupBy(x => new { x.Request, x.Worker }) .Select(x => new { - Command = Sql.Custom($"CASE WHEN {columns.Worker} is null THEN {columns.Command} ELSE {sqlCommandWorker} END"), + Request = Sql.Custom($"CASE WHEN {columns.Worker} is null THEN {columns.Request} ELSE {sqlRequestWorker} END"), DurationMs = Sql.Custom($"CASE WHEN SUM({columns.DurationMs}) > {int.MaxValue} THEN {int.MaxValue} ELSE SUM({columns.DurationMs}) END"), })); lastApiDurations = new(apiDurations); From 43e07f4180fc2332cbac27779ab827078bed3472 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 20 Jan 2026 19:04:13 +0800 Subject: [PATCH 101/140] tidy --- .../WriteExpressionCommandExtensions.cs | 4 +- .../ToInsertAndUpdateStatementTests.cs | 94 +++++++++---------- 2 files changed, 44 insertions(+), 54 deletions(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/WriteExpressionCommandExtensions.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/WriteExpressionCommandExtensions.cs index 7f48d8af5a4..2cd667c1443 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/WriteExpressionCommandExtensions.cs +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/Expressions/WriteExpressionCommandExtensions.cs @@ -200,11 +200,11 @@ internal static string GetUpdateOnlyWhereExpression(this IOrmLiteDialectProvi if (modelDef.RowVersion == null || !updateFields.TryGetValue(ModelDefinition.RowVersionName, out var rowVersion)) { - args = new[] { idValue }; + args = [idValue]; return "(" + dialectProvider.GetQuotedColumnName(pkField) + " = {0})"; } - args = new[] { idValue, rowVersion }; + args = [idValue, rowVersion]; return "(" + dialectProvider.GetQuotedColumnName(pkField) + " = {0} AND " + dialectProvider.GetRowVersionColumn(modelDef.RowVersion) + " = {1})"; } diff --git a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ToInsertAndUpdateStatementTests.cs b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ToInsertAndUpdateStatementTests.cs index 5f38b1b8ca2..83a625b5348 100644 --- a/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ToInsertAndUpdateStatementTests.cs +++ b/ServiceStack.OrmLite/tests/ServiceStack.OrmLite.Tests/ToInsertAndUpdateStatementTests.cs @@ -10,88 +10,78 @@ public class ToInsertAndUpdateStatementTests(DialectContext context) : OrmLitePr [Test] public void Can_use_ToUpdateStatement_to_generate_inline_SQL() { - using (var db = OpenDbConnection()) - { - db.DropAndCreateTable(); - db.InsertAll(Person.Rockstars); + using var db = OpenDbConnection(); + db.DropAndCreateTable(); + db.InsertAll(Person.Rockstars); - var row = db.SingleById(1); - row.Age = 42; + var row = db.SingleById(1); + row.Age = 42; - var sql = db.ToUpdateStatement(row); - sql.Print(); - db.ExecuteSql(sql); + var sql = db.ToUpdateStatement(row); + sql.Print(); + db.ExecuteSql(sql); - var updatedRow = db.SingleById(1); - Assert.That(updatedRow.Equals(row)); - } + var updatedRow = db.SingleById(1); + Assert.That(updatedRow.Equals(row)); } [Test] public void Can_use_ToInsertStatement_to_generate_inline_SQL() { - using (var db = OpenDbConnection()) - { - db.DropAndCreateTable(); - var row = Person.Rockstars[0]; + using var db = OpenDbConnection(); + db.DropAndCreateTable(); + var row = Person.Rockstars[0]; - var sql = db.ToInsertStatement(row); - sql.Print(); - db.ExecuteSql(sql); + var sql = db.ToInsertStatement(row); + sql.Print(); + db.ExecuteSql(sql); - var insertedRow = db.SingleById(row.Id); - Assert.That(insertedRow.Equals(row)); - } + var insertedRow = db.SingleById(row.Id); + Assert.That(insertedRow.Equals(row)); } [Test] public void Correct_DbNull_in_ToInsertStatement() { - using (var db = OpenDbConnection()) - { - db.DropAndCreateTable(); - var row = PersonWithReferenceType.TestValues[0]; + using var db = OpenDbConnection(); + db.DropAndCreateTable(); + var row = PersonWithReferenceType.TestValues[0]; - var sql = db.ToInsertStatement(row); - sql.Print(); - db.ExecuteSql(sql); + var sql = db.ToInsertStatement(row); + sql.Print(); + db.ExecuteSql(sql); - var insertedRow = db.SingleById(row.Id); - Assert.That(insertedRow.Equals(row)); - } + var insertedRow = db.SingleById(row.Id); + Assert.That(insertedRow.Equals(row)); } [Test] public void Correct_Ref_in_ToInsertStatement() { - using (var db = OpenDbConnection()) - { - db.DropAndCreateTable(); - var row = PersonWithReferenceType.TestValues[1]; + using var db = OpenDbConnection(); + db.DropAndCreateTable(); + var row = PersonWithReferenceType.TestValues[1]; - var sql = db.ToInsertStatement(row); - sql.Print(); - db.ExecuteSql(sql); + var sql = db.ToInsertStatement(row); + sql.Print(); + db.ExecuteSql(sql); - var insertedRow = db.SingleById(row.Id); - Assert.That(insertedRow.Equals(row)); - } + var insertedRow = db.SingleById(row.Id); + Assert.That(insertedRow.Equals(row)); } [Test] public void Correct_nullable_in_ToInsertStatement() { - using (var db = OpenDbConnection()) - { - db.DropAndCreateTable(); - var row = TestProduct.TestValues[0]; + using var db = OpenDbConnection(); + db.DropAndCreateTable(); + var row = TestProduct.TestValues[0]; - var sql = db.ToInsertStatement(row); - sql.Print(); - db.ExecuteSql(sql); + var sql = db.ToInsertStatement(row); + sql.Print(); + db.ExecuteSql(sql); - var insertedRow = db.SingleById(row.Id); - Assert.AreEqual(insertedRow, row); - } + var insertedRow = db.SingleById(row.Id); + Assert.AreEqual(insertedRow, row); } } \ No newline at end of file From 5c7318e1b45183899c3f9e4232353fd7297b098a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 20 Jan 2026 19:04:29 +0800 Subject: [PATCH 102/140] Include updateFields in ToUpdateStatement --- .../src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs index 56743b71d66..a50a9165f04 100644 --- a/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs +++ b/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs @@ -1022,7 +1022,7 @@ public virtual string ToUpdateStatement(IDbCommand dbCmd, T item, ICollection { dbCmd.Parameters.Clear(); var dialectProvider = dbCmd.GetDialectProvider(); - dialectProvider.PrepareParameterizedUpdateStatement(dbCmd); + dialectProvider.PrepareParameterizedUpdateStatement(dbCmd, updateFields); if (string.IsNullOrEmpty(dbCmd.CommandText)) return null; From 3c58f7801c9910cd4fc7140f3be2a4ff8c93e01c Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 24 Jan 2026 01:44:25 +0800 Subject: [PATCH 103/140] Use enableIdentityInsert when requeuing failed job --- ServiceStack/src/ServiceStack.Server/DbJobs.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Server/DbJobs.cs b/ServiceStack/src/ServiceStack.Server/DbJobs.cs index fd60639a586..b1a1c5c7916 100644 --- a/ServiceStack/src/ServiceStack.Server/DbJobs.cs +++ b/ServiceStack/src/ServiceStack.Server/DbJobs.cs @@ -511,17 +511,8 @@ public void RequeueFailedJob(long jobId) requeueJob.DurationMs = 0; requeueJob.StartedDate = requeueJob.LastActivityDate = DateTime.UtcNow; - var jobMetadata = typeof(BackgroundJob).GetModelMetadata(); - try - { - jobMetadata.PrimaryKey.AutoIncrement = false; - db.Insert(requeueJob); - monthDb.DeleteById(failedJob.Id); - } - finally - { - jobMetadata.PrimaryKey.AutoIncrement = true; - } + db.Insert(requeueJob, enableIdentityInsert:true); + monthDb.DeleteById(failedJob.Id); } public void FailJob(BackgroundJob job, Exception ex) From b6a91fdd848937735f6cc672c2a1f7e690c56465 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 24 Jan 2026 03:02:00 +0800 Subject: [PATCH 104/140] Also use enableIdentityInsert for SQLite --- .../src/ServiceStack.Jobs/BackgroundJobs.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs index cd00ad924c5..012ef8854df 100644 --- a/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs +++ b/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs @@ -509,17 +509,8 @@ public void RequeueFailedJob(long jobId) lock (db.GetWriteLock()) { - var jobMetadata = typeof(BackgroundJob).GetModelMetadata(); - try - { - jobMetadata.PrimaryKey.AutoIncrement = false; - db.Insert(requeueJob); - monthDb.DeleteById(failedJob.Id); - } - finally - { - jobMetadata.PrimaryKey.AutoIncrement = true; - } + db.Insert(requeueJob, enableIdentityInsert:true); + monthDb.DeleteById(failedJob.Id); } } From a1d0cc2e91922542d1472378ff6d2d371152a5e5 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 24 Jan 2026 03:02:34 +0800 Subject: [PATCH 105/140] Add new Blazor project --- ServiceStack/tests/Blazor/AppComponentBase.cs | 18 + ServiceStack/tests/Blazor/Blazor.csproj | 68 + .../Account/IdentityNoOpEmailSender.cs | 20 + .../tests/Blazor/Components/App.razor | 35 + .../Blazor/Components/Layout/MainLayout.razor | 68 + .../Components/Layout/ReconnectModal.razor | 31 + .../Layout/ReconnectModal.razor.css | 158 + .../Components/Layout/ReconnectModal.razor.js | 63 + .../Components/Pages/Admin/Bookings.razor | 11 + .../Components/Pages/Admin/Coupons.razor | 11 + .../Blazor/Components/Pages/Admin/Index.razor | 35 + .../Blazor/Components/Pages/Counter.razor | 19 + .../tests/Blazor/Components/Pages/Docs.razor | 54 + .../tests/Blazor/Components/Pages/Error.razor | 42 + .../tests/Blazor/Components/Pages/Home.razor | 29 + .../Blazor/Components/Pages/Profile.razor | 65 + .../Components/Pages/Secure/Bookings.razor | 76 + .../Components/Pages/Secure/Coupons.razor | 9 + .../Blazor/Components/Pages/TodoMvc.razor | 158 + .../Blazor/Components/Pages/Weather.razor | 49 + .../tests/Blazor/Components/Routes.razor | 10 + .../Components/Shared/FormLoading.razor | 44 + .../Components/Shared/GettingStarted.razor | 88 + .../Blazor/Components/Shared/Header.razor | 104 + .../Components/Shared/ShellCommand.razor | 46 + .../Blazor/Components/Shared/Sidebar.razor | 122 + .../tests/Blazor/Components/_Imports.razor | 22 + .../tests/Blazor/Configure.AppHost.cs | 19 + ServiceStack/tests/Blazor/Configure.Auth.cs | 19 + .../tests/Blazor/Configure.AutoQuery.cs | 27 + .../tests/Blazor/Configure.BackgroundJobs.cs | 91 + .../tests/Blazor/Configure.Db.Migrations.cs | 127 + ServiceStack/tests/Blazor/Configure.Db.cs | 30 + .../tests/Blazor/Configure.HealthChecks.cs | 37 + .../tests/Blazor/Configure.Markdown.cs | 74 + .../tests/Blazor/Configure.RequestLogs.cs | 34 + ServiceStack/tests/Blazor/Markdown.Pages.cs | 137 + ServiceStack/tests/Blazor/Markdown.Videos.cs | 61 + .../tests/Blazor/MarkdownPagesBase.cs | 720 ++++ ...301000000_CreateIdentitySchema.Designer.cs | 280 ++ .../20240301000000_CreateIdentitySchema.cs | 226 + .../ApplicationDbContextModelSnapshot.cs | 277 ++ .../tests/Blazor/Migrations/Migration1000.cs | 84 + ServiceStack/tests/Blazor/Program.cs | 93 + .../Blazor/Properties/launchSettings.json | 38 + .../ServiceInterface/CheckUrlsCommand.cs | 162 + .../Data/ApplicationDbContext.cs | 9 + .../ServiceInterface/Data/ApplicationUser.cs | 12 + .../Data/CustomUserSession.cs | 42 + .../Blazor/ServiceInterface/EmailServices.cs | 94 + .../Blazor/ServiceInterface/MyServices.cs | 11 + .../Blazor/ServiceInterface/TodosServices.cs | 34 + .../tests/Blazor/ServiceModel/Bookings.cs | 208 + .../tests/Blazor/ServiceModel/Hello.cs | 16 + .../tests/Blazor/ServiceModel/Icons.cs | 11 + .../tests/Blazor/ServiceModel/Roles.cs | 8 + .../tests/Blazor/ServiceModel/Todos.cs | 49 + .../tests/Blazor/ServiceModel/types/README.md | 2 + ServiceStack/tests/Blazor/_pages/about.md | 19 + ServiceStack/tests/Blazor/_pages/deploy.md | 300 ++ ServiceStack/tests/Blazor/_pages/privacy.md | 69 + .../tests/Blazor/_videos/blazor/admin.md | 13 + .../tests/Blazor/_videos/blazor/components.md | 12 + .../tests/Blazor/_videos/blazor/darkmode.md | 14 + .../tests/Blazor/_videos/blazor/tailwind.md | 14 + .../tests/Blazor/_videos/blazor/universal.md | 15 + .../tests/Blazor/appsettings.Development.json | 8 + .../tests/Blazor/appsettings.Production.json | 8 + ServiceStack/tests/Blazor/appsettings.json | 13 + ServiceStack/tests/Blazor/package.json | 14 + ServiceStack/tests/Blazor/postinstall.js | 231 ++ ServiceStack/tests/Blazor/tailwind.config.js | 12 + ServiceStack/tests/Blazor/tailwind.input.css | 163 + ServiceStack/tests/Blazor/wwwroot/app.css | 47 + ServiceStack/tests/Blazor/wwwroot/css/app.css | 3621 +++++++++++++++++ .../tests/Blazor/wwwroot/css/highlight.css | 62 + .../tests/Blazor/wwwroot/css/typography.css | 240 ++ ServiceStack/tests/Blazor/wwwroot/favicon.png | Bin 0 -> 1148 bytes .../tests/Blazor/wwwroot/img/blazor.svg | 1 + .../tests/Blazor/wwwroot/img/nav/bookings.svg | 5 + .../tests/Blazor/wwwroot/img/nav/counter.svg | 3 + .../tests/Blazor/wwwroot/img/nav/coupon.svg | 3 + .../tests/Blazor/wwwroot/img/nav/home.svg | 5 + .../tests/Blazor/wwwroot/img/nav/profile.svg | 3 + .../tests/Blazor/wwwroot/img/nav/todomvc.svg | 3 + .../tests/Blazor/wwwroot/img/nav/weather.svg | 3 + .../Blazor/wwwroot/img/profiles/user1.svg | 55 + .../Blazor/wwwroot/img/profiles/user2.svg | 51 + .../Blazor/wwwroot/img/profiles/user3.svg | 41 + ServiceStack/tests/Blazor/wwwroot/mjs/app.mjs | 36 + .../tests/Blazor/wwwroot/mjs/components.mjs | 178 + .../Account/Manage/EnableAuthenticator.mjs | 9 + .../Account/Manage/ManageUserApiKeys.mjs | 439 ++ .../pages/Account/Manage/apikeys-apis.mjs | 185 + .../wwwroot/tailwind/ServiceStack.Blazor.html | 2161 ++++++++++ 95 files changed, 12543 insertions(+) create mode 100644 ServiceStack/tests/Blazor/AppComponentBase.cs create mode 100644 ServiceStack/tests/Blazor/Blazor.csproj create mode 100644 ServiceStack/tests/Blazor/Components/Account/IdentityNoOpEmailSender.cs create mode 100644 ServiceStack/tests/Blazor/Components/App.razor create mode 100644 ServiceStack/tests/Blazor/Components/Layout/MainLayout.razor create mode 100644 ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor create mode 100644 ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.css create mode 100644 ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.js create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Admin/Bookings.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Admin/Coupons.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Admin/Index.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Counter.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Docs.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Error.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Home.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Profile.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Secure/Bookings.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Secure/Coupons.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/TodoMvc.razor create mode 100644 ServiceStack/tests/Blazor/Components/Pages/Weather.razor create mode 100644 ServiceStack/tests/Blazor/Components/Routes.razor create mode 100644 ServiceStack/tests/Blazor/Components/Shared/FormLoading.razor create mode 100644 ServiceStack/tests/Blazor/Components/Shared/GettingStarted.razor create mode 100644 ServiceStack/tests/Blazor/Components/Shared/Header.razor create mode 100644 ServiceStack/tests/Blazor/Components/Shared/ShellCommand.razor create mode 100644 ServiceStack/tests/Blazor/Components/Shared/Sidebar.razor create mode 100644 ServiceStack/tests/Blazor/Components/_Imports.razor create mode 100644 ServiceStack/tests/Blazor/Configure.AppHost.cs create mode 100644 ServiceStack/tests/Blazor/Configure.Auth.cs create mode 100644 ServiceStack/tests/Blazor/Configure.AutoQuery.cs create mode 100644 ServiceStack/tests/Blazor/Configure.BackgroundJobs.cs create mode 100644 ServiceStack/tests/Blazor/Configure.Db.Migrations.cs create mode 100644 ServiceStack/tests/Blazor/Configure.Db.cs create mode 100644 ServiceStack/tests/Blazor/Configure.HealthChecks.cs create mode 100644 ServiceStack/tests/Blazor/Configure.Markdown.cs create mode 100644 ServiceStack/tests/Blazor/Configure.RequestLogs.cs create mode 100644 ServiceStack/tests/Blazor/Markdown.Pages.cs create mode 100644 ServiceStack/tests/Blazor/Markdown.Videos.cs create mode 100644 ServiceStack/tests/Blazor/MarkdownPagesBase.cs create mode 100644 ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.Designer.cs create mode 100644 ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.cs create mode 100644 ServiceStack/tests/Blazor/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 ServiceStack/tests/Blazor/Migrations/Migration1000.cs create mode 100644 ServiceStack/tests/Blazor/Program.cs create mode 100644 ServiceStack/tests/Blazor/Properties/launchSettings.json create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/CheckUrlsCommand.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationDbContext.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationUser.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/Data/CustomUserSession.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/EmailServices.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/MyServices.cs create mode 100644 ServiceStack/tests/Blazor/ServiceInterface/TodosServices.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/Bookings.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/Hello.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/Icons.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/Roles.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/Todos.cs create mode 100644 ServiceStack/tests/Blazor/ServiceModel/types/README.md create mode 100644 ServiceStack/tests/Blazor/_pages/about.md create mode 100644 ServiceStack/tests/Blazor/_pages/deploy.md create mode 100644 ServiceStack/tests/Blazor/_pages/privacy.md create mode 100644 ServiceStack/tests/Blazor/_videos/blazor/admin.md create mode 100644 ServiceStack/tests/Blazor/_videos/blazor/components.md create mode 100644 ServiceStack/tests/Blazor/_videos/blazor/darkmode.md create mode 100644 ServiceStack/tests/Blazor/_videos/blazor/tailwind.md create mode 100644 ServiceStack/tests/Blazor/_videos/blazor/universal.md create mode 100644 ServiceStack/tests/Blazor/appsettings.Development.json create mode 100644 ServiceStack/tests/Blazor/appsettings.Production.json create mode 100644 ServiceStack/tests/Blazor/appsettings.json create mode 100644 ServiceStack/tests/Blazor/package.json create mode 100644 ServiceStack/tests/Blazor/postinstall.js create mode 100644 ServiceStack/tests/Blazor/tailwind.config.js create mode 100644 ServiceStack/tests/Blazor/tailwind.input.css create mode 100644 ServiceStack/tests/Blazor/wwwroot/app.css create mode 100644 ServiceStack/tests/Blazor/wwwroot/css/app.css create mode 100644 ServiceStack/tests/Blazor/wwwroot/css/highlight.css create mode 100644 ServiceStack/tests/Blazor/wwwroot/css/typography.css create mode 100644 ServiceStack/tests/Blazor/wwwroot/favicon.png create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/blazor.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/bookings.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/counter.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/coupon.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/home.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/profile.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/todomvc.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/nav/weather.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/profiles/user1.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/profiles/user2.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/img/profiles/user3.svg create mode 100644 ServiceStack/tests/Blazor/wwwroot/mjs/app.mjs create mode 100644 ServiceStack/tests/Blazor/wwwroot/mjs/components.mjs create mode 100644 ServiceStack/tests/Blazor/wwwroot/pages/Account/Manage/EnableAuthenticator.mjs create mode 100644 ServiceStack/tests/Blazor/wwwroot/pages/Account/Manage/ManageUserApiKeys.mjs create mode 100644 ServiceStack/tests/Blazor/wwwroot/pages/Account/Manage/apikeys-apis.mjs create mode 100644 ServiceStack/tests/Blazor/wwwroot/tailwind/ServiceStack.Blazor.html diff --git a/ServiceStack/tests/Blazor/AppComponentBase.cs b/ServiceStack/tests/Blazor/AppComponentBase.cs new file mode 100644 index 00000000000..c42aa6c86da --- /dev/null +++ b/ServiceStack/tests/Blazor/AppComponentBase.cs @@ -0,0 +1,18 @@ +using ServiceStack; +using ServiceStack.Blazor; + +namespace MyApp; + +/// +/// For Pages and Components that make use of ServiceStack functionality, e.g. Client +/// +public abstract class AppComponentBase : ServiceStack.Blazor.BlazorComponentBase, IHasJsonApiClient +{ +} + +/// +/// For Pages and Components requiring Authentication +/// +public abstract class AppAuthComponentBase : AuthBlazorComponentBase +{ +} diff --git a/ServiceStack/tests/Blazor/Blazor.csproj b/ServiceStack/tests/Blazor/Blazor.csproj new file mode 100644 index 00000000000..4e89bbc197d --- /dev/null +++ b/ServiceStack/tests/Blazor/Blazor.csproj @@ -0,0 +1,68 @@ + + + + net10.0 + enable + enable + MyApp + aspnet-MyApp-7b2ab71a-0b50-423f-969d-e35a9402b1b5 + true + DefaultContainer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefaultItemExcludes);App_Data\**;node_modules\** + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Components/Account/IdentityNoOpEmailSender.cs b/ServiceStack/tests/Blazor/Components/Account/IdentityNoOpEmailSender.cs new file mode 100644 index 00000000000..8d0b538ec6d --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Account/IdentityNoOpEmailSender.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using MyApp.Data; + +namespace MyApp.Components.Account; + +// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation. +internal sealed class IdentityNoOpEmailSender : IEmailSender +{ + private readonly IEmailSender emailSender = new NoOpEmailSender(); + + public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => + emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by clicking here."); + + public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by clicking here."); + + public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); +} diff --git a/ServiceStack/tests/Blazor/Components/App.razor b/ServiceStack/tests/Blazor/Components/App.razor new file mode 100644 index 00000000000..5e175e75769 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/App.razor @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + @BlazorHtml.ImportMap(new() + { + ["app.mjs"] = ("/mjs/app.mjs", "/mjs/app.mjs"), + ["@servicestack/client"] = ("/lib/mjs/servicestack-client.mjs", "/lib/mjs/servicestack-client.min.mjs"), + ["vue"] = ("/js/vue.mjs", "/js/vue.min.mjs"), + ["@servicestack/vue"] = ("/js/servicestack-vue.mjs", "/js/servicestack-vue.min.mjs"), + }) + + + + + + + + + + + + + + diff --git a/ServiceStack/tests/Blazor/Components/Layout/MainLayout.razor b/ServiceStack/tests/Blazor/Components/Layout/MainLayout.razor new file mode 100644 index 00000000000..e604b443b3f --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Layout/MainLayout.razor @@ -0,0 +1,68 @@ +@inherits LayoutComponentBase +@inject NavigationManager NavigationManager; + +
+
+ + + +
+
+ +
+
+
+
+ @Body +
+
+
+
+ +
+ + + + diff --git a/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor new file mode 100644 index 00000000000..49d916bc56b --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor @@ -0,0 +1,31 @@ + + + +
+ +

+ Rejoining the server... +

+

+ Rejoin failed... trying again in seconds. +

+

+ Failed to rejoin.
Please retry or reload the page. +

+ +

+ The session has been paused by the server. +

+ +

+ Failed to resume the session.
Please reload the page. +

+
+
diff --git a/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.css b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.css new file mode 100644 index 00000000000..7aa3a7a3174 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.css @@ -0,0 +1,158 @@ +.components-reconnect-first-attempt-visible, +.components-reconnect-repeated-attempt-visible, +.components-reconnect-failed-visible, +.components-pause-visible, +.components-resume-failed-visible, +.components-rejoining-animation { + display: none; +} + +#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible, +#components-reconnect-modal.components-reconnect-show .components-rejoining-animation, +#components-reconnect-modal.components-reconnect-paused .components-pause-visible, +#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible, +#components-reconnect-modal.components-reconnect-retrying, +#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible, +#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation, +#components-reconnect-modal.components-reconnect-failed, +#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible { + display: block; +} + + +#components-reconnect-modal { + z-index: 100; + background-color: white; + width: 20rem; + margin: 20vh auto; + padding: 2rem; + border: 0; + border-radius: 0.5rem; + box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3); + opacity: 0; + transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete; + animation: components-reconnect-modal-fadeOutOpacity 0.5s both; + &[open] + +{ + animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s; + animation-fill-mode: both; +} + +} + +#components-reconnect-modal::backdrop { + background-color: rgba(0, 0, 0, 0.4); + animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out; + opacity: 1; +} + +@keyframes components-reconnect-modal-slideUp { + 0% { + transform: translateY(30px) scale(0.95); + } + + 100% { + transform: translateY(0); + } +} + +@keyframes components-reconnect-modal-fadeInOpacity { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes components-reconnect-modal-fadeOutOpacity { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.components-reconnect-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +#components-reconnect-modal p { + margin: 0; + text-align: center; +} + +#components-reconnect-modal button { + border: 0; + background-color: #6b9ed2; + color: white; + padding: 4px 24px; + border-radius: 4px; +} + + #components-reconnect-modal button:hover { + background-color: #3b6ea2; + } + + #components-reconnect-modal button:active { + background-color: #6b9ed2; + } + +.components-rejoining-animation { + position: relative; + width: 80px; + height: 80px; +} + + .components-rejoining-animation div { + position: absolute; + border: 3px solid #0087ff; + opacity: 1; + border-radius: 50%; + animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite; + } + + .components-rejoining-animation div:nth-child(2) { + animation-delay: -0.5s; + } + +@keyframes components-rejoining-animation { + 0% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 0; + } + + 4.9% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 0; + } + + 5% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 1; + } + + 100% { + top: 0px; + left: 0px; + width: 80px; + height: 80px; + opacity: 0; + } +} diff --git a/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.js b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.js new file mode 100644 index 00000000000..e52a190bacb --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Layout/ReconnectModal.razor.js @@ -0,0 +1,63 @@ +// Set up event handlers +const reconnectModal = document.getElementById("components-reconnect-modal"); +reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged); + +const retryButton = document.getElementById("components-reconnect-button"); +retryButton.addEventListener("click", retry); + +const resumeButton = document.getElementById("components-resume-button"); +resumeButton.addEventListener("click", resume); + +function handleReconnectStateChanged(event) { + if (event.detail.state === "show") { + reconnectModal.showModal(); + } else if (event.detail.state === "hide") { + reconnectModal.close(); + } else if (event.detail.state === "failed") { + document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + } else if (event.detail.state === "rejected") { + location.reload(); + } +} + +async function retry() { + document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + + try { + // Reconnect will asynchronously return: + // - true to mean success + // - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID) + // - exception to mean we didn't reach the server (this can be sync or async) + const successful = await Blazor.reconnect(); + if (!successful) { + // We have been able to reach the server, but the circuit is no longer available. + // We'll reload the page so the user can continue using the app as quickly as possible. + const resumeSuccessful = await Blazor.resumeCircuit(); + if (!resumeSuccessful) { + location.reload(); + } else { + reconnectModal.close(); + } + } + } catch (err) { + // We got an exception, server is currently unavailable + document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + } +} + +async function resume() { + try { + const successful = await Blazor.resumeCircuit(); + if (!successful) { + location.reload(); + } + } catch { + location.reload(); + } +} + +async function retryWhenDocumentBecomesVisible() { + if (document.visibilityState === "visible") { + await retry(); + } +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Admin/Bookings.razor b/ServiceStack/tests/Blazor/Components/Pages/Admin/Bookings.razor new file mode 100644 index 00000000000..0c7b992c5b9 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Admin/Bookings.razor @@ -0,0 +1,11 @@ +@page "/admin/bookings" +@attribute [Authorize(Roles = "Admin")] +@rendermode RenderMode.InteractiveServer + + + + Bookings + + + + diff --git a/ServiceStack/tests/Blazor/Components/Pages/Admin/Coupons.razor b/ServiceStack/tests/Blazor/Components/Pages/Admin/Coupons.razor new file mode 100644 index 00000000000..6d0a6c94e9a --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Admin/Coupons.razor @@ -0,0 +1,11 @@ +@page "/admin/coupons" +@attribute [Authorize(Roles = "Admin")] +@rendermode RenderMode.InteractiveServer + + + + Coupons + + + + \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Components/Pages/Admin/Index.razor b/ServiceStack/tests/Blazor/Components/Pages/Admin/Index.razor new file mode 100644 index 00000000000..636c2775994 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Admin/Index.razor @@ -0,0 +1,35 @@ +@page "/admin" +@attribute [Authorize(Roles = "Admin")] + +
+
+
+ Blazor MyApp +
+
+
+

+ MyApp Admin +

+

+ Manage App Database +

+
+
+ + + + Create and Manage Bookings + + + Create and Manage Coupons + + + +

+ View all Database Tables +

+
+
+
+
\ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Components/Pages/Counter.razor b/ServiceStack/tests/Blazor/Components/Pages/Counter.razor new file mode 100644 index 00000000000..6457f7b4012 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Counter.razor @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode RenderMode.InteractiveServer + +Counter + +Counter + +

Current count: @currentCount

+ +Click me + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Docs.razor b/ServiceStack/tests/Blazor/Components/Pages/Docs.razor new file mode 100644 index 00000000000..dde2311f380 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Docs.razor @@ -0,0 +1,54 @@ +@page "/{Slug:regex(^[a-z-_]+$)}" +@inherits AppComponentBase +@inject MarkdownPages Markdown + +@if (doc != null) +{ + @doc.Title + +
+
+

+ @doc.Title +

+
+
+ @BlazorHtml.Raw(doc.Preview) +
+
+} +else +{ +
+ @if (error != null) + { + + } + else + { + + } +
+} + +@code { + [Parameter] + public required string Slug { get; set; } + + MarkdownFileInfo? doc; + ResponseStatus? error; + + void load() + { + doc = Markdown.GetBySlug(Slug); + if (doc == null) + { + error = new() { Message = $"_pages/{Slug}.md was not found" }; + return; + } + } + + protected override void OnInitialized() => load(); + + protected override void OnParametersSet() => load(); +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Error.razor b/ServiceStack/tests/Blazor/Components/Pages/Error.razor new file mode 100644 index 00000000000..8aa96e1d9fb --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Error.razor @@ -0,0 +1,42 @@ +@page "/Error" +@using System.Diagnostics + +Error + +
+
+

+ Error. +

+ An error occurred while processing your request. +
+
+ @if (ShowRequestId) + { +

+ Request ID: @RequestId +

+ } + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+
+
+ +@code { + [CascadingParameter] public HttpContext? HttpContext { get; set; } + + public string? RequestId { get; set; } + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Home.razor b/ServiceStack/tests/Blazor/Components/Pages/Home.razor new file mode 100644 index 00000000000..1b61eb5d11e --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Home.razor @@ -0,0 +1,29 @@ +@page "/" + +Home + +
+
+

+ Welcome to + ServiceStack.Blazor +

+

+ Welcome to your new Blazor App, checkout links below to get started: +

+ +
+
+ + \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Components/Pages/Profile.razor b/ServiceStack/tests/Blazor/Components/Pages/Profile.razor new file mode 100644 index 00000000000..c62a35cc529 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Profile.razor @@ -0,0 +1,65 @@ +@page "/profile" +@attribute [Authorize] +@inherits AppAuthComponentBase + +@inject NavigationManager NavigationManager + +
+

Profile Overview

+
+
+
+
+ +
+
+

Welcome back,

+

@User.GetDisplayName()

+ @if (User.GetRoles().Length > 0) + { +
+ @foreach (var role in User.GetRoles()) + { + + @role + + } +
+ } + @if (User.GetPermissions().Length > 0) + { +
+ @foreach (var perm in User.GetPermissions()) + { + + @perm + + } +
+ } +
+
+
+ + + + + Sign Out + + +
+
+
+
+ +@code { + private string? currentUrl; + + protected override void OnInitialized() + { + currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + } +} + diff --git a/ServiceStack/tests/Blazor/Components/Pages/Secure/Bookings.razor b/ServiceStack/tests/Blazor/Components/Pages/Secure/Bookings.razor new file mode 100644 index 00000000000..c528a15d19f --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Secure/Bookings.razor @@ -0,0 +1,76 @@ +@page "/secure/bookings" +@attribute [Authorize(Roles = "Employee")] +@rendermode RenderMode.InteractiveServer +@inject IJSRuntime JS + +Bookings + +Bookings + + + + + + + + + +
+ Type +
+
+ + +
+ No +
+
+ + + + +
+ Start +
+
+ + +
+ End +
+ +
+ + + + +
+
+ +@code { + string FormatDate(object o) => o is DateTime d ? d.ToShortDateString() : ""; + + // Handle when table header is selected + public async Task OnSelectedHeader(Column item) + { + await JS.Log(item.Name); + } + + // Handle when table row is selected + public async Task OnSelectedRow(Booking? x) + { + var wasDeselected = x == null; + if (!wasDeselected) await JS.Log($"{x!.Name}"); + } +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Secure/Coupons.razor b/ServiceStack/tests/Blazor/Components/Pages/Secure/Coupons.razor new file mode 100644 index 00000000000..503aea2304a --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Secure/Coupons.razor @@ -0,0 +1,9 @@ +@page "/secure/coupons" +@attribute [Authorize(Roles = "Employee")] +@rendermode RenderMode.InteractiveServer + +Coupons + +Coupons + + diff --git a/ServiceStack/tests/Blazor/Components/Pages/TodoMvc.razor b/ServiceStack/tests/Blazor/Components/Pages/TodoMvc.razor new file mode 100644 index 00000000000..ec0caaaf42e --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/TodoMvc.razor @@ -0,0 +1,158 @@ +@page "/todomvc" +@inherits AppComponentBase +@rendermode RenderMode.InteractiveServer + +Todos Application + +
+ + + + + + + +
+
    + @foreach (var todo in filteredTodos()) + { +
  • +
    +
    + @if (todo.IsFinished) + { + + + + } + else + { + + + + } +
    +
    + +
    +
    + @if (todo.IsFinished) + { + + + + } +
    +
    +
  • + } +
+
+ +
+
+ @unfinishedTodos().Count() left +
+ +
+ + + +
+ +
+ +
+
+ +
+ + +@code { + enum Filter + { + All, + Finished, + Unfinished + } + static string[] VisibleFields = new[] { nameof(CreateTodo.Text) }; + + string TabClass(string @class, bool isActive) => + ClassNames("border-gray-200 dark:border-gray-800 text-sm font-medium px-4 py-2 hover:bg-gray-100 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700", + (isActive ? "text-blue-700 dark:bg-blue-600" : "text-gray-900 hover:text-blue-700 dark:bg-gray-700"), @class); + + List todos = new(); + Filter filter = Filter.All; + + CreateTodo request = new(); + ResponseStatus? errorStatus; + + IEnumerable filteredTodos() => filter switch + { + Filter.Finished => finishedTodos(), + Filter.Unfinished => unfinishedTodos(), + _ => todos + }; + IEnumerable finishedTodos() => todos.Where(x => x.IsFinished); + IEnumerable unfinishedTodos() => todos.Where(x => !x.IsFinished); + + protected override async Task OnInitializedAsync() => await refreshTodos(); + + // For best UX: apply changes locally then revalidate with real server state + async Task refreshTodos() + { + var api = await ApiAsync(new QueryTodos()); + if (api.Succeeded) + todos = api.Response!.Results; + else + errorStatus = api.Error; + } + + async Task addTodo() + { + errorStatus = null; + todos.Add(new Todo { Text = request.Text }); + var api = await ApiAsync(request); + if (api.Succeeded) + request.Text = ""; + else + errorStatus = api.Error; + await refreshTodos(); + } + + async Task removeTodo(long id) + { + todos.RemoveAll(x => x.Id == id); + var api = await ApiAsync(new DeleteTodos { Ids = [id] }); + errorStatus = api.Error; + await refreshTodos(); + } + + async Task removeFinishedTodos() + { + var ids = todos.Where(x => x.IsFinished).Select(x => x.Id).ToList(); + if (ids.Count == 0) return; + todos.RemoveAll(x => ids.Contains(x.Id)); + var api = await ApiAsync(new DeleteTodos { Ids = ids }); + errorStatus = api.Error; + await refreshTodos(); + } + + async Task toggleTodo(long id) + { + var todo = todos.Find(x => x.Id == id)!; + todo.IsFinished = !todo.IsFinished; + var api = await ApiAsync(new UpdateTodo { Id = todo.Id, Text = todo.Text, IsFinished = todo.IsFinished }); + errorStatus = api.Error; + await refreshTodos(); + } +} diff --git a/ServiceStack/tests/Blazor/Components/Pages/Weather.razor b/ServiceStack/tests/Blazor/Components/Pages/Weather.razor new file mode 100644 index 00000000000..9715ebcbabe --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Pages/Weather.razor @@ -0,0 +1,49 @@ +@page "/weather" +@attribute [StreamRendering(true)] + +Weather + +Weather + +

This component demonstrates showing data.

+ +@if (forecasts == null) +{ + +} +else +{ + + + + + + +} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate streaming rendering + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); + } + + public class WeatherForecast + { + public DateOnly Date { get; set; } + public int TemperatureC { get; set; } + public string? Summary { get; set; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/ServiceStack/tests/Blazor/Components/Routes.razor b/ServiceStack/tests/Blazor/Components/Routes.razor new file mode 100644 index 00000000000..17ece10b9ec --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Routes.razor @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ServiceStack/tests/Blazor/Components/Shared/FormLoading.razor b/ServiceStack/tests/Blazor/Components/Shared/FormLoading.razor new file mode 100644 index 00000000000..f9e16632133 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Shared/FormLoading.razor @@ -0,0 +1,44 @@ +@using ServiceStack +@inject IJSRuntime JsRuntime +@inject NavigationManager NavigationManager + +@if (Loading) +{ +
+ @if (Icon) + { + + + + + + + + + + + + + + + + + + } + @Text +
+} + +@code { + [Parameter] + public bool Loading { get; set; } + + [Parameter] + public bool Icon { get; set; } = true; + + [Parameter] + public string Text { get; set; } = "loading..."; + + [Parameter] + public string? @class { get; set; } +} diff --git a/ServiceStack/tests/Blazor/Components/Shared/GettingStarted.razor b/ServiceStack/tests/Blazor/Components/Shared/GettingStarted.razor new file mode 100644 index 00000000000..13a1a4c29a4 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Shared/GettingStarted.razor @@ -0,0 +1,88 @@ +@rendermode RenderMode.InteractiveServer + +
+
+

+ Getting Started +

+
+
+ +

Create New Project

+ + +
+ + +
+ +
+ Open with Visual Studio + or JetBrains Rider +
+ +

+ After install run + DB Migrations + to create App DB +

+ npm run migrate + +

+ Run Tailwind +

+ npm run ui:dev + +

+ Run Blazor App + in /MyApp +

+ dotnet watch +
+
+
+
+ +@code { + string project { get; set; } = ""; + + string projectName => string.IsNullOrEmpty(project) ? "MyApp" : project; + + string projectZip => projectName + ".zip"; + + string zipUrl(string template) => + $"https://account.servicestack.net/archive/{template}?Name={projectName}"; +} diff --git a/ServiceStack/tests/Blazor/Components/Shared/Header.razor b/ServiceStack/tests/Blazor/Components/Shared/Header.razor new file mode 100644 index 00000000000..fe266ad6797 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Shared/Header.razor @@ -0,0 +1,104 @@ +@inherits AppAuthComponentBase +@implements IDisposable +@inject NavigationManager NavigationManager + +
+
+
+ +
+
+
+ +@code { + private string? currentUrl; + + protected override void OnInitialized() + { + currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + NavigationManager.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + currentUrl = NavigationManager.ToBaseRelativePath(e.Location); + StateHasChanged(); + } + + public void Dispose() + { + NavigationManager.LocationChanged -= OnLocationChanged; + } +} + diff --git a/ServiceStack/tests/Blazor/Components/Shared/ShellCommand.razor b/ServiceStack/tests/Blazor/Components/Shared/ShellCommand.razor new file mode 100644 index 00000000000..14653db65d5 --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Shared/ShellCommand.razor @@ -0,0 +1,46 @@ +@inject IJSRuntime JS + +
+
+ + sh +
+@if (SuccessText != string.Empty) +{ +
+
+
+ +
+
+

+ @SuccessText +

+
+
+
+} +
+ +@code { + [Parameter] + public string? @class { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + string SuccessText { get; set; } = string.Empty; + + private ElementReference elCmd; + + async Task copyCommand(MouseEventArgs e) + { + SuccessText = "copied"; + var text = await JS.InvokeAsync("JS.invoke", elCmd, "innerText"); + await JS.InvokeVoidAsync("navigator.clipboard.writeText", text); + await Task.Delay(3_000); + SuccessText = string.Empty; + } +} diff --git a/ServiceStack/tests/Blazor/Components/Shared/Sidebar.razor b/ServiceStack/tests/Blazor/Components/Shared/Sidebar.razor new file mode 100644 index 00000000000..8b8cb94c98d --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/Shared/Sidebar.razor @@ -0,0 +1,122 @@ +@inject NavigationManager NavigationManager + + + + + + + + + +@code { + const string MobileNavLinkActiveClass = "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-50"; + const string MobileNavLinkClass = "text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md"; + + const string DesktopNavLinkActiveClass = "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-50"; + const string DesktopNavLinkClass = "text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-50 group flex items-center px-2 py-2 font-medium rounded-md"; +} diff --git a/ServiceStack/tests/Blazor/Components/_Imports.razor b/ServiceStack/tests/Blazor/Components/_Imports.razor new file mode 100644 index 00000000000..71d4e6521af --- /dev/null +++ b/ServiceStack/tests/Blazor/Components/_Imports.razor @@ -0,0 +1,22 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Authorization +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using ServiceStack +@using ServiceStack.Web +@using ServiceStack.Html +@using ServiceStack.Blazor +@using ServiceStack.Blazor.Components +@using ServiceStack.Blazor.Components.Tailwind +@using MyApp +@using MyApp.Components +@using MyApp.Components.Shared +@using MyApp.Components.Layout +@using MyApp.ServiceModel diff --git a/ServiceStack/tests/Blazor/Configure.AppHost.cs b/ServiceStack/tests/Blazor/Configure.AppHost.cs new file mode 100644 index 00000000000..a1556b7f9d5 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.AppHost.cs @@ -0,0 +1,19 @@ +[assembly: HostingStartup(typeof(MyApp.AppHost))] + +namespace MyApp; + +public class AppHost() : AppHostBase("MyApp"), IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => + { + // Configure ASP.NET Core IOC Dependencies + }); + + // Configure your AppHost with the necessary configuration and dependencies your App needs + public override void Configure() + { + SetConfig(new HostConfig { + }); + } +} diff --git a/ServiceStack/tests/Blazor/Configure.Auth.cs b/ServiceStack/tests/Blazor/Configure.Auth.cs new file mode 100644 index 00000000000..dec35eaec21 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.Auth.cs @@ -0,0 +1,19 @@ +using ServiceStack.Auth; +using MyApp.Data; + +[assembly: HostingStartup(typeof(MyApp.ConfigureAuth))] + +namespace MyApp; + +public class ConfigureAuth : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => + { + services.AddPlugin(new AuthFeature(IdentityAuth.For(options => { + options.SessionFactory = () => new CustomUserSession(); + options.CredentialsAuth(); + options.AdminUsersFeature(); + }))); + }); +} diff --git a/ServiceStack/tests/Blazor/Configure.AutoQuery.cs b/ServiceStack/tests/Blazor/Configure.AutoQuery.cs new file mode 100644 index 00000000000..84c6dbfdeb5 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.AutoQuery.cs @@ -0,0 +1,27 @@ +using ServiceStack.Data; + +[assembly: HostingStartup(typeof(MyApp.ConfigureAutoQuery))] + +namespace MyApp; + +public class ConfigureAutoQuery : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => { + // Enable Audit History + services.AddSingleton(c => + new OrmLiteCrudEvents(c.GetRequiredService())); + + // For TodosService + services.AddPlugin(new AutoQueryDataFeature()); + + // For Bookings https://docs.servicestack.net/autoquery-crud-bookings + services.AddPlugin(new AutoQueryFeature { + MaxLimit = 1000, + //IncludeTotal = true, + }); + }) + .ConfigureAppHost(appHost => { + appHost.Resolve().InitSchema(); + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Configure.BackgroundJobs.cs b/ServiceStack/tests/Blazor/Configure.BackgroundJobs.cs new file mode 100644 index 00000000000..5d631d84040 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.BackgroundJobs.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Identity; +using ServiceStack.Jobs; +using MyApp.Data; +using MyApp.ServiceInterface; +using MyApp.ServiceModel; + +[assembly: HostingStartup(typeof(MyApp.ConfigureBackgroundJobs))] + +namespace MyApp; + +public class ConfigureBackgroundJobs : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context,services) => { + var smtpConfig = context.Configuration.GetSection(nameof(SmtpConfig))?.Get(); + if (smtpConfig is not null) + { + services.AddSingleton(smtpConfig); + } + // Lazily register SendEmailCommand to allow SmtpConfig to only be required if used + services.AddTransient(c => new SendEmailCommand( + c.GetRequiredService>(), + c.GetRequiredService(), + c.GetRequiredService())); + + services.AddPlugin(new CommandsFeature()); + services.AddPlugin(new BackgroundsJobFeature()); + services.AddHostedService(); + }).ConfigureAppHost(afterAppHostInit: appHost => { + var services = appHost.GetApplicationServices(); + + // Log if EmailSender is enabled and SmtpConfig missing + var log = services.GetRequiredService>(); + var emailSender = services.GetRequiredService>(); + if (emailSender is EmailSender) + { + var smtpConfig = services.GetService(); + if (smtpConfig is null) + { + log.LogWarning("SMTP is not configured, please configure SMTP to enable sending emails"); + } + else + { + log.LogWarning("SMTP is configured with <{FromEmail}> {FromName}", smtpConfig.FromEmail, smtpConfig.FromName); + } + } + + var jobs = services.GetRequiredService(); + // Example of registering a Recurring Job to run Every Hour + //jobs.RecurringCommand(Schedule.Hourly); + }); +} + +public class JobsHostedService(ILogger log, IBackgroundJobs jobs) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await jobs.StartAsync(stoppingToken); + + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3)); + while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken)) + { + await jobs.TickAsync(); + } + } +} + +/// +/// Sends emails by executing SendEmailCommand in a background job where it's serially processed by 'smtp' worker +/// +public class EmailSender(IBackgroundJobs jobs) : IEmailSender +{ + public Task SendEmailAsync(string email, string subject, string htmlMessage) + { + jobs.EnqueueCommand(new SendEmail { + To = email, + Subject = subject, + BodyHtml = htmlMessage, + }); + return Task.CompletedTask; + } + + public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => + SendEmailAsync(email, "Confirm your email", $"Please confirm your account by clicking here."); + + public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => + SendEmailAsync(email, "Reset your password", $"Please reset your password by clicking here."); + + public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => + SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); +} diff --git a/ServiceStack/tests/Blazor/Configure.Db.Migrations.cs b/ServiceStack/tests/Blazor/Configure.Db.Migrations.cs new file mode 100644 index 00000000000..d1371d77425 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.Db.Migrations.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using MyApp.Data; +using MyApp.Migrations; +using MyApp.ServiceModel; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDbMigrations))] + +namespace MyApp; + +// Code-First DB Migrations: https://docs.servicestack.net/ormlite/db-migrations +public class ConfigureDbMigrations : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureAppHost(appHost => { + var migrator = new Migrator(appHost.Resolve(), typeof(Migration1000).Assembly); + AppTasks.Register("migrate", _ => + { + var log = appHost.GetApplicationServices().GetRequiredService>(); + + log.LogInformation("Running EF Migrations..."); + var scopeFactory = appHost.GetApplicationServices().GetRequiredService(); + using (var scope = scopeFactory.CreateScope()) + { + using var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + db.Database.Migrate(); + + // Only seed users if DB was just created + if (!db.Users.Any()) + { + log.LogInformation("Adding Seed Users..."); + AddSeedUsers(scope.ServiceProvider).Wait(); + } + } + + log.LogInformation("Running OrmLite Migrations..."); + migrator.Run(); + }); + AppTasks.Register("migrate.revert", args => migrator.Revert(args[0])); + AppTasks.Register("migrate.rerun", args => migrator.Rerun(args[0])); + AppTasks.Run(); + }); + + private async Task AddSeedUsers(IServiceProvider services) + { + //initializing custom roles + var roleManager = services.GetRequiredService>(); + var userManager = services.GetRequiredService>(); + string[] allRoles = [Roles.Admin, Roles.Manager, Roles.Employee]; + + void assertResult(IdentityResult result) + { + if (!result.Succeeded) + throw new Exception(result.Errors.First().Description); + } + + async Task EnsureUserAsync(ApplicationUser user, string password, string[]? roles = null) + { + var existingUser = await userManager.FindByEmailAsync(user.Email!); + if (existingUser != null) return; + + await userManager!.CreateAsync(user, password); + if (roles?.Length > 0) + { + var newUser = await userManager.FindByEmailAsync(user.Email!); + assertResult(await userManager.AddToRolesAsync(user, roles)); + } + } + + foreach (var roleName in allRoles) + { + var roleExist = await roleManager.RoleExistsAsync(roleName); + if (!roleExist) + { + //Create the roles and seed them to the database + assertResult(await roleManager.CreateAsync(new IdentityRole(roleName))); + } + } + + await EnsureUserAsync(new ApplicationUser + { + DisplayName = "Test User", + Email = "test@email.com", + UserName = "test@email.com", + FirstName = "Test", + LastName = "User", + EmailConfirmed = true, + ProfileUrl = "/img/profiles/user1.svg", + }, "p@55wOrd"); + + await EnsureUserAsync(new ApplicationUser + { + DisplayName = "Test Employee", + Email = "employee@email.com", + UserName = "employee@email.com", + FirstName = "Test", + LastName = "Employee", + EmailConfirmed = true, + ProfileUrl = "/img/profiles/user2.svg", + }, "p@55wOrd", [Roles.Employee]); + + await EnsureUserAsync(new ApplicationUser + { + DisplayName = "Test Manager", + Email = "manager@email.com", + UserName = "manager@email.com", + FirstName = "Test", + LastName = "Manager", + EmailConfirmed = true, + ProfileUrl = "/img/profiles/user3.svg", + }, "p@55wOrd", [Roles.Manager, Roles.Employee]); + + await EnsureUserAsync(new ApplicationUser + { + DisplayName = "Admin User", + Email = "admin@email.com", + UserName = "admin@email.com", + FirstName = "Admin", + LastName = "User", + EmailConfirmed = true, + }, "p@55wOrd", allRoles); + } +} diff --git a/ServiceStack/tests/Blazor/Configure.Db.cs b/ServiceStack/tests/Blazor/Configure.Db.cs new file mode 100644 index 00000000000..24cc05ce39e --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.Db.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using MyApp.Data; +using Microsoft.EntityFrameworkCore.Diagnostics; + +[assembly: HostingStartup(typeof(MyApp.ConfigureDb))] + +namespace MyApp; + +public class ConfigureDb : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + var connectionString = context.Configuration.GetConnectionString("DefaultConnection") + ?? "DataSource=App_Data/app.db;Cache=Shared"; + + services.AddOrmLite(options => options.UseSqlite(connectionString)); + + // $ dotnet ef migrations add CreateIdentitySchema + // $ dotnet ef database update + services.AddDbContext(options => { + options.UseSqlite(connectionString); //, b => b.MigrationsAssembly(nameof(MyApp))); + options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); + }); + + // Enable built-in Database Admin UI at /admin-ui/database + services.AddPlugin(new AdminDatabaseFeature()); + }); +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Configure.HealthChecks.cs b/ServiceStack/tests/Blazor/Configure.HealthChecks.cs new file mode 100644 index 00000000000..dbbb70001b4 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.HealthChecks.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; + +[assembly: HostingStartup(typeof(MyApp.HealthChecks))] + +namespace MyApp; + +public class HealthChecks : IHostingStartup +{ + public class HealthCheck : IHealthCheck + { + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken token = default) + { + // Perform health check logic here + return HealthCheckResult.Healthy(); + } + } + + public void Configure(IWebHostBuilder builder) + { + builder.ConfigureServices(services => + { + services.AddHealthChecks() + .AddCheck("HealthCheck"); + + services.AddTransient(); + }); + } + + public class StartupFilter : IStartupFilter + { + public Action Configure(Action next) + => app => { + app.UseHealthChecks("/up"); + next(app); + }; + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Configure.Markdown.cs b/ServiceStack/tests/Blazor/Configure.Markdown.cs new file mode 100644 index 00000000000..0a34bab2ac2 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.Markdown.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using ServiceStack; +using ServiceStack.IO; + +[assembly: HostingStartup(typeof(MyApp.ConfigureMarkdown))] + +namespace MyApp; + +public class ConfigureMarkdown : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => + { + context.Configuration.GetSection(nameof(AppConfig)).Bind(AppConfig.Instance); + services.AddSingleton(AppConfig.Instance); + services.AddSingleton(); + services.AddSingleton(); + }) + .ConfigureAppHost( + afterPluginsLoaded: appHost => + { + var pages = appHost.Resolve(); + var videos = appHost.Resolve(); + + pages.LoadFrom("_pages"); + videos.LoadFrom("_videos"); + AppConfig.Instance.GitPagesBaseUrl ??= ResolveGitBlobBaseUrl(appHost.ContentRootDirectory); + }); + + private string? ResolveGitBlobBaseUrl(IVirtualDirectory contentDir) + { + var srcDir = new DirectoryInfo(contentDir.RealPath); + var gitConfig = new FileInfo(Path.Combine(srcDir.Parent!.FullName, ".git", "config")); + if (gitConfig.Exists) + { + var txt = gitConfig.ReadAllText(); + var pos = txt.IndexOf("url = ", StringComparison.Ordinal); + if (pos >= 0) + { + var url = txt[(pos + "url = ".Length)..].LeftPart(".git").LeftPart('\n').Trim(); + var gitBaseUrl = url.CombineWith($"blob/main/{srcDir.Name}"); + return gitBaseUrl; + } + } + return null; + } +} + +public class AppConfig +{ + public static AppConfig Instance { get; } = new(); + public string LocalBaseUrl { get; set; } + public string PublicBaseUrl { get; set; } + public string? GitPagesBaseUrl { get; set; } +} + +// Add additional frontmatter info to include +public class MarkdownFileInfo : MarkdownFileBase +{ +} + +public static class HtmlHelpers +{ + public static string ToAbsoluteContentUrl(string? relativePath) => HostContext.DebugMode + ? AppConfig.Instance.LocalBaseUrl.CombineWith(relativePath) + : AppConfig.Instance.PublicBaseUrl.CombineWith(relativePath); + public static string ToAbsoluteApiUrl(string? relativePath) => HostContext.DebugMode + ? AppConfig.Instance.LocalBaseUrl.CombineWith(relativePath) + : AppConfig.Instance.PublicBaseUrl.CombineWith(relativePath); + + + public static string ContentUrl(this IHtmlHelper html, string? relativePath) => ToAbsoluteContentUrl(relativePath); + public static string ApiUrl(this IHtmlHelper html, string? relativePath) => ToAbsoluteApiUrl(relativePath); +} diff --git a/ServiceStack/tests/Blazor/Configure.RequestLogs.cs b/ServiceStack/tests/Blazor/Configure.RequestLogs.cs new file mode 100644 index 00000000000..cbd61313c85 --- /dev/null +++ b/ServiceStack/tests/Blazor/Configure.RequestLogs.cs @@ -0,0 +1,34 @@ +using ServiceStack.Jobs; +using ServiceStack.Web; + +[assembly: HostingStartup(typeof(MyApp.ConfigureRequestLogs))] + +namespace MyApp; + +public class ConfigureRequestLogs : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + + services.AddPlugin(new RequestLogsFeature { + RequestLogger = new SqliteRequestLogger(), + // EnableResponseTracking = true, + EnableRequestBodyTracking = true, + EnableErrorTracking = true + }); + services.AddHostedService(); + }); +} + +public class RequestLogsHostedService(ILogger log, IRequestLogger requestLogger) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var dbRequestLogger = (SqliteRequestLogger)requestLogger; + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3)); + while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken)) + { + dbRequestLogger.Tick(log); + } + } +} diff --git a/ServiceStack/tests/Blazor/Markdown.Pages.cs b/ServiceStack/tests/Blazor/Markdown.Pages.cs new file mode 100644 index 00000000000..12f7f343a25 --- /dev/null +++ b/ServiceStack/tests/Blazor/Markdown.Pages.cs @@ -0,0 +1,137 @@ +// run node postinstall.js to update to latest version +using ServiceStack.IO; + +namespace MyApp; + +public class MarkdownPages(ILogger log, IWebHostEnvironment env, IVirtualFiles fs) + : MarkdownPagesBase(log, env, fs) +{ + public override string Id => "pages"; + + public virtual string? DefaultMenuIcon { get; set; } = + ""; + + List Pages { get; set; } = new(); + public List GetVisiblePages(string? prefix=null, bool allDirectories=false) => prefix == null + ? Pages.Where(x => IsVisible(x) && !x.Slug!.Contains('/')).OrderBy(x => x.Order).ThenBy(x => x.Path).ToList() + : Pages.Where(x => IsVisible(x) && x.Slug!.StartsWith(prefix.WithTrailingSlash())) + .Where(x => allDirectories || (x.Slug.CountOccurrencesOf('/') == prefix.CountOccurrencesOf('/') + 1)) + .OrderBy(x => x.Order).ThenBy(x => x.Path).ToList(); + + public MarkdownFileInfo? GetBySlug(string slug) + { + slug = slug.Trim('/'); + return Fresh(Pages.Where(IsVisible).FirstOrDefault(x => x.Slug == slug)); + } + + public Dictionary> Sidebars { get; set; } = new(); + + public void LoadFrom(string fromDirectory) + { + Sidebars.Clear(); + Pages.Clear(); + var files = VirtualFiles.GetDirectory(fromDirectory).GetAllFiles() + .OrderBy(x => x.VirtualPath) + .ToList(); + log.LogInformation("Found {Count} pages", files.Count); + + var pipeline = CreatePipeline(); + + foreach (var file in files) + { + try + { + if (file.Extension == "md") + { + var doc = Load(file.VirtualPath, pipeline); + if (doc == null) + continue; + + var relativePath = file.VirtualPath[(fromDirectory.Length + 1)..]; + if (relativePath.IndexOf('/') >= 0) + { + doc.Slug = relativePath.LastLeftPart('/') + '/' + doc.Slug; + } + + Pages.Add(doc); + } + else if (file.Name == "sidebar.json") + { + var virtualPath = file.VirtualPath.Substring(fromDirectory.Length); + var folder = virtualPath[..^"sidebar.json".Length].Trim('/'); + var sidebarJson = file.ReadAllText(); + var sidebar = sidebarJson.FromJson>(); + + // If first entry is home and icon is not provided or '' use DefaultMenuIcon + var defaultMenu = sidebar.FirstOrDefault(); + if (defaultMenu?.Link?.Trim('/') == folder && defaultMenu.Icon == null) + { + defaultMenu.Icon = DefaultMenuIcon; + } + Sidebars[folder] = sidebar; + } + } + catch (Exception e) + { + log.LogError(e, "Couldn't load {VirtualPath}: {Message}", file.VirtualPath, e.Message); + } + } + if (Sidebars.Count > 0) + { + log.LogInformation("Loaded {Count} sidebars: {Sidebars}", Sidebars.Count, Sidebars.Keys.Join(", ")); + } + } + + public override List GetAll() => Pages.Where(IsVisible).Map(doc => ToMetaDoc(doc, x => x.Url = $"/{x.Slug}")); + + public virtual List GetSidebar(string folder, MarkdownMenu? defaultMenu=null) + { + if (Sidebars.TryGetValue(folder, out var sidebar)) + return sidebar; + + var allPages = GetVisiblePages(folder); + var allGroups = allPages.Select(x => x.Group) + .Distinct() + .OrderBy(x => x) + .ToList(); + + sidebar = new List(); + foreach (var group in allGroups) + { + MarkdownMenu? menuItem; + if (group == null) + { + menuItem = defaultMenu ?? new MarkdownMenu { + Children = [], + }; + } + else + { + menuItem = new() { + Text = group + }; + } + sidebar.Add(menuItem); + + foreach (var page in allPages.Where(x => x.Group == group).OrderBy(x => x.Order)) + { + menuItem.Children ??= []; + var link = page.Slug!; + if (link.EndsWith("/index")) + { + link = link.Substring(0, link.Length - "index".Length); + // Hide /index from auto Sidebar as it's included in Docs Page Sidebar Header by default + if (link.Trim('/') == folder) + continue; + } + menuItem.Children.Add(new() + { + Text = page.Title!, + Link = link, + }); + } + } + + return sidebar; + } +} diff --git a/ServiceStack/tests/Blazor/Markdown.Videos.cs b/ServiceStack/tests/Blazor/Markdown.Videos.cs new file mode 100644 index 00000000000..e9104039f6c --- /dev/null +++ b/ServiceStack/tests/Blazor/Markdown.Videos.cs @@ -0,0 +1,61 @@ +// BSD License: https://docs.servicestack.net/BSD-LICENSE.txt +// run node postinstall.js to update to latest version +using ServiceStack.IO; + +namespace MyApp; + +public class MarkdownVideos(ILogger log, IWebHostEnvironment env, IVirtualFiles fs) + : MarkdownPagesBase(log, env, fs) +{ + public override string Id => "videos"; + public Dictionary> Groups { get; set; } = new(); + + public List GetVideos(string group) + { + return Groups.TryGetValue(group, out var docs) + ? Fresh(docs.Where(IsVisible).OrderBy(x => x.Order).ThenBy(x => x.FileName).ToList()) + : []; + } + + public void LoadFrom(string fromDirectory) + { + Groups.Clear(); + var dirs = VirtualFiles.GetDirectory(fromDirectory).GetDirectories().ToList(); + log.LogInformation("Found {Count} video directories", dirs.Count); + + var pipeline = CreatePipeline(); + + foreach (var dir in dirs) + { + var group = dir.Name; + + foreach (var file in dir.GetFiles().OrderBy(x => x.Name)) + { + try + { + var doc = Load(file.VirtualPath, pipeline); + if (doc == null) + continue; + + doc.Group = group; + var groupVideos = Groups.GetOrAdd(group, v => new List()); + groupVideos.Add(doc); + } + catch (Exception e) + { + log.LogError(e, "Couldn't load {VirtualPath}: {Message}", file.VirtualPath, e.Message); + } + } + } + } + + public override List GetAll() + { + var to = new List(); + foreach (var entry in Groups) + { + to.AddRange(entry.Value.Where(IsVisible).Map(doc => ToMetaDoc(doc, x => x.Content = StripFrontmatter(doc.Content)))); + } + return to; + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/MarkdownPagesBase.cs b/ServiceStack/tests/Blazor/MarkdownPagesBase.cs new file mode 100644 index 00000000000..d418ba8b879 --- /dev/null +++ b/ServiceStack/tests/Blazor/MarkdownPagesBase.cs @@ -0,0 +1,720 @@ +// run node postinstall.js to update to latest version +using Markdig; +using Markdig.Parsers; +using Markdig.Parsers.Inlines; +using Markdig.Renderers; +using Markdig.Renderers.Html; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; +using Markdig.Extensions.CustomContainers; +using ServiceStack.IO; +using ServiceStack.Text; + +namespace MyApp; + +public class MarkdigConfig +{ + public static MarkdigConfig Instance { get; private set; } = new(); + public Action? ConfigurePipeline { get; set; } + public Action ConfigureContainers { get; set; } = x => x.AddBuiltInContainers(); + + public static void Set(MarkdigConfig config) + { + Instance = config; + } +} + +public class MarkdownFileBase +{ + public string Path { get; set; } = default!; + public string? Slug { get; set; } + public string? Layout { get; set; } + public string? FileName { get; set; } + public string? HtmlFileName { get; set; } + /// + /// Whether to hide this document in Production + /// + public bool Draft { get; set; } + public string? Title { get; set; } + public string? Summary { get; set; } + public string? Image { get; set; } + public string? Author { get; set; } + public List Tags { get; set; } = new(); + /// + /// Date document is published. Documents with future Dates are only shown in Development + /// + public DateTime? Date { get; set; } + public string? Content { get; set; } + public string? Url { get; set; } + /// + /// The rendered HTML of the Markdown + /// + public string? Preview { get; set; } + public string? HtmlPage { get; set; } + public int? WordCount { get; set; } + public int? LineCount { get; set; } + public string? Group { get; set; } + public int? Order { get; set; } + public DocumentMap? DocumentMap { get; set; } + + /// + /// Update Markdown File to latest version + /// + /// + public virtual void Update(MarkdownFileBase newDoc) + { + Layout = newDoc.Layout; + Title = newDoc.Title; + Summary = newDoc.Summary; + Draft = newDoc.Draft; + Image = newDoc.Image; + Author = newDoc.Author; + Tags = newDoc.Tags; + Content = newDoc.Content; + Url = newDoc.Url; + Preview = newDoc.Preview; + HtmlPage = newDoc.HtmlPage; + WordCount = newDoc.WordCount; + LineCount = newDoc.LineCount; + Group = newDoc.Group; + Order = newDoc.Order; + DocumentMap = newDoc.DocumentMap; + + if (newDoc.Date != null) + Date = newDoc.Date; + } +} + +public interface IMarkdownPages +{ + string Id { get; } + List GetAll(); +} + +public abstract class MarkdownPagesBase(ILogger log, IWebHostEnvironment env, IVirtualFiles fs) : IMarkdownPages + where T : MarkdownFileBase +{ + public abstract string Id { get; } + public IVirtualFiles VirtualFiles => fs; + + public virtual MarkdownPipeline CreatePipeline() + { + var builder = new MarkdownPipelineBuilder() + .UseYamlFrontMatter() + .UseAdvancedExtensions() + .UseAutoLinkHeadings() + .UseHeadingsMap() + .UseCustomContainers(MarkdigConfig.Instance.ConfigureContainers); + MarkdigConfig.Instance.ConfigurePipeline?.Invoke(builder); + + var pipeline = builder.Build(); + return pipeline; + } + + public virtual List Fresh(List docs) + { + if (docs.IsEmpty()) + return docs; + foreach (var doc in docs) + { + Fresh(doc); + } + return docs; + } + + public virtual T? Fresh(T? doc) + { + // Ignore reloading source .md if run in production or as AppTask + if (doc == null || !env.IsDevelopment() || AppTasks.IsRunAsAppTask()) + return doc; + var newDoc = Load(doc.Path); + doc.Update(newDoc); + return doc; + } + + public virtual T CreateMarkdownFile(string content, TextWriter writer, MarkdownPipeline? pipeline = null) + { + pipeline ??= CreatePipeline(); + + var renderer = new Markdig.Renderers.HtmlRenderer(writer); + pipeline.Setup(renderer); + + var document = Markdown.Parse(content, pipeline); + renderer.Render(document); + + var block = document + .Descendants() + .FirstOrDefault(); + + var doc = block? + .Lines // StringLineGroup[] + .Lines // StringLine[] + .Select(x => $"{x}\n") + .ToList() + .Select(x => x.Replace("---", string.Empty)) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => KeyValuePairs.Create(x.LeftPart(':').Trim(), x.RightPart(':').Trim())) + .ToObjectDictionary() + .ConvertTo() + ?? typeof(T).CreateInstance(); + + doc.Tags = doc.Tags.Map(x => x.Trim()); + doc.Content = content; + doc.DocumentMap = document.GetData(nameof(DocumentMap)) as DocumentMap; + + return doc; + } + + public virtual T? Load(string path, MarkdownPipeline? pipeline = null) + { + var file = fs.GetFile(path) + ?? throw new FileNotFoundException(path.LastRightPart('/')); + var content = file.ReadAllText(); + + var writer = new StringWriter(); + + var doc = CreateMarkdownFile(content, writer, pipeline); + doc.Title ??= file.Name; + + doc.Path = file.VirtualPath; + doc.FileName = file.Name; + doc.Slug = doc.FileName.WithoutExtension().GenerateSlug(); + doc.Content = content; + doc.WordCount = WordCount(content); + doc.LineCount = LineCount(content); + writer.Flush(); + doc.Preview = writer.ToString(); + doc.Date ??= file.LastModified; + + return doc; + } + + public virtual bool IsVisible(T doc) => env.IsDevelopment() || + !doc.Draft && (doc.Date == null || doc.Date.Value <= DateTime.UtcNow); + + public int WordsPerMin { get; set; } = 225; + public char[] WordBoundaries { get; set; } = { ' ', '.', '?', '!', '(', ')', '[', ']' }; + public virtual int WordCount(string str) => str.Split(WordBoundaries, StringSplitOptions.RemoveEmptyEntries).Length; + public virtual int LineCount(string str) => str.CountOccurrencesOf('\n'); + public virtual int MinutesToRead(int? words) => (int)Math.Ceiling((words ?? 1) / (double)WordsPerMin); + + public virtual List GetAll() => new(); + + public virtual string? StripFrontmatter(string? content) + { + if (content == null) + return null; + var startPos = content.IndexOf("---", StringComparison.CurrentCulture); + if (startPos == -1) + return content; + var endPos = content.IndexOf("---", startPos + 3, StringComparison.Ordinal); + if (endPos == -1) + return content; + return content.Substring(endPos + 3).Trim(); + } + + public virtual MarkdownFileBase ToMetaDoc(T x, Action? fn = null) + { + var to = new MarkdownFileBase + { + Slug = x.Slug, + Title = x.Title, + Summary = x.Summary, + Date = x.Date, + Tags = x.Tags, + Author = x.Author, + Image = x.Image, + WordCount = x.WordCount, + LineCount = x.LineCount, + Url = x.Url, + Group = x.Group, + Order = x.Order, + }; + fn?.Invoke(to); + return to; + } + + /// + /// Need to escape '{{' and '}}' template literals when using content inside a Vue template + /// + public virtual string? SanitizeVueTemplate(string? content) + { + if (content == null) + return null; + + return content + .Replace("{{", "{‎{") + .Replace("}}", "}‎}"); + } +} + +public struct HeadingInfo +{ + public int Level { get; } + public string Id { get; } + public string Content { get; } + + public HeadingInfo(int level, string id, string content) + { + Level = level; + Id = id; + Content = content; + } +} + +/// +/// An HTML renderer for a . +/// +/// +public class AutoLinkHeadingRenderer : HtmlObjectRenderer +{ + private static readonly string[] HeadingTexts = { + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + }; + public event Action? OnHeading; + + protected override void Write(HtmlRenderer renderer, HeadingBlock obj) + { + int index = obj.Level - 1; + string[] headings = HeadingTexts; + string headingText = ((uint)index < (uint)headings.Length) + ? headings[index] + : $"h{obj.Level}"; + + if (renderer.EnableHtmlForBlock) + { + renderer.Write('<'); + renderer.Write(headingText); + renderer.WriteAttributes(obj); + renderer.Write('>'); + } + + renderer.WriteLeafInline(obj); + + var attrs = obj.TryGetAttributes(); + if (attrs?.Id != null && obj.Level <= 4) + { + renderer.Write(""); + } + + if (renderer.EnableHtmlForBlock) + { + renderer.Write("'); + } + + renderer.EnsureLine(); + + OnHeading?.Invoke(obj); + } +} +public class AutoLinkHeadingsExtension : IMarkdownExtension +{ + public void Setup(MarkdownPipelineBuilder pipeline) + { + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) + { + renderer.ObjectRenderers.Replace(new AutoLinkHeadingRenderer()); + } +} + +public class CopyContainerRenderer : HtmlObjectRenderer +{ + public string Class { get; set; } = ""; + public string BoxClass { get; set; } = "bg-gray-700"; + public string IconClass { get; set; } = ""; + public string TextClass { get; set; } = "text-lg text-white"; + protected override void Write(HtmlRenderer renderer, CustomContainer obj) + { + renderer.EnsureLine(); + if (renderer.EnableHtmlForBlock) + { + renderer.Write(@$"
+
+
"); + } + // We don't escape a CustomContainer + renderer.WriteChildren(obj); + if (renderer.EnableHtmlForBlock) + { + renderer.WriteLine(@$"
+
+
+
+ + + + +
+
+
"); + } + } +} + +public class CustomInfoRenderer : HtmlObjectRenderer +{ + public string Title { get; set; } = "TIP"; + public string Class { get; set; } = "tip"; + protected override void Write(HtmlRenderer renderer, CustomContainer obj) + { + renderer.EnsureLine(); + if (renderer.EnableHtmlForBlock) + { + var title = obj.Arguments ?? obj.Info; + if (string.IsNullOrEmpty(title)) + title = Title; + renderer.Write(@$"
+

{title}

"); + } + // We don't escape a CustomContainer + renderer.WriteChildren(obj); + if (renderer.EnableHtmlForBlock) + { + renderer.WriteLine("
"); + } + } +} + +/// +/// Render HTML-encoded inline contents inside a >pre class="pre"/< +/// +public class PreContainerRenderer : HtmlObjectRenderer +{ + public string Class { get; set; } = "pre"; + protected override void Write(HtmlRenderer renderer, CustomContainer obj) + { + renderer.EnsureLine(); + if (renderer.EnableHtmlForBlock) + { + var attrs = obj.TryGetAttributes(); + if (attrs != null && attrs.Classes.IsEmpty()) + { + attrs.Classes ??= new(); + attrs.Classes.Add(Class); + } + renderer.Write("'); + renderer.WriteLine(); + } + + if (obj.FirstOrDefault() is LeafBlock leafBlock) + { + // There has to be an official API to resolve the original text from a renderer? + string? FindOriginalText(ContainerBlock? block) + { + if (block != null) + { + if (block.FirstOrDefault(x => x is LeafBlock { Lines.Count: > 0 }) is LeafBlock first) + return first.Lines.Lines[0].Slice.Text; + return FindOriginalText(block.Parent); + } + return null; + } + var originalSource = leafBlock.Lines.Count > 0 + ? leafBlock.Lines.Lines[0].Slice.Text + : FindOriginalText(obj.Parent); + if (originalSource == null) + { + HostContext.Resolve>().LogError("Could not find original Text"); + renderer.WriteLine($"Could not find original Text"); + } + else + { + renderer.WriteEscape(originalSource.AsSpan().Slice(leafBlock.Span.Start, leafBlock.Span.Length)); + } + } + else + { + renderer.WriteChildren(obj); + } + + if (renderer.EnableHtmlForBlock) + { + renderer.WriteLine(""); + } + } +} + +public class IncludeContainerInlineRenderer : HtmlObjectRenderer +{ + protected override void Write(HtmlRenderer renderer, CustomContainerInline obj) + { + var include = obj.FirstChild is LiteralInline literalInline + ? literalInline.Content.AsSpan().RightPart(' ').ToString() + : null; + if (string.IsNullOrEmpty(include)) + return; + + renderer.Write("'); + MarkdownFileBase? doc = null; + if (include.EndsWith(".md")) + { + var markdown = HostContext.Resolve(); + // default relative path to _includes/ + include = include[0] != '/' + ? "_includes/" + include + : include.TrimStart('/'); + var prefix = include.LeftPart('/'); + var slug = include.LeftPart('.'); + var allIncludes = markdown.GetVisiblePages(prefix, allDirectories: true); + doc = allIncludes.FirstOrDefault(x => x.Slug == slug); + } + + if (doc?.Preview != null) + { + renderer.WriteLine(doc.Preview!); + } + else + { + var log = HostContext.Resolve>(); + log.LogError("Could not find: {Include}", include); + renderer.WriteLine($"Could not find: {include}"); + } + + renderer.Write(""); + } +} + +public class CustomContainerRenderers(ContainerExtensions extensions) : HtmlObjectRenderer +{ + public ContainerExtensions Extensions { get; } = extensions; + + protected override void Write(HtmlRenderer renderer, CustomContainer obj) + { + var useRenderer = obj.Info != null && Extensions.BlockContainers.TryGetValue(obj.Info, out var customRenderer) + ? customRenderer + : new HtmlCustomContainerRenderer(); + useRenderer.Write(renderer, obj); + } +} + +public class CustomContainerInlineRenderers(ContainerExtensions extensions) : HtmlObjectRenderer +{ + public ContainerExtensions Extensions { get; } = extensions; + + protected override void Write(HtmlRenderer renderer, CustomContainerInline obj) + { + var firstWord = obj.FirstChild is LiteralInline literalInline + ? literalInline.Content.AsSpan().LeftPart(' ').ToString() + : null; + var useRenderer = firstWord != null && Extensions.InlineContainers.TryGetValue(firstWord, out var customRenderer) + ? customRenderer + : new HtmlCustomContainerInlineRenderer(); + useRenderer.Write(renderer, obj); + } +} + +public class ContainerExtensions : IMarkdownExtension +{ + public Dictionary> BlockContainers { get; set; } = new(); + public Dictionary> InlineContainers { get; set; } = new(); + + public void AddBlockContainer(string name, HtmlObjectRenderer container) => + BlockContainers[name] = container; + public void AddInlineContainer(string name, HtmlObjectRenderer container) => + InlineContainers[name] = container; + + public void AddBuiltInContainers(string[]? exclude = null) + { + BlockContainers = new() + { + ["copy"] = new CopyContainerRenderer + { + Class = "not-prose copy cp", + IconClass = "bg-sky-500", + }, + ["sh"] = new CopyContainerRenderer + { + Class = "not-prose sh-copy cp", + BoxClass = "bg-gray-800", + IconClass = "bg-green-600", + TextClass = "whitespace-pre text-base text-gray-100", + }, + ["tip"] = new CustomInfoRenderer(), + ["info"] = new CustomInfoRenderer + { + Class = "info", + Title = "INFO", + }, + ["warning"] = new CustomInfoRenderer + { + Class = "warning", + Title = "WARNING", + }, + ["danger"] = new CustomInfoRenderer + { + Class = "danger", + Title = "DANGER", + }, + ["pre"] = new PreContainerRenderer(), + }; + InlineContainers = new() + { + ["include"] = new IncludeContainerInlineRenderer(), + }; + + if (exclude != null) + { + foreach (var name in exclude) + { + BlockContainers.TryRemove(name, out _); + InlineContainers.TryRemove(name, out _); + } + } + } + + public void Setup(MarkdownPipelineBuilder pipeline) + { + if (!pipeline.BlockParsers.Contains()) + { + // Insert the parser before any other parsers + pipeline.BlockParsers.Insert(0, new CustomContainerParser()); + } + + // Plug the inline parser for CustomContainerInline + var inlineParser = pipeline.InlineParsers.Find(); + if (inlineParser != null && !inlineParser.HasEmphasisChar(':')) + { + inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true)); + inlineParser.TryCreateEmphasisInlineList.Add((emphasisChar, delimiterCount) => + { + if (delimiterCount >= 2 && emphasisChar == ':') + { + return new CustomContainerInline(); + } + return null; + }); + } + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) + { + if (renderer is HtmlRenderer htmlRenderer) + { + if (!htmlRenderer.ObjectRenderers.Contains()) + { + // Must be inserted before CodeBlockRenderer + htmlRenderer.ObjectRenderers.Insert(0, new CustomContainerRenderers(this)); + } + htmlRenderer.ObjectRenderers.TryRemove(); + // Must be inserted before EmphasisRenderer + htmlRenderer.ObjectRenderers.Insert(0, new CustomContainerInlineRenderers(this)); + } + } +} + +public class HeadingsMapExtension : IMarkdownExtension +{ + public void Setup(MarkdownPipelineBuilder pipeline) + { + var headingBlockParser = pipeline.BlockParsers.Find(); + if (headingBlockParser != null) + { + // Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed + // headingBlockParser.Closed -= HeadingBlockParser_Closed; + // headingBlockParser.Closed += HeadingBlockParser_Closed; + } + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) + { + if (renderer.ObjectRenderers.TryFind(out var customHeader)) + { + customHeader.OnHeading += OnHeading; + } + } + + private void OnHeading(HeadingBlock headingBlock) + { + if (headingBlock.Parent is not MarkdownDocument document) + return; + + if (document.GetData(nameof(DocumentMap)) is not DocumentMap docMap) + { + docMap = new(); + document.SetData(nameof(DocumentMap), docMap); + } + + var text = headingBlock.Inline?.FirstChild is LiteralInline literalInline + ? literalInline.ToString() + : null; + var attrs = headingBlock.TryGetAttributes(); + + if (!string.IsNullOrEmpty(text) && attrs?.Id != null) + { + if (headingBlock.Level == 2) + { + docMap.Headings.Add(new MarkdownMenu { + Text = text, + Link = $"#{attrs.Id}", + }); + } + else if (headingBlock.Level == 3) + { + var lastHeading = docMap.Headings.LastOrDefault(); + if (lastHeading != null) + { + lastHeading.Children ??= new(); + lastHeading.Children.Add(new MarkdownMenuItem { + Text = text, + Link = $"#{attrs.Id}", + }); + } + } + } + } +} + +public static class MarkdigExtensions +{ + /// + /// Uses the auto-identifier extension. + /// + public static MarkdownPipelineBuilder UseAutoLinkHeadings(this MarkdownPipelineBuilder pipeline) + { + pipeline.Extensions.AddIfNotAlready(new AutoLinkHeadingsExtension()); + return pipeline; + } + + public static MarkdownPipelineBuilder UseHeadingsMap(this MarkdownPipelineBuilder pipeline) + { + pipeline.Extensions.AddIfNotAlready(new HeadingsMapExtension()); + return pipeline; + } + + public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineBuilder pipeline, Action? configure = null) + { + var ext = new ContainerExtensions(); + configure?.Invoke(ext); + pipeline.Extensions.AddIfNotAlready(ext); + return pipeline; + } +} + +public class DocumentMap +{ + public List Headings { get; } = new(); +} + +public class MarkdownMenu +{ + public string? Icon { get; set; } + public string? Text { get; set; } + public string? Link { get; set; } + public List? Children { get; set; } +} +public class MarkdownMenuItem +{ + public string Text { get; set; } + public string Link { get; set; } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.Designer.cs b/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 00000000000..cb96a6913bc --- /dev/null +++ b/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,280 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240301000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.cs b/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.cs new file mode 100644 index 00000000000..c8b0782e3f0 --- /dev/null +++ b/ServiceStack/tests/Blazor/Migrations/20240301000000_CreateIdentitySchema.cs @@ -0,0 +1,226 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyApp.Migrations +{ + /// + public partial class CreateIdentitySchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + ProfileUrl = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/ServiceStack/tests/Blazor/Migrations/ApplicationDbContextModelSnapshot.cs b/ServiceStack/tests/Blazor/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 00000000000..8984a6a0560 --- /dev/null +++ b/ServiceStack/tests/Blazor/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,277 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyApp.Data; + +#nullable disable + +namespace MyApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("MyApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProfileUrl") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MyApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ServiceStack/tests/Blazor/Migrations/Migration1000.cs b/ServiceStack/tests/Blazor/Migrations/Migration1000.cs new file mode 100644 index 00000000000..b292ad41770 --- /dev/null +++ b/ServiceStack/tests/Blazor/Migrations/Migration1000.cs @@ -0,0 +1,84 @@ +using System.Data; +using ServiceStack; +using ServiceStack.DataAnnotations; +using ServiceStack.OrmLite; + +namespace MyApp.Migrations; + +public class Migration1000 : MigrationBase +{ + public class Booking : AuditBase + { + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } = default!; + public RoomType RoomType { get; set; } + public int RoomNumber { get; set; } + public DateTime BookingStartDate { get; set; } + public DateTime? BookingEndDate { get; set; } + public decimal Cost { get; set; } + public string? Notes { get; set; } + public bool? Cancelled { get; set; } + + [References(typeof(Coupon))] + public string? CouponId { get; set; } + } + + public class Coupon + { + public string Id { get; set; } = default!; + public string Description { get; set; } = default!; + public int Discount { get; set; } + public DateTime ExpiryDate { get; set; } + } + + public enum RoomType + { + Queen, + Double, + Suite, + } + + public override void Up() + { + Db.CreateTable(); + Db.CreateTable(); + + new[] { 5, 10, 15, 20, 25, 30, 40, 50, 60, 70, }.Each(percent => { + Db.Insert(new Coupon + { + Id = $"BOOK{percent}", + Description = $"{percent}% off", + Discount = percent, + ExpiryDate = DateTime.UtcNow.AddDays(30) + }); + }); + + CreateBooking(Db, "First Booking!", RoomType.Queen, 10, 100, "BOOK10", "employee@email.com"); + CreateBooking(Db, "Booking 2", RoomType.Double, 12, 120, "BOOK25", "manager@email.com"); + CreateBooking(Db, "Booking the 3rd", RoomType.Suite, 13, 130, null, "employee@email.com"); + } + + public void CreateBooking(IDbConnection? db, + string name, RoomType type, int roomNo, decimal cost, string? couponId, string by) => + db.Insert(new Booking + { + Name = name, + RoomType = type, + RoomNumber = roomNo, + Cost = cost, + BookingStartDate = DateTime.UtcNow.AddDays(roomNo), + BookingEndDate = DateTime.UtcNow.AddDays(roomNo + 7), + CouponId = couponId, + CreatedBy = by, + CreatedDate = DateTime.UtcNow, + ModifiedBy = by, + ModifiedDate = DateTime.UtcNow, + }); + + public override void Down() + { + Db.DropTable(); + Db.DropTable(); + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/Program.cs b/ServiceStack/tests/Blazor/Program.cs new file mode 100644 index 00000000000..ff870b3cb19 --- /dev/null +++ b/ServiceStack/tests/Blazor/Program.cs @@ -0,0 +1,93 @@ +using System.Net; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using ServiceStack.Blazor; +using MyApp.Data; +using MyApp.Components; +using MyApp.Components.Account; +using MyApp.ServiceInterface; + +var builder = WebApplication.CreateBuilder(args); + +var services = builder.Services; +var config = builder.Configuration; + +// Add services to the container. +services.AddRazorComponents() + .AddInteractiveServerComponents(); + +// services.AddCascadingAuthenticationState(); +// services.AddScoped(); +// services.AddScoped(); +// services.AddScoped(); + +services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(options => options.DisableRedirectsForApis()); +services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo("App_Data")); + +services.AddDatabaseDeveloperPageExceptionFilter(); + +services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles() + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); + +services.AddSingleton, IdentityNoOpEmailSender>(); +// Uncomment to send emails with SMTP, configure SMTP with "SmtpConfig" in appsettings.json +// services.AddSingleton, EmailSender>(); +// services.AddScoped, AdditionalUserClaimsPrincipalFactory>(); +services.AddScoped, AdditionalUserClaimsPrincipalFactory>(); + +var baseUrl = builder.Configuration["ApiBaseUrl"] ?? + (builder.Environment.IsDevelopment() ? "https://localhost:5001" : "http://" + IPAddress.Loopback); +services.AddScoped(c => new HttpClient { BaseAddress = new Uri(baseUrl) }); +services.AddBlazorServerIdentityApiClient(baseUrl); +services.AddLocalStorage(); + +// Register all services +services.AddServiceStack(typeof(MyServices).Assembly); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseMigrationsEndPoint(); +} +else +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.UseServiceStack(new AppHost(), options => { + options.MapEndpoints(); +}); + +BlazorConfig.Set(new() +{ + Services = app.Services, + JSParseObject = JS.ParseObject, + IsDevelopment = app.Environment.IsDevelopment(), + EnableLogging = app.Environment.IsDevelopment(), + EnableVerboseLogging = app.Environment.IsDevelopment(), +}); + +app.Run(); diff --git a/ServiceStack/tests/Blazor/Properties/launchSettings.json b/ServiceStack/tests/Blazor/Properties/launchSettings.json new file mode 100644 index 00000000000..72b372f4122 --- /dev/null +++ b/ServiceStack/tests/Blazor/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8080", + "sslPort": 44300 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ServiceStack/tests/Blazor/ServiceInterface/CheckUrlsCommand.cs b/ServiceStack/tests/Blazor/ServiceInterface/CheckUrlsCommand.cs new file mode 100644 index 00000000000..dadee8b7f3e --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/CheckUrlsCommand.cs @@ -0,0 +1,162 @@ +using ServiceStack; +using ServiceStack.Jobs; + +namespace MyApp.ServiceInterface; + +public class QueueCheckUrlsResponse +{ + public BackgroundJobRef JobRef { get; set; } +} + +public class QueueCheckUrls : IReturn +{ + [Input(Type = "textarea")] public string Urls { get; set; } +} + +public class CheckUrlServices(IBackgroundJobs jobs, IHttpClientFactory httpClientFactory) : Service +{ + public object Any(QueueCheckUrls request) + { + var jobRef = jobs.EnqueueCommand(new CheckUrls + { + Urls = request.Urls.Split("\n").ToList() + }, new() + { + Worker = nameof(CheckUrlsCommand), + Callback = nameof(CheckUrlsReportCommand) + }); + + return new QueueCheckUrlsResponse + { + JobRef = jobRef + }; + } + + public async Task Any(CheckUrl request) + { + var url = request.Url.Trim(); + using var client = httpClientFactory.CreateClient(); + var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url)); + response.EnsureSuccessStatusCode(); + return new CheckUrlResponse { Url = url, Result = true }; + } + + public object Any(QueueCheckUrlApi request) + { + var jobRef = jobs.EnqueueApi(new CheckUrl { Url = request.Url }, + new() { + Worker = nameof(CheckUrlsCommand), + Callback = nameof(CheckUrlsReportCommand) + }); + + return new QueueCheckUrlsResponse + { + JobRef = jobRef + }; + } +} + +public class CheckUrls +{ + public List Urls { get; set; } = []; +} + +public class CheckUrlsResult +{ + public Dictionary UrlStatuses { get; set; } = []; +} + +public class CheckUrl : IReturn +{ + [ValidateNotEmpty] public required string Url { get; set; } +} + +public class CheckUrlResponse : IHasResponseStatus +{ + public string Url { get; set; } + public bool Result { get; set; } + public ResponseStatus? ResponseStatus { get; set; } +} + +public class QueueCheckUrlApi : IReturn +{ + [ValidateNotEmpty] public required string Url { get; set; } +} + +// Command implementation +public class CheckUrlsCommand( + IHttpClientFactory httpClientFactory, + IBackgroundJobs jobs, + ILogger logger) : AsyncCommandWithResult +{ + protected override async Task RunAsync(CheckUrls request, CancellationToken token) + { + var result = new CheckUrlsResult + { + UrlStatuses = new Dictionary() + }; + + var job = Request.TryGetBackgroundJob(); + + // Create a JobLogger to log messages to the background job + var log = Request.CreateJobLogger(jobs, logger); + + log.LogInformation("Checking {Count} URLs", request.Urls.Count); + + using var client = httpClientFactory.CreateClient(); + // Set a timeout of 3 seconds for each request + client.Timeout = TimeSpan.FromSeconds(3); + var batchId = Guid.NewGuid().ToString("N"); + + foreach (var url in request.Urls.Where(x => !string.IsNullOrEmpty(x.Trim()))) + { + try + { + var testUrl = url.Trim(); + log.LogInformation("Checking {Url}", testUrl); + // Send a HEAD request to check if the URL is up + var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url), token); + response.EnsureSuccessStatusCode(); + + result.UrlStatuses[url] = response.IsSuccessStatusCode; + log.LogInformation("{Url} is {Status}", testUrl, response.IsSuccessStatusCode ? "up" : "down"); + await Task.Delay(1000, token); + } + catch (Exception e) + { + log.LogError(e, "Error checking url {Url}: {Message}", url, e.Message); + // If an exception occurs (e.g., network error), consider the URL as down + result.UrlStatuses[url] = false; + } + // jobs.EnqueueCommand(new SendEmail { + // To = "demis.bellot@gmail.com", + // Subject = $"{testUrl} status", + // BodyText = $"{testUrl} is " + (response.IsSuccessStatusCode ? "up" : "down"), + // }, new() { + // DependsOn = job.Id, + // BatchId = batchId, + // }); + } + + log.LogInformation("Finished checking URLs, {Up} up, {Down} down", + result.UrlStatuses.Values.Count(x => x), result.UrlStatuses.Values.Count(x => !x)); + + return result; + } +} + +public class CheckUrlsReportCommand( + ILogger logger, + IBackgroundJobs jobs) : AsyncCommand +{ + protected override async Task RunAsync(CheckUrlsResult request, CancellationToken token) + { + var log = Request.CreateJobLogger(jobs, logger); + log.LogInformation("Reporting on {Count} URLs", request.UrlStatuses.Count); + foreach (var (url, status) in request.UrlStatuses) + { + log.LogInformation("{Url} is {Status}", url, status ? "up" : "down"); + await Task.Delay(1000, token); + } + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationDbContext.cs b/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationDbContext.cs new file mode 100644 index 00000000000..6a4a94c20ea --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace MyApp.Data; + +public class ApplicationDbContext(DbContextOptions options) + : IdentityDbContext(options) +{ +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationUser.cs b/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationUser.cs new file mode 100644 index 00000000000..d4e19cf1f2e --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/Data/ApplicationUser.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Identity; + +namespace MyApp.Data; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser +{ + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? DisplayName { get; set; } + public string? ProfileUrl { get; set; } +} diff --git a/ServiceStack/tests/Blazor/ServiceInterface/Data/CustomUserSession.cs b/ServiceStack/tests/Blazor/ServiceInterface/Data/CustomUserSession.cs new file mode 100644 index 00000000000..5c6be8730f6 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/Data/CustomUserSession.cs @@ -0,0 +1,42 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using ServiceStack; +using ServiceStack.Web; + +namespace MyApp.Data; + +public class CustomUserSession : AuthUserSession +{ + public override void PopulateFromClaims(IRequest httpReq, ClaimsPrincipal principal) + { + // Populate Session with data from Identity Auth Claims + ProfileUrl = principal.FindFirstValue(JwtClaimTypes.Picture); + } +} + +/// +/// Add additional claims to the Identity Auth Cookie +/// +public class AdditionalUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IOptions optionsAccessor) + : UserClaimsPrincipalFactory(userManager, roleManager, optionsAccessor) +{ + public override async Task CreateAsync(ApplicationUser user) + { + var principal = await base.CreateAsync(user); + var identity = (ClaimsIdentity)principal.Identity!; + + var claims = new List(); + // Add additional claims here + if (user.ProfileUrl != null) + { + claims.Add(new Claim(JwtClaimTypes.Picture, user.ProfileUrl)); + } + + identity.AddClaims(claims); + return principal; + } +} diff --git a/ServiceStack/tests/Blazor/ServiceInterface/EmailServices.cs b/ServiceStack/tests/Blazor/ServiceInterface/EmailServices.cs new file mode 100644 index 00000000000..dc01b35c595 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/EmailServices.cs @@ -0,0 +1,94 @@ +using System.Net.Mail; +using Microsoft.Extensions.Logging; +using ServiceStack; +using ServiceStack.Jobs; + +namespace MyApp.ServiceInterface; + +/// +/// Configuration for sending emails using SMTP servers in EmailServices +/// E.g. for managed services like Amazon (SES): https://aws.amazon.com/ses/ or https://mailtrap.io +/// +public class SmtpConfig +{ + /// + /// Username of the SMTP Server account + /// + public string Username { get; set; } + /// + /// Password of the SMTP Server account + /// + public string Password { get; set; } + /// + /// Hostname of the SMTP Server + /// + public string Host { get; set; } + /// + /// Port of the SMTP Server + /// + public int Port { get; set; } = 587; + /// + /// Which email address to send emails from + /// + public string FromEmail { get; set; } + /// + /// The name of the Email Sender + /// + public string? FromName { get; set; } + /// + /// Prevent emails from being sent to real users during development by sending to this Dev email instead + /// + public string? DevToEmail { get; set; } + /// + /// Keep a copy of all emails sent by BCC'ing a copy to this email address + /// + public string? Bcc { get; set; } +} + +public class SendEmail +{ + public string To { get; set; } + public string? ToName { get; set; } + public string Subject { get; set; } + public string? BodyText { get; set; } + public string? BodyHtml { get; set; } +} + +[Worker("smtp")] +public class SendEmailCommand(ILogger logger, IBackgroundJobs jobs, SmtpConfig config) + : SyncCommand +{ + private static long count = 0; + protected override void Run(SendEmail request) + { + Interlocked.Increment(ref count); + var log = Request.CreateJobLogger(jobs, logger); + log.LogInformation("Sending {Count} email to {Email} with subject {Subject}", + count, request.To, request.Subject); + + using var client = new SmtpClient(config.Host, config.Port); + client.Credentials = new System.Net.NetworkCredential(config.Username, config.Password); + client.EnableSsl = true; + + // If DevToEmail is set, send all emails to that address instead + var emailTo = config.DevToEmail != null + ? new MailAddress(config.DevToEmail) + : new MailAddress(request.To, request.ToName); + + var emailFrom = new MailAddress(config.FromEmail, config.FromName); + + var msg = new MailMessage(emailFrom, emailTo) + { + Subject = request.Subject, + Body = request.BodyHtml ?? request.BodyText, + IsBodyHtml = request.BodyHtml != null, + }; + + if (config.Bcc != null) + { + msg.Bcc.Add(new MailAddress(config.Bcc)); + } + + client.Send(msg); + } +} diff --git a/ServiceStack/tests/Blazor/ServiceInterface/MyServices.cs b/ServiceStack/tests/Blazor/ServiceInterface/MyServices.cs new file mode 100644 index 00000000000..e0a97b740c0 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/MyServices.cs @@ -0,0 +1,11 @@ +using MyApp.ServiceModel; + +namespace MyApp.ServiceInterface; + +public class MyServices : Service +{ + public object Any(Hello request) + { + return new HelloResponse { Result = $"Hello, {request.Name}!" }; + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/ServiceInterface/TodosServices.cs b/ServiceStack/tests/Blazor/ServiceInterface/TodosServices.cs new file mode 100644 index 00000000000..7d464ec8192 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceInterface/TodosServices.cs @@ -0,0 +1,34 @@ +using MyApp.ServiceModel; + +namespace MyApp.ServiceInterface; + +public class TodosServices(IAutoQueryData autoQuery) : Service +{ + static readonly PocoDataSource Todos = PocoDataSource.Create(new Todo[] + { + new () { Id = 1, Text = "Learn" }, + new () { Id = 2, Text = "Blazor", IsFinished = true }, + }, nextId: x => x.Select(e => e.Id).Max()); + + public object Get(QueryTodos query) + { + var db = Todos.ToDataSource(query, Request); + return autoQuery.Execute(query, autoQuery.CreateQuery(query, Request, db), db); + } + + public Todo Post(CreateTodo request) + { + var newTodo = new Todo { Id = Todos.NextId(), Text = request.Text }; + Todos.Add(newTodo); + return newTodo; + } + + public Todo Put(UpdateTodo request) + { + var todo = request.ConvertTo(); + Todos.TryUpdateById(todo, todo.Id); + return todo; + } + + public void Delete(DeleteTodos request) => Todos.TryDeleteByIds(request.Ids); +} diff --git a/ServiceStack/tests/Blazor/ServiceModel/Bookings.cs b/ServiceStack/tests/Blazor/ServiceModel/Bookings.cs new file mode 100644 index 00000000000..14f81f56eb5 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/Bookings.cs @@ -0,0 +1,208 @@ +// Complete declarative AutoQuery services for Bookings CRUD example: +// https://docs.servicestack.net/autoquery-crud-bookings + +using System; +using ServiceStack; +using ServiceStack.DataAnnotations; + +namespace MyApp.ServiceModel; + +[Icon(Svg = Icons.Booking)] +[Description("Booking Details")] +[Notes("Captures a Persons Name & Room Booking information")] +public class Booking : AuditBase +{ + [AutoIncrement] + public int Id { get; set; } + public string Name { get; set; } = default!; + public RoomType RoomType { get; set; } + public int RoomNumber { get; set; } + [IntlDateTime(DateStyle.Long)] + public DateTime BookingStartDate { get; set; } + [IntlRelativeTime] + public DateTime? BookingEndDate { get; set; } + [IntlNumber(Currency = NumberCurrency.USD)] + public decimal Cost { get; set; } + + [References(typeof(Coupon))] + [Ref(Model = nameof(Coupon), RefId = nameof(Coupon.Id), RefLabel = nameof(Coupon.Description))] + public string? CouponId { get; set; } + + [Reference] + public Coupon Discount { get; set; } + public string? Notes { get; set; } + public bool? Cancelled { get; set; } + + + [References(typeof(Address))] + public long? PermanentAddressId { get; set; } + + [Reference] + public Address? PermanentAddress { get; set; } + + [References(typeof(Address))] + public long? PostalAddressId { get; set; } + + [Reference] + public Address? PostalAddress { get; set; } +} + +public enum RoomType +{ + Single, + Double, + Queen, + Twin, + Suite, +} + +[Tag("bookings"), Description("Find Bookings")] +[Notes("Find out how to quickly create a C# Bookings App from Scratch")] +[Route("/bookings", "GET")] +[Route("/bookings/{Id}", "GET")] +[AutoApply(Behavior.AuditQuery)] +public class QueryBookings : QueryDb +{ + public int? Id { get; set; } +} + +// Uncomment below to enable DeletedBookings API to view deleted bookings: +// [Route("/bookings/deleted")] +// [AutoFilter(QueryTerm.Ensure, nameof(AuditBase.DeletedDate), Template = SqlTemplate.IsNotNull)] +// public class DeletedBookings : QueryDb {} + +[Tag("bookings"), Description("Create a new Booking")] +[LocodeCss(Field = "col-span-12 sm:col-span-6", Fieldset = "grid grid-cols-8 gap-2", Form = "border overflow-hidden max-w-screen-lg")] +[ExplorerCss(Field = "col-span-12 sm:col-span-6", Fieldset = "grid grid-cols-6 gap-8", Form = "border border-indigo-500 overflow-hidden max-w-screen-lg")] +[Route("/bookings", "POST")] +[ValidateHasRole(Roles.Employee)] +[AutoApply(Behavior.AuditCreate)] +public class CreateBooking : ICreateDb, IReturn +{ + [Description("Name this Booking is for"), ValidateNotEmpty] + public required string Name { get; set; } + public RoomType RoomType { get; set; } + [ValidateGreaterThan(0)] + public int RoomNumber { get; set; } + [ValidateGreaterThan(0)] + public decimal Cost { get; set; } + [Required] + public DateTime BookingStartDate { get; set; } + public DateTime? BookingEndDate { get; set; } + [Input(Type = "textarea")] + public string? Notes { get; set; } + public string? CouponId { get; set; } + public long? PermanentAddressId { get; set; } + public long? PostalAddressId { get; set; } +} + +[Tag("bookings"), Description("Update an existing Booking")] +[Notes("Find out how to quickly create a C# Bookings App from Scratch")] +[Route("/booking/{Id}", "PATCH")] +[ValidateHasRole(Roles.Employee)] +[AutoApply(Behavior.AuditModify)] +public class UpdateBooking : IPatchDb, IReturn +{ + public required int Id { get; set; } + public string? Name { get; set; } + public RoomType? RoomType { get; set; } + [ValidateGreaterThan(0)] + public int? RoomNumber { get; set; } + [ValidateGreaterThan(0)] + public decimal? Cost { get; set; } + public DateTime? BookingStartDate { get; set; } + public DateTime? BookingEndDate { get; set; } + [Input(Type = "textarea")] + public string? Notes { get; set; } + public string? CouponId { get; set; } + public bool? Cancelled { get; set; } + public long? PermanentAddressId { get; set; } + public long? PostalAddressId { get; set; } +} + +[Tag("bookings"), Description("Delete a Booking")] +[Route("/booking/{Id}", "DELETE")] +[ValidateHasRole(Roles.Manager)] +[AutoApply(Behavior.AuditSoftDelete)] +public class DeleteBooking : IDeleteDb, IReturnVoid +{ + public required int Id { get; set; } +} + + +[Description("Discount Coupons")] +[Icon(Svg = Icons.Coupon)] +public class Coupon +{ + public string Id { get; set; } = default!; + public string Description { get; set; } = default!; + public int Discount { get; set; } + public DateTime ExpiryDate { get; set; } +} + +[Tag("bookings"), Description("Find Coupons")] +[Route("/coupons", "GET")] +public class QueryCoupons : QueryDb +{ + public string? Id { get; set; } +} + +[Tag("bookings")] +[Route("/coupons", "POST")] +[ValidateHasRole(Roles.Employee)] +public class CreateCoupon : ICreateDb, IReturn +{ + [ValidateNotEmpty] + public required string Description { get; set; } + [ValidateGreaterThan(0)] + public int Discount { get; set; } + [ValidateNotNull] + public DateTime ExpiryDate { get; set; } +} + +[Tag("bookings")] +[Route("/coupons/{Id}", "PATCH")] +[ValidateHasRole(Roles.Employee)] +public class UpdateCoupon : IPatchDb, IReturn +{ + public required string Id { get; set; } + [ValidateNotEmpty] + public string? Description { get; set; } + [ValidateNotNull, ValidateGreaterThan(0)] + public int? Discount { get; set; } + [ValidateNotNull] + public DateTime? ExpiryDate { get; set; } +} + +[Tag("bookings"), Description("Delete a Coupon")] +[Route("/coupons/{Id}", "DELETE")] +[ValidateHasRole("Manager")] +public class DeleteCoupon : IDeleteDb, IReturnVoid +{ + public required string Id { get; set; } +} + + +public class Address +{ + [AutoIncrement] + public long Id { get; set; } + public string? AddressText { get; set; } +} +public class QueryAddresses : QueryDb
+{ + public long[] Ids { get; set; } +} + +public class CreateAddress + : ICreateDb
, IReturn +{ + public string? AddressText { get; set; } +} + +public class UpdateAddress + : IPatchDb
, IReturn +{ + public int Id { get; set; } + public string? AddressText { get; set; } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/ServiceModel/Hello.cs b/ServiceStack/tests/Blazor/ServiceModel/Hello.cs new file mode 100644 index 00000000000..8e495b45f8c --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/Hello.cs @@ -0,0 +1,16 @@ +using ServiceStack; +using MyApp; + +namespace MyApp.ServiceModel; + +[Route("/hello")] +[Route("/hello/{Name}")] +public class Hello : IGet, IReturn +{ + public string? Name { get; set; } +} + +public class HelloResponse +{ + public string Result { get; set; } +} diff --git a/ServiceStack/tests/Blazor/ServiceModel/Icons.cs b/ServiceStack/tests/Blazor/ServiceModel/Icons.cs new file mode 100644 index 00000000000..dca36ff354f --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/Icons.cs @@ -0,0 +1,11 @@ +// Complete declarative AutoQuery services for Bookings CRUD example: +// https://docs.servicestack.net/autoquery-crud-bookings + +namespace MyApp.ServiceModel; + +public static class Icons +{ + public const string Dashboard = ""; + public const string Booking = ""; + public const string Coupon = ""; +} diff --git a/ServiceStack/tests/Blazor/ServiceModel/Roles.cs b/ServiceStack/tests/Blazor/ServiceModel/Roles.cs new file mode 100644 index 00000000000..a4cea2a3660 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/Roles.cs @@ -0,0 +1,8 @@ +namespace MyApp.ServiceModel; + +public class Roles +{ + public const string Admin = nameof(Admin); + public const string Manager = nameof(Manager); + public const string Employee = nameof(Employee); +} diff --git a/ServiceStack/tests/Blazor/ServiceModel/Todos.cs b/ServiceStack/tests/Blazor/ServiceModel/Todos.cs new file mode 100644 index 00000000000..cfbaa6b62d2 --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/Todos.cs @@ -0,0 +1,49 @@ +using ServiceStack; +using ServiceStack.Model; + +namespace MyApp.ServiceModel; + +[Tag("todos")] +[Route("/todos", "GET")] +[ValidateApiKey] +public class QueryTodos : QueryData +{ + public int? Id { get; set; } + public List? Ids { get; set; } + public string? TextContains { get; set; } +} + +[Tag("todos")] +[Route("/todos", "POST")] +[ValidateApiKey] +public class CreateTodo : IPost, IReturn +{ + [ValidateNotEmpty] + public string Text { get; set; } +} + +[Tag("todos")] +[Route("/todos/{Id}", "PUT")] +[ValidateApiKey] +public class UpdateTodo : IPut, IReturn +{ + public long Id { get; set; } + [ValidateNotEmpty] + public string Text { get; set; } + public bool IsFinished { get; set; } +} + +[Tag("todos")] +[Route("/todos", "DELETE")] +[ValidateApiKey] +public class DeleteTodos : IDelete, IReturnVoid +{ + public List Ids { get; set; } +} + +public class Todo : IHasId +{ + public long Id { get; set; } + public string Text { get; set; } + public bool IsFinished { get; set; } +} diff --git a/ServiceStack/tests/Blazor/ServiceModel/types/README.md b/ServiceStack/tests/Blazor/ServiceModel/types/README.md new file mode 100644 index 00000000000..2ebc826728e --- /dev/null +++ b/ServiceStack/tests/Blazor/ServiceModel/types/README.md @@ -0,0 +1,2 @@ +As part of our [Physical Project Structure](https://docs.servicestack.net/physical-project-structure) convention +we recommend maintaining any shared non Request/Response DTOs in the `ServiceModel.Types` namespace. \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_pages/about.md b/ServiceStack/tests/Blazor/_pages/about.md new file mode 100644 index 00000000000..3f3c127b7dc --- /dev/null +++ b/ServiceStack/tests/Blazor/_pages/about.md @@ -0,0 +1,19 @@ +--- +title: About this Blazor App +--- + +## .NET 8 Blazor App Templates + +ServiceStack's new .NET 8 Blazor templates enhances the default ASP.NET Blazor App templates with several modern, high-productivity features, including: + + - [Tailwind CSS](https://tailwindcss.com) - Style your Blazor Apps with the modern popular utility-first CSS framework for creating beautiful, maintainable responsive UIs with DarkMode support + - [ServiceStack.Blazor Components](https://blazor-gallery.jamstacks.net) - Rapidly develop beautiful Blazor Apps integrated with Rich high-productivity UI Tailwind Components like [AutoQueryGrid](https://blazor-gallery.servicestack.net/gallery/autoquerygrid) and [AutoForms](https://blazor-gallery.servicestack.net/gallery/autoform) + - [ASP .NET Identity Auth](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/) - Use the same ASP .NET Identity Auth used in ASP.NET's .NET 8 Blazor Apps, with all Identity Pages upgraded with beautiful Tailwind CSS styling + - [Entity Framework](https://learn.microsoft.com/ef/) & [OrmLite](https://docs.servicestack.net/ormlite/) - Choose the Best ORM to build each App feature, with a unified solution that sees [OrmLite's Code-First DB Migrations](https://docs.servicestack.net/ormlite/db-migrations) run both EF and OrmLite migrations, inc. Seed Data with a single command at Development or Deployment + - [AutoQuery](https://docs.servicestack.net/autoquery/) - Rapidly developing data-driven APIs, UIs and CRUD Apps + - [Auto Admin Pages](https://www.youtube.com/watch?v=tt0ytzVVjEY) - Quickly develop your back-office CRUD Admin UIs to manage your App's Database tables at [/admin](/admin) + - [Markdown](https://docs.servicestack.net/razor-press/syntax) - Maintain SEO-friendly documentation and content-rich pages like this one with just Markdown, beautifully styled with [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin) + - [Built-in UIs](https://servicestack.net/auto-ui) - Use ServiceStack's Auto UIs to [Explore your APIs](https://docs.servicestack.net/api-explorer) at [/ui](/ui/) + or Query your [App's Database Tables](https://docs.servicestack.net/admin-ui-database) at [/admin-ui/database](/admin-ui/database) + - [Universal API Components](https://youtu.be/66DgLHExC9E) - Effortlessly create reusable, maximally performant universal Blazor API Components that works in Blazor Server and Web Assembly Interactivity modes + - [Built-in Docker Deployments](/deploy) - Use the built-in GitHub Actions to effortlessly deploy .NET 8 containerized Blazor Apps with Docker and GitHub Registry via SSH to any Linux Server \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_pages/deploy.md b/ServiceStack/tests/Blazor/_pages/deploy.md new file mode 100644 index 00000000000..447bb612072 --- /dev/null +++ b/ServiceStack/tests/Blazor/_pages/deploy.md @@ -0,0 +1,300 @@ +--- +title: GitHub Action Deployments +--- + +In today's DevOps ecosystem, [GitHub Actions](https://github.com/features/actions) stand out as an invaluable asset for +automating CI/CD workflows directly within your GitHub repository. The introduction of .NET 8 takes this a step further, +offering a streamlined approach to generating Docker images through the `DefaultContainer` +setting in your `.csproj`. This ensures consistent application packaging, making it deployment-ready by just using `dotnet publish`. + +ServiceStack's project templates bring additional flexibility, by utilizing foundational tools like +[Docker](https://www.docker.com) for containerization and [SSH](https://en.wikipedia.org/wiki/Secure_Shell) for secure deployments, +it's able to deploy your Dockerized .NET applications to any Linux server, whether self-hosted or on any cloud provider. + +#### Live Demos use their GitHub Actions to deploy themselves + +Each template's Live demo are themselves utilizing their included GitHub Actions to deploy itself to a Linux server running +on a **€13.60 /month** shared 8BG RAM [Hetzner Cloud VM](https://www.hetzner.com/cloud) that's currently running 50+ Docker Containers. + +This guide aims to walk you through the hosting setup and the GitHub Actions release process as introduced in the +ServiceStack's latest .NET 8 project templates. + +## Deployment Files + +Deployment files that are included in project templates to facilitate GitHub Actions deployments: + +#### .deploy/ +- [nginx-proxy-compose.yml](https://github.com/NetCoreTemplates/blazor/blob/master/.deploy/nginx-proxy-compose.yml) - Manage nginx reverse proxy and Lets Encrypt companion container (one-time setup per server) +- [docker-compose.yml](https://github.com/NetCoreTemplates/blazor/blob/main/.deploy/docker-compose.yml) - Manage .NET App Docker Container + +#### .github/workflows/ +- [build.yml](https://github.com/NetCoreTemplates/blazor/blob/master/.github/workflows/build.yml) - Build .NET Project and Run Tests +- [release.yml](https://github.com/NetCoreTemplates/blazor/blob/master/.github/workflows/release.yml) - Build container, Push to GitHub Packages Registry, SSH deploy to Linux server, Run DB Migrations and start new Docker Container if successful otherwise revert Migration + +## Prerequisites + +Before your Linux server can accept GitHub Actions deployments, we need to set up a few things: + +### Setup Deployment Server + +#### 1. Install Docker and Docker-Compose + +Follow [Docker's installation instructions](https://docs.docker.com/engine/install/ubuntu/) +to correctly install the latest version of Docker. + +#### 2. Configure SSH for GitHub Actions + +Generate a dedicated SSH key pair to be used by GitHub Actions: + +:::sh +ssh-keygen -t rsa -b 4096 -f ~/.ssh/github_actions +::: + +Add the **public key** to your server's SSH **authorized_keys**: + +:::sh +cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys +::: + +Then, add the **private key** to your repo's `DEPLOY_KEY` GitHub Action Secret which GitHub Actions will use to +securely SSH into the server. + +#### 3. Set Up nginx-reverse-proxy + +You should have a `docker-compose` file similar to the `nginx-proxy-compose.yml` in your repository. Upload this file to your server: + +:::sh +scp nginx-proxy-compose.yml user@your_server:~/ +::: + +To bring up the nginx reverse proxy and its companion container for handling TLS certificates, run: + +:::sh +docker compose -f ~/nginx-proxy-compose.yml up -d +::: + +This will start an nginx reverse proxy along with a companion LetsEncrypt container that will automatically watch for +additional Docker containers on the same network and initialize them with valid TLS certificates. + +### Ready to host containerized .NET Apps + +Your Linux server is now ready to accept multiple .NET App deployments from GitHub Actions. The guide below walks through +the process of setting up your GitHub repository to deploy new ServiceStack .NET Apps to your Linux server. + +## Step-by-Step Guide + +### 1. Create Your ServiceStack Application + +Start by creating your ServiceStack application, either from [ServiceStack's Start Page](https://servicestack.net/start) +or by using the [x dotnet tool](https://docs.servicestack.net/dotnet-tool): + +:::sh +x new blazor ProjectName +::: + +Replace `ProjectName` with your desired project name to generate a new ServiceStack application pre-configured +with the necessary Docker compose files and GitHub Action workflows as above. + +### 2. Configure DNS for Your Application + +You need a domain to point to your Linux server. Create an A Record in your DNS settings that points to the IP address +of your Linux server: + +- **Subdomain**: `app.example.org` +- **Record Type**: A +- **Value/Address**: IP address of your Linux server + +This ensures that any requests to `app.example.org` are directed to your server. + +### 3. Setting Up GitHub Secrets + +Navigate to your GitHub repository's settings, find the "Secrets and variables" section, and add the following secrets: + +- `DEPLOY_HOST`: IP address or hostname of your Linux server +- `DEPLOY_USERNAME`: SSH Username to use for deployments +- `DEPLOY_KEY`: Private key generated for GitHub Actions to SSH into your server +- `LETSENCRYPT_EMAIL`: Your email address for Let's Encrypt notifications + +#### Using GitHub CLI for Secret Management + +You can use the [GitHub CLI](https://cli.github.com/manual/gh_secret_set) for a quicker setup of these GitHub Action Secrets, e.g: + +```bash +gh secret set DEPLOY_HOST --body="linux-server-host" +gh secret set DEPLOY_USERNAME --body="linux-server-username" +gh secret set DEPLOY_KEY --bodyFile="path/to/ssh-private-key" +gh secret set LETSENCRYPT_EMAIL --body="your-email@example.org" +``` + +These secrets will populate environment variables within your GitHub Actions workflow and other configuration files, +enabling secure and automated deployment of your ServiceStack applications. + +### 4. Push to Main Branch to Trigger Deployment + +With everything set up, pushing code to the main branch of your repository will trigger the GitHub Action workflow, initiating the deployment process: + +```bash +git add . +git commit -m "Initial commit" +git push origin main +``` + +### 5. Verifying the Deployment + +After the GitHub Actions workflow completes, you can verify the deployment by: + +- Checking the workflow's logs in your GitHub repository to ensure it completed successfully +- Navigating to your application's URL (e.g., `https://app.example.org`) in a web browser. You should see your ServiceStack application up and running with a secure HTTPS connection + +## Features + +### DB Migrations + +The GitHub Actions workflow includes a step to run database migrations on the remote server in the **Run remote db migrations** step +which automatically runs the `migrate` AppTask in the `app-migration` companion Docker container on the Linux Host Server +to validate migration was successful before completing deployment of the new App. A failed migration will cause deployment to fail +and the previous App version to continue to run. + +### Patch appsettings.json with production secrets + +One way to maintain sensitive information like API keys and connection strings for your Production App outside of its +source code GitHub repository is to patch the `appsettings.json` file with a [JSON Patch](https://jsonpatch.com) that's +stored in your repo's `APPSETTINGS_PATCH` GitHub Action Secret which will be applied with the deployed App's `appsettings.json` file. + +For example this JSON Patch below will replace values and objects in your App's **appsettings.json**: + +```json +[ + { "op":"add", "path":"/oauth.facebook.AppSecret", "value":"xxxx" }, + { "op":"add", "path":"/oauth.microsoft.AppSecret", "value":"xxxx" }, + { "op":"add", "path":"/smtp", "value":{ + "UserName": "xxxx", + "Password": "xxxx", + "Host": "smtp-server.example.org", + "Port": 587, + "From": "noreply@example.org", + "FromName": "No Reply" + } + } +] +``` + +You can test your JSON Patch by saving it to `appsettings.json.patch` and applying it with the +[patch feature](https://docs.servicestack.net/dotnet-tool#patch-json-files) of the `x` dotnet tool: + +:::sh +x patch appsettings.json.patch +::: + +## Anatomy of GitHub Actions Workflow + +GitHub Actions workflows are defined in YAML files, and they provide a powerful way to automate your development process. +This guide will take you through the key sections of the workflow to give you a comprehensive understanding of how it functions. + +## Permissions + +In this workflow, two permissions are specified: + +- `packages: write`: This allows the workflow to upload Docker images to GitHub Packages. +- `contents: write`: This is required to access the repository content. + +Specifying permissions ensures that the GitHub Actions runner has just enough access to perform the tasks in the workflow. + +## Jobs + +This workflow consists of two jobs: `push_to_registry` and `deploy_via_ssh`. + +### push_to_registry + +This job runs on an Ubuntu 22.04 runner and is responsible for pushing the Docker image to the GitHub Container Registry. It proceeds only if the previous workflow did not fail. The job includes the following steps: + +1. **Checkout**: Retrieves the latest or specific tag of the repository's code. +2. **Env variable assignment**: Assigns necessary environment variables for subsequent steps. +3. **Login to GitHub Container Registry**: Authenticates to the GitHub Container Registry. +4. **Setup .NET Core**: Prepares the environment for .NET 8. +5. **Build and push Docker image**: Creates and uploads the Docker image to GitHub Container Registry (ghcr.io). + +### deploy_via_ssh + +This job also runs on an Ubuntu 22.04 runner and depends on the successful completion of the `push_to_registry` job. Its role is to deploy the application via SSH. The steps involved are: + +1. **Checkout**: Retrieves the latest or specific tag of the repository's code. +2. **Repository name fix and env**: Sets up necessary environment variables. +3. **Create .env file**: Generates a .env file required for deployment. +4. **Copy files to target server via scp**: Securely copies files to the remote server. +5. **Run remote db migrations**: Executes database migrations on the remote server. +6. **Remote docker-compose up via ssh**: Deploys the Docker image with the application. + +## Triggers (on) + +The workflow is designed to be triggered by: + +1. **New GitHub Release**: Activates when a new release is published. +2. **Successful Build action**: Runs whenever the specified Build action completes successfully on the main or master branches. +3. **Manual trigger**: Allows for rollback to a specific release or redeployment of the latest release, with an input for specifying the version tag. + +Understanding these sections will help you navigate and modify the workflow as per your needs, ensuring a smooth and automated deployment process. + +## Deployment Server Setup Expanded + +### Ubuntu as the Reference Point + +Though our example leverages Ubuntu, it's important to emphasize that the primary requirements for this deployment architecture are a Linux operating system, Docker, and SSH. Many popular Linux distributions like CentOS, Fedora, or Debian will work just as efficiently, provided they support Docker and SSH. + +### The Crucial Role of SSH in GitHub Actions + +**SSH** (Secure SHell) is not just a protocol to remotely access your server's terminal. In the context of GitHub Actions: + +- SSH offers a **secure communication channel** between GitHub Actions and your Linux server. +- Enables GitHub to **execute commands directly** on your server. +- Provides a mechanism to **transfer files** (like Docker-compose configurations or environment files) from the GitHub repository to the server. + +By generating a dedicated SSH key pair specifically for GitHub Actions (as detailed in the previous documentation), we ensure a secure and isolated access mechanism. Only the entities possessing the private key (in this case, only GitHub Actions) can initiate an authenticated connection. + +### Docker & Docker-Compose: Powering the Architecture + +**Docker** encapsulates your ServiceStack application into containers, ensuring consistency across different environments. Some of its advantages include: + +- **Isolation**: Your application runs in a consistent environment, irrespective of where Docker runs. +- **Scalability**: Easily replicate containers to handle more requests. +- **Version Control for Environments**: Create, maintain, and switch between different container images. + +**Docker-Compose** extends Docker's benefits by orchestrating the deployment of multi-container applications: + +- **Ease of Configuration**: Describe your application's entire stack, including the application, database, cache, etc., in a single YAML file. +- **Consistency Across Multiple Containers**: Ensures that containers are spun up in the right order and with the correct configurations. +- **Simplifies Commands**: Instead of a long string of Docker CLI commands, a single `docker-compose up` brings your whole stack online. + +### NGINX Reverse Proxy: The Silent Workhorse + +Using an **nginx reverse proxy** in this deployment design offers several powerful advantages: + +- **Load Balancing**: Distributes incoming requests across multiple ServiceStack applications, ensuring optimal resource utilization. +- **TLS Management**: Together with its companion container, nginx reverse proxy automates the process of obtaining and renewing TLS certificates. This ensures your applications are always securely accessible over HTTPS. +- **Routing**: Directs incoming traffic to the correct application based on the domain or subdomain. +- **Performance**: Caches content to reduce load times and reduce the load on your ServiceStack applications. + +With an nginx reverse proxy, you can host multiple ServiceStack (or non-ServiceStack) applications on a single server while providing each with its domain or subdomain. + + +## Additional Resources + +### ServiceStack + +- **[Official Documentation](https://docs.servicestack.net/)**: Comprehensive guides and best practices for ServiceStack. +- **[ServiceStack Website](https://servicestack.net/)**: Explore features, templates, and client libraries. + +### Docker & Docker-Compose + +- **[Docker Documentation](https://docs.docker.com/)**: Core concepts, CLI usage, and practical applications. +- **[Docker-Compose Documentation](https://docs.docker.com/compose/)**: Define and manage multi-container applications. + +### GitHub Actions + +- **[GitHub Actions Documentation](https://docs.github.com/en/actions)**: Creating workflows, managing secrets, and automation tips. +- **[Starter Workflows](https://github.com/actions/starter-workflows)**: Templates for various languages and tools. + +### SSH & Security + +- **[SSH Key Management](https://www.ssh.com/academy/ssh/keygen)**: Guidelines on generating and managing SSH keys. +- **[GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)**: Securely store and use sensitive information. \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_pages/privacy.md b/ServiceStack/tests/Blazor/_pages/privacy.md new file mode 100644 index 00000000000..8c091217f04 --- /dev/null +++ b/ServiceStack/tests/Blazor/_pages/privacy.md @@ -0,0 +1,69 @@ +--- +title: Privacy Policy +--- + +[Your business name] is committed to providing quality services to you and this policy outlines our ongoing obligations to you in respect of how we manage your Personal Information. + +We have adopted the Australian Privacy Principles (APPs) contained in the Privacy Act 1988 (Cth) (the Privacy Act). The NPPs govern the way in which we collect, use, disclose, store, secure and dispose of your Personal Information. + +A copy of the Australian Privacy Principles may be obtained from the website of The Office of the Australian Information Commissioner at https://www.oaic.gov.au/. + +What is Personal Information and why do we collect it? +Personal Information is information or an opinion that identifies an individual. Examples of Personal Information we collect includes names, addresses, email addresses, phone and facsimile numbers. + +This Personal Information is obtained in many ways including [interviews, correspondence, by telephone and facsimile, by email, via our website www.yourbusinessname.com.au, from your website, from media and publications, from other publicly available sources, from cookies- delete all that aren’t applicable] and from third parties. We don’t guarantee website links or policy of authorised third parties. + +We collect your Personal Information for the primary purpose of providing our services to you, providing information to our clients and marketing. We may also use your Personal Information for secondary purposes closely related to the primary purpose, in circumstances where you would reasonably expect such use or disclosure. You may unsubscribe from our mailing/marketing lists at any time by contacting us in writing. + +When we collect Personal Information we will, where appropriate and where possible, explain to you why we are collecting the information and how we plan to use it. + +Sensitive Information +Sensitive information is defined in the Privacy Act to include information or opinion about such things as an individual's racial or ethnic origin, political opinions, membership of a political association, religious or philosophical beliefs, membership of a trade union or other professional body, criminal record or health information. + +Sensitive information will be used by us only: + +- For the primary purpose for which it was obtained + +- For a secondary purpose that is directly related to the primary purpose + +- With your consent; or where required or authorised by law. + +Third Parties +Where reasonable and practicable to do so, we will collect your Personal Information only from you. However, in some circumstances we may be provided with information by third parties. In such a case we will take reasonable steps to ensure that you are made aware of the information provided to us by the third party. + +Disclosure of Personal Information +Your Personal Information may be disclosed in a number of circumstances including the following: + +- Third parties where you consent to the use or disclosure; and + +- Where required or authorised by law. + +Security of Personal Information +Your Personal Information is stored in a manner that reasonably protects it from misuse and loss and from unauthorized access, modification or disclosure. + +When your Personal Information is no longer needed for the purpose for which it was obtained, we will take reasonable steps to destroy or permanently de-identify your Personal Information. However, most of the Personal Information is or will be stored in client files which will be kept by us for a minimum of 7 years. + +Access to your Personal Information +You may access the Personal Information we hold about you and to update and/or correct it, subject to certain exceptions. If you wish to access your Personal Information, please contact us in writing. + +[Your business name] will not charge any fee for your access request, but may charge an administrative fee for providing a copy of your Personal Information. + +In order to protect your Personal Information we may require identification from you before releasing the requested information. + +Maintaining the Quality of your Personal Information +It is an important to us that your Personal Information is up to date. We will take reasonable steps to make sure that your Personal Information is accurate, complete and up-to-date. If you find that the information we have is not up to date or is inaccurate, please advise us as soon as practicable so we can update our records and ensure we can continue to provide quality services to you. + +Policy Updates +This Policy may change from time to time and is available on our website. + +Privacy Policy Complaints and Enquiries +If you have any queries or complaints about our Privacy Policy please contact us at: + + +[Your business address] + +[Your business email address] + +[Your business phone number] + + \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_videos/blazor/admin.md b/ServiceStack/tests/Blazor/_videos/blazor/admin.md new file mode 100644 index 00000000000..0444cfaafd4 --- /dev/null +++ b/ServiceStack/tests/Blazor/_videos/blazor/admin.md @@ -0,0 +1,13 @@ +--- +title: Create Beautiful UX optimized Blazor Apps with Auto Admin pages +url: https://youtu.be/tt0ytzVVjEY +tags: blazor,admin +date: 12-12-2022 +order: 2 +--- + +We walk through the process of creating admin screens using ServiceStack Blazor Components by looking at our Blazor Diffusion +App as an example which generates AI artworks using Stable Diffusion and curates a gallery of amazing visuals. + +With the ServiceStack Blazor Components, we'll show you how to quickly create a powerful and intuitive admin interface for +managing the application data, with minimal coding required. diff --git a/ServiceStack/tests/Blazor/_videos/blazor/components.md b/ServiceStack/tests/Blazor/_videos/blazor/components.md new file mode 100644 index 00000000000..129cb196221 --- /dev/null +++ b/ServiceStack/tests/Blazor/_videos/blazor/components.md @@ -0,0 +1,12 @@ +--- +title: Rapidly Develop Beautiful Apps with Blazor Tailwind Components +url: https://youtu.be/iKpQI2233nY +tags: blazor,components,autoquery +date: 11-10-2022 +order: 1 +--- + +The ServiceStack Blazor components for Blazor WASM and Server Rendered Apps are a powerful set of tools that enable +developers to quickly and easily create interactive data grids from their AutoQuery services using minimal code. + +These components can be styled with Tailwind CSS, enabling creating professional-looking custom applications. \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_videos/blazor/darkmode.md b/ServiceStack/tests/Blazor/_videos/blazor/darkmode.md new file mode 100644 index 00000000000..2fd2721494a --- /dev/null +++ b/ServiceStack/tests/Blazor/_videos/blazor/darkmode.md @@ -0,0 +1,14 @@ +--- +title: Beautiful Blazor Tailwind Components with Darkmode +url: https://youtu.be/8nwpC_B4AC4 +tags: darkmode,blazor,components +date: 07-12-2022 +order: 5 +--- + +We walkthrough the ServiceStack Blazor Components support for Darkmode. +Our Blazor Components provide a suite of powerful, pre-built UI components that are easy to use, customizable, +and can be used to build high-performing, responsive web applications. + +The components support both Blazor Server and Blazor WASM, and are designed to work seamlessly with +ServiceStack's features, providing a simplified and integrated developer experience. \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/_videos/blazor/tailwind.md b/ServiceStack/tests/Blazor/_videos/blazor/tailwind.md new file mode 100644 index 00000000000..d2f14ff33fe --- /dev/null +++ b/ServiceStack/tests/Blazor/_videos/blazor/tailwind.md @@ -0,0 +1,14 @@ +--- +title: Creating Beautiful Blazor Apps with Tailwind +url: https://youtu.be/3gD_MMcYI-4 +tags: blazor,autoquery +date: 25-07-2022 +order: 4 +--- + +We tour the Blazor Web Assembly template that utilizes Tailwind CSS, +and show you how to set up hot reload for both during development. + +Tailwind CSS is a popular utility-first CSS framework that provides a +comprehensive set of pre-defined CSS styles, enabling developers to +create modern and responsive designs with ease. diff --git a/ServiceStack/tests/Blazor/_videos/blazor/universal.md b/ServiceStack/tests/Blazor/_videos/blazor/universal.md new file mode 100644 index 00000000000..ee7aaa5ac2a --- /dev/null +++ b/ServiceStack/tests/Blazor/_videos/blazor/universal.md @@ -0,0 +1,15 @@ +--- +title: Universal Blazor API Components for Blazor Server and WASM +url: https://youtu.be/66DgLHExC9E +tags: blazor,server,wasm,components +date: 04-01-2023 +order: 3 +--- + +We walk through the process of creating universal Blazor API components for Blazor Server +and Blazor WASM using the ServiceStack.Blazor library. + +We'll show how Developers can create UI components that can be shared between their Blazor +applications without worrying about the hosting model used. This allows developers to streamline +their development process and reduce the amount of code they need to write, making it easier to build +and maintain complex Apps. diff --git a/ServiceStack/tests/Blazor/appsettings.Development.json b/ServiceStack/tests/Blazor/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/ServiceStack/tests/Blazor/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ServiceStack/tests/Blazor/appsettings.Production.json b/ServiceStack/tests/Blazor/appsettings.Production.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/ServiceStack/tests/Blazor/appsettings.Production.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ServiceStack/tests/Blazor/appsettings.json b/ServiceStack/tests/Blazor/appsettings.json new file mode 100644 index 00000000000..6eb03244e86 --- /dev/null +++ b/ServiceStack/tests/Blazor/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AppConfig": { + "LocalBaseUrl": "https://localhost:5001", + "PublicBaseUrl": "https://blazor.web-templates.io" + } +} diff --git a/ServiceStack/tests/Blazor/package.json b/ServiceStack/tests/Blazor/package.json new file mode 100644 index 00000000000..5e291402130 --- /dev/null +++ b/ServiceStack/tests/Blazor/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "postinstall": "node postinstall.js && npm run migrate", + "dtos": "npx get-dtos mjs", + "dev": "dotnet watch", + "ui:dev": "tailwindcss -i ./tailwind.input.css -o ./wwwroot/css/app.css --watch", + "ui:build": "tailwindcss -i ./tailwind.input.css -o ./wwwroot/css/app.css --minify", + "build": "npm run ui:build", + "migrate": "dotnet run --AppTasks=migrate", + "revert:last": "dotnet run --AppTasks=migrate.revert:last", + "revert:all": "dotnet run --AppTasks=migrate.revert:all", + "rerun:last": "npm run revert:last && npm run migrate" + } +} diff --git a/ServiceStack/tests/Blazor/postinstall.js b/ServiceStack/tests/Blazor/postinstall.js new file mode 100644 index 00000000000..781eef15deb --- /dev/null +++ b/ServiceStack/tests/Blazor/postinstall.js @@ -0,0 +1,231 @@ +// Usage: npm install + +const path = require('path') +const fs = require('fs') +const { pipeline } = require('stream') +const { promisify } = require('util') +const { execSync } = require('child_process') +const pipe = promisify(pipeline) + +const writeTo = './wwwroot' +const files = { + tailwind: { + 'ServiceStack.Blazor.html' : 'https://raw.githubusercontent.com/ServiceStack/ServiceStack/refs/heads/main/ServiceStack.Blazor/src/ServiceStack.Blazor/dist/tailwind.html' + } +} + +;(async () => { + const requests = [] + Object.keys(files).forEach(dir => { + const dirFiles = files[dir] + Object.keys(dirFiles).forEach(name => { + let url = dirFiles[name] + if (url.startsWith('/')) + url = defaultPrefix + url + const toFile = path.join(writeTo, dir, name) + requests.push(fetchDownload(url, toFile, 5)) + }) + }) + + await Promise.all(requests) + await downloadTailwindBinary() +})() + +async function fetchDownload(url, toFile, retries) { + const toDir = path.dirname(toFile) + fs.mkdirSync(toDir, { recursive: true }) + for (let i=retries; i>=0; --i) { + try { + let r = await fetch(url) + if (!r.ok) { + throw new Error(`${r.status} ${r.statusText}`); + } + let txt = await r.text() + console.log(`writing ${url} to ${toFile}`) + await fs.writeFileSync(toFile, txt) + return + } catch (e) { + console.log(`get ${url} failed: ${e}${i > 0 ? `, ${i} retries remaining...` : ''}`) + } + } +} + +async function downloadTailwindBinary() { + const platform = process.platform // e.g., 'darwin', 'linux', 'win32' + const arch = process.arch // e.g., 'arm64', 'x64' + + // Check if tailwindcss is already in PATH + try { + const command = platform === 'win32' ? 'where tailwindcss' : 'which tailwindcss' + const result = execSync(command, { stdio: 'pipe' }) + if (result) { + // Check version of tailwindcss by looking for 'tailwindcss v4' in `taildwindcss --help` + const helpResult = execSync('tailwindcss --help', { stdio: 'pipe' }) + const helpOutput = helpResult.toString() + if (helpOutput.includes('tailwindcss v1') || helpOutput.includes('tailwindcss v2') || helpOutput.includes('tailwindcss v3')) { + console.log('old version of tailwindcss detected, please uninstall and rerun this script.') + } else { + console.log('tailwindcss is already installed.') + } + return + } + } catch (e) { + // Command failed, tailwindcss not in PATH + } + + // if file already exists, exit + const tailwindcssPath = path.join(process.cwd(), 'tailwindcss') + if (fs.existsSync(tailwindcssPath)) { + console.log(`${tailwindcssPath} already exists, skipping download.`) + return + } + + console.log() + function getBinaryFileName() { + // Determine the correct binary file name based on the current OS and architecture + if (platform === 'darwin') { // macOS + if (arch === 'arm64') { + return 'tailwindcss-macos-arm64' + } else if (arch === 'x64') { + return 'tailwindcss-macos-x64' + } + } else if (platform === 'linux') { // Linux + if (arch === 'arm64') { + return 'tailwindcss-linux-arm64' + } else if (arch === 'x64') { + return 'tailwindcss-linux-x64' + } + } else if (platform === 'win32') { // Windows + if (arch === 'arm64') { + return 'arm64-windows' + } else if (arch === 'x64') { + return 'tailwindcss-windows-x64.exe' + } + } + } + + let binaryFileName = getBinaryFileName() + + // If no matching binary is found, exit with an error + if (!binaryFileName) { + console.error(`Error: Unsupported platform/architecture combination: ${platform}/${arch}`) + console.error(`Please ensure your system is one of the following:`) + console.error(` macOS (arm64, x64)`) + console.error(` Linux (arm64, x64)`) + console.error(` Windows (arm64, x64)`) + process.exit(1) + } + + // Base URL for Tailwind CSS latest release downloads + const downloadTailwindBaseUrl = `https://github.com/tailwindlabs/tailwindcss/releases/latest/download/` + const downloadUrl = `${downloadTailwindBaseUrl}${binaryFileName}` + // Set the output file name. On Windows, it should have a .exe extension. + const outputFileName = (platform === 'win32' || platform === 'cygwin' || platform === 'msys') ? 'tailwindcss.exe' : 'tailwindcss' + const outputPath = path.join(process.cwd(), outputFileName) + + console.log(`Attempting to download the latest Tailwind CSS binary for ${platform}/${arch}...`) + console.log(`Downloading ${downloadUrl}...`) + + try { + const response = await fetch(downloadUrl) + + // Check if the response status is not OK (e.g., 404, 500). + // Fetch automatically handles redirects (3xx status codes). + if (!response.ok) { + console.error(`Failed to download: HTTP Status Code ${response.status} - ${response.statusText}`) + return + } + + // Ensure there's a readable stream body + if (!response.body) { + console.error('No response body received from the download URL.') + return + } + + const fileStream = fs.createWriteStream(outputPath) + // Pipe the readable stream from the fetch response body directly to the file stream + await pipe(response.body, fileStream) + + // Set executable permissions for non-Windows platforms + if (platform !== 'win32' && platform !== 'cygwin' && platform !== 'msys') { + console.log(`Setting executable permissions (+x) on ${outputPath}...`) + // '755' means: owner can read, write, execute; group and others can read and execute. + fs.chmodSync(outputPath, '755') + // console.log('Permissions set successfully.') + + const tryFolders = [ + `${process.env.HOME}/.local/bin`, + `${process.env.HOME}/.npm-global/bin`, + '/usr/local/bin', + '/usr/bin', + '/usr/sbin' + ] + + // Move the binary to a common location in PATH + for (const folder of tryFolders) { + if (!fs.existsSync(folder)) { + // console.log(`Folder ${folder} does not exist, skipping...`); + continue + } + const targetPath = path.join(folder, outputFileName) + if (fs.accessSync(folder, fs.constants.W_OK)) { + try { + fs.renameSync(outputPath, targetPath) + console.log(`Saved to ${targetPath}`) + break + } + catch (err) { + console.error(`Failed to move ${outputPath} to ${targetPath}: ${err.message}`) + } + } + + try { + // try using sudo with process exec + execSync(`sudo mv ${outputPath} ${targetPath}`) + console.log(`Saved to ${targetPath}`) + break + } + catch (err) { + console.log(`Manually move tailwindcss to ${targetPath} by running:`) + console.log(`sudo mv ${outputPath} ${targetPath}`) + break + } + } + } else if (platform === 'win32') { + let moved = false + // Move the binary to a common location in PATH for .NET Devs + const tryFolders = [ + `${process.env.APPDATA}/npm`, + `${process.env.USERPROFILE}/.dotnet/tools`, + ] + for (const folder of tryFolders) { + if (!fs.existsSync(folder)) { + continue + } + const targetPath = path.join(folder, outputFileName) + try { + fs.renameSync(outputPath, targetPath) + console.log(`Saved to ${targetPath}`) + moved = true + break + } + catch (err) { + } + } + if (!moved) { + console.log() + console.log(`Saved to ${outputPath}`) + console.log(`Tip: Make ${outputFileName} globally accessible by moving it to a folder in your PATH`) + } + } + + console.log() + console.log(`You can now run it from your terminal using:`) + console.log(outputFileName === 'tailwindcss.exe' ? `${outputFileName} --help` : `${outputFileName} --help`) + + } catch (error) { + console.error(`\nError during download or permission setting:`) + console.error(error.message) + process.exit(1) + } +} diff --git a/ServiceStack/tests/Blazor/tailwind.config.js b/ServiceStack/tests/Blazor/tailwind.config.js new file mode 100644 index 00000000000..c64abb9ca5c --- /dev/null +++ b/ServiceStack/tests/Blazor/tailwind.config.js @@ -0,0 +1,12 @@ +export default { + theme: { + extend: { + colors: { + 'accent-1': '#FAFAFA', + 'accent-2': '#EAEAEA', + danger: 'rgb(153 27 27)', + success: 'rgb(22 101 52)', + }, + }, + } +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/tailwind.input.css b/ServiceStack/tests/Blazor/tailwind.input.css new file mode 100644 index 00000000000..26ca1ffee15 --- /dev/null +++ b/ServiceStack/tests/Blazor/tailwind.input.css @@ -0,0 +1,163 @@ +@import "tailwindcss"; +@config "./tailwind.config.js"; + +@custom-variant dark (&:where(.dark, .dark *)); + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + } + :root.dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } + + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } +} + +@theme { + --default-ring-color: hsl(var(--ring)); +} + +@layer base { + /*custom*/ + [data-collapsed] [data-collapse=hidden] { + display: none + } + [data-collapse-off=opacity-100] { + opacity: 1 + } + [data-collapsed] [data-collapse=opacity-0] { + opacity: 0 + } + [data-collapse-off=-translate-x-full] { + transform: translateX(0) + } + [data-collapsed] [data-collapse=-translate-x-full] { + transform: translateX(-100%) + } + @media (min-width: 640px) { + .youtube { + width: 761px; + height: 428px; + } + } + + /*vue*/ + [v-cloak] {display:none} + + /* override element defaults */ + b, strong { font-weight:600; } + ::-webkit-scrollbar{width:8px;height:8px} + ::-webkit-scrollbar-thumb{background-color:#ccc} + [role=dialog].z-10 { + z-index: 60; + } + em { + color: #3b82f6; + font-weight: 400; + background-color: #eff6ff; + border-radius: 0.25rem; + padding: 0.125em 0.5rem; + margin-left: 0.125em; + margin-right: 0.125em; + font-style: normal; + } + + /* @tailwindcss/forms css */ + [type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='week'],[type='search'],[type='tel'],[type='time'],[type='color'],[multiple],textarea,select + {-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-width:1px;padding:0.5rem 0.75rem;font-size:1rem} + [type='text']:focus,[type='email']:focus,[type='url']:focus,[type='password']:focus,[type='number']:focus,[type='date']:focus,[type='datetime-local']:focus,[type='month']:focus,[type='week']:focus,[type='search']:focus,[type='tel']:focus,[type='time']:focus,[type='color']:focus,[multiple]:focus,textarea:focus,select:focus{ + outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb; + --tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);border-color:#2563eb;} + input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1} + input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1} + input::placeholder,textarea::placeholder{color:#6b7280;opacity:1} + select{ + background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position:right 0.5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact} + [multiple]{ + background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:0.75rem;-webkit-print-color-adjust:unset;color-adjust:unset;} + [type='checkbox'],[type='radio']{ + -webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block; + vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none; + flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-width:1px} + [type='radio']{border-radius:100%} + [type='checkbox']:focus,[type='radio']:focus{ + outline:2px solid transparent;outline-offset:2px; + --tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb; + --tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)} + [type='checkbox']:checked,[type='radio']:checked{ + border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat} + [type='checkbox']:checked{ + background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")} + [type='radio']:checked{ + background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")} + [type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus{ + border-color:transparent;background-color:currentColor} + [type='checkbox']:indeterminate{ + background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat} + [type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus{border-color:transparent;background-color:currentColor} + [type='file']{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit} + [type='file']:focus{outline:1px auto -webkit-focus-ring-color;} + [type='color']{height:2.4rem;padding:2px 3px} + [type='range']{height:2.4rem} + + @media (min-width: 640px) { + [type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='week'],[type='search'],[type='tel'],[type='time'],[type='color'],[multiple],textarea,select { + font-size: .875rem; + line-height: 1.25rem; + } + } + + /* dark mode autocomplete fields */ + .dark input:-webkit-autofill, + .dark input:-webkit-autofill:hover, + .dark input:-webkit-autofill:focus, + .dark input:-webkit-autofill:active { + transition: background-color 5000s ease-in-out 0s; + -webkit-text-fill-color: #ffffff; + } + .dark input[data-autocompleted] { + background-color: transparent !important; + } + + /* @tailwindcss/aspect css */ + .aspect-h-9 { + --tw-aspect-h: 9; + } + .aspect-w-16 { + position: relative; + padding-bottom: calc(var(--tw-aspect-h) / var(--tw-aspect-w) * 100%); + --tw-aspect-w: 16; + } + .aspect-w-16 > * { + position: absolute; + height: 100%; + width: 100%; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +} diff --git a/ServiceStack/tests/Blazor/wwwroot/app.css b/ServiceStack/tests/Blazor/wwwroot/app.css new file mode 100644 index 00000000000..2bab5fbcebe --- /dev/null +++ b/ServiceStack/tests/Blazor/wwwroot/app.css @@ -0,0 +1,47 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e51040; +} + +.validation-message { + color: #e51040; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } diff --git a/ServiceStack/tests/Blazor/wwwroot/css/app.css b/ServiceStack/tests/Blazor/wwwroot/css/app.css new file mode 100644 index 00000000000..d851dfe6e03 --- /dev/null +++ b/ServiceStack/tests/Blazor/wwwroot/css/app.css @@ -0,0 +1,3621 @@ +/*! tailwindcss v4.1.15 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; + --color-red-50: oklch(97.1% 0.013 17.38); + --color-red-100: oklch(93.6% 0.032 17.717); + --color-red-200: oklch(88.5% 0.062 18.334); + --color-red-300: oklch(80.8% 0.114 19.571); + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-500: oklch(63.7% 0.237 25.331); + --color-red-600: oklch(57.7% 0.245 27.325); + --color-red-700: oklch(50.5% 0.213 27.518); + --color-red-800: oklch(44.4% 0.177 26.899); + --color-red-900: oklch(39.6% 0.141 25.723); + --color-yellow-50: oklch(98.7% 0.026 102.212); + --color-yellow-100: oklch(97.3% 0.071 103.193); + --color-yellow-200: oklch(94.5% 0.129 101.54); + --color-yellow-400: oklch(85.2% 0.199 91.936); + --color-yellow-500: oklch(79.5% 0.184 86.047); + --color-yellow-600: oklch(68.1% 0.162 75.834); + --color-yellow-700: oklch(55.4% 0.135 66.442); + --color-yellow-800: oklch(47.6% 0.114 61.907); + --color-green-50: oklch(98.2% 0.018 155.826); + --color-green-100: oklch(96.2% 0.044 156.743); + --color-green-200: oklch(92.5% 0.084 155.995); + --color-green-300: oklch(87.1% 0.15 154.449); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-green-500: oklch(72.3% 0.219 149.579); + --color-green-600: oklch(62.7% 0.194 149.214); + --color-green-700: oklch(52.7% 0.154 150.069); + --color-green-800: oklch(44.8% 0.119 151.328); + --color-green-900: oklch(39.3% 0.095 152.535); + --color-cyan-300: oklch(86.5% 0.127 207.078); + --color-cyan-500: oklch(71.5% 0.143 215.221); + --color-cyan-600: oklch(60.9% 0.126 221.723); + --color-cyan-700: oklch(52% 0.105 223.128); + --color-sky-300: oklch(82.8% 0.111 230.318); + --color-sky-400: oklch(74.6% 0.16 232.661); + --color-sky-500: oklch(68.5% 0.169 237.323); + --color-sky-600: oklch(58.8% 0.158 241.966); + --color-sky-700: oklch(50% 0.134 242.749); + --color-blue-50: oklch(97% 0.014 254.604); + --color-blue-200: oklch(88.2% 0.059 254.128); + --color-blue-300: oklch(80.9% 0.105 251.813); + --color-blue-400: oklch(70.7% 0.165 254.624); + --color-blue-500: oklch(62.3% 0.214 259.815); + --color-blue-600: oklch(54.6% 0.245 262.881); + --color-blue-700: oklch(48.8% 0.243 264.376); + --color-blue-800: oklch(42.4% 0.199 265.638); + --color-blue-900: oklch(37.9% 0.146 265.522); + --color-indigo-50: oklch(96.2% 0.018 272.314); + --color-indigo-100: oklch(93% 0.034 272.788); + --color-indigo-200: oklch(87% 0.065 274.039); + --color-indigo-300: oklch(78.5% 0.115 274.713); + --color-indigo-400: oklch(67.3% 0.182 276.935); + --color-indigo-500: oklch(58.5% 0.233 277.117); + --color-indigo-600: oklch(51.1% 0.262 276.966); + --color-indigo-700: oklch(45.7% 0.24 277.023); + --color-indigo-800: oklch(39.8% 0.195 277.366); + --color-indigo-900: oklch(35.9% 0.144 278.697); + --color-violet-50: oklch(96.9% 0.016 293.756); + --color-violet-100: oklch(94.3% 0.029 294.588); + --color-violet-200: oklch(89.4% 0.057 293.283); + --color-violet-700: oklch(49.1% 0.27 292.581); + --color-violet-800: oklch(43.2% 0.232 292.759); + --color-violet-900: oklch(38% 0.189 293.745); + --color-purple-500: oklch(62.7% 0.265 303.9); + --color-purple-600: oklch(55.8% 0.288 302.321); + --color-purple-700: oklch(49.6% 0.265 301.924); + --color-purple-800: oklch(43.8% 0.218 303.724); + --color-pink-600: oklch(59.2% 0.249 0.584); + --color-slate-400: oklch(70.4% 0.04 256.788); + --color-slate-500: oklch(55.4% 0.046 257.417); + --color-gray-50: oklch(98.5% 0.002 247.839); + --color-gray-100: oklch(96.7% 0.003 264.542); + --color-gray-200: oklch(92.8% 0.006 264.531); + --color-gray-300: oklch(87.2% 0.01 258.338); + --color-gray-400: oklch(70.7% 0.022 261.325); + --color-gray-500: oklch(55.1% 0.027 264.364); + --color-gray-600: oklch(44.6% 0.03 256.802); + --color-gray-700: oklch(37.3% 0.034 259.733); + --color-gray-800: oklch(27.8% 0.033 256.848); + --color-gray-900: oklch(21% 0.034 264.665); + --color-black: #000; + --color-white: #fff; + --spacing: 0.25rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --container-xs: 20rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-3xl: 48rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --text-5xl: 3rem; + --text-5xl--line-height: 1; + --text-6xl: 3.75rem; + --text-6xl--line-height: 1; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --font-weight-extrabold: 800; + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-wider: 0.05em; + --leading-tight: 1.25; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-spin: spin 1s linear infinite; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + & { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + ::-webkit-calendar-picker-indicator { + line-height: 1; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden='until-found'])) { + display: none !important; + } +} +@layer utilities { + .pointer-events-auto { + pointer-events: auto; + } + .pointer-events-none { + pointer-events: none; + } + .invisible { + visibility: hidden; + } + .visible { + visibility: visible; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + } + .absolute { + position: absolute; + } + .fixed { + position: fixed; + } + .relative { + position: relative; + } + .static { + position: static; + } + .sticky { + position: sticky; + } + .inset-0 { + inset: calc(var(--spacing) * 0); + } + .inset-y-0 { + inset-block: calc(var(--spacing) * 0); + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .bottom-0 { + bottom: calc(var(--spacing) * 0); + } + .z-10 { + z-index: 10; + } + .z-20 { + z-index: 20; + } + .z-40 { + z-index: 40; + } + .col-span-6 { + grid-column: span 6 / span 6; + } + .col-span-12 { + grid-column: span 12 / span 12; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .m-0 { + margin: calc(var(--spacing) * 0); + } + .m-2 { + margin: calc(var(--spacing) * 2); + } + .m-4 { + margin: calc(var(--spacing) * 4); + } + .-mx-1\.5 { + margin-inline: calc(var(--spacing) * -1.5); + } + .-mx-2 { + margin-inline: calc(var(--spacing) * -2); + } + .-mx-4 { + margin-inline: calc(var(--spacing) * -4); + } + .mx-1 { + margin-inline: calc(var(--spacing) * 1); + } + .mx-3 { + margin-inline: calc(var(--spacing) * 3); + } + .mx-auto { + margin-inline: auto; + } + .-my-1\.5 { + margin-block: calc(var(--spacing) * -1.5); + } + .-my-2 { + margin-block: calc(var(--spacing) * -2); + } + .my-2 { + margin-block: calc(var(--spacing) * 2); + } + .my-3 { + margin-block: calc(var(--spacing) * 3); + } + .my-4 { + margin-block: calc(var(--spacing) * 4); + } + .my-8 { + margin-block: calc(var(--spacing) * 8); + } + .-mt-0\.5 { + margin-top: calc(var(--spacing) * -0.5); + } + .-mt-1 { + margin-top: calc(var(--spacing) * -1); + } + .-mt-3 { + margin-top: calc(var(--spacing) * -3); + } + .-mt-8 { + margin-top: calc(var(--spacing) * -8); + } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } + .mt-3 { + margin-top: calc(var(--spacing) * 3); + } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mt-5 { + margin-top: calc(var(--spacing) * 5); + } + .mt-8 { + margin-top: calc(var(--spacing) * 8); + } + .mt-12 { + margin-top: calc(var(--spacing) * 12); + } + .mt-16 { + margin-top: calc(var(--spacing) * 16); + } + .-mr-12 { + margin-right: calc(var(--spacing) * -12); + } + .-mr-28 { + margin-right: calc(var(--spacing) * -28); + } + .mr-1 { + margin-right: calc(var(--spacing) * 1); + } + .mr-2 { + margin-right: calc(var(--spacing) * 2); + } + .mr-3 { + margin-right: calc(var(--spacing) * 3); + } + .mr-4 { + margin-right: calc(var(--spacing) * 4); + } + .mb-1 { + margin-bottom: calc(var(--spacing) * 1); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-3 { + margin-bottom: calc(var(--spacing) * 3); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-8 { + margin-bottom: calc(var(--spacing) * 8); + } + .mb-16 { + margin-bottom: calc(var(--spacing) * 16); + } + .mb-20 { + margin-bottom: calc(var(--spacing) * 20); + } + .mb-24 { + margin-bottom: calc(var(--spacing) * 24); + } + .-ml-0\.5 { + margin-left: calc(var(--spacing) * -0.5); + } + .-ml-1 { + margin-left: calc(var(--spacing) * -1); + } + .ml-0\.5 { + margin-left: calc(var(--spacing) * 0.5); + } + .ml-1 { + margin-left: calc(var(--spacing) * 1); + } + .ml-2 { + margin-left: calc(var(--spacing) * 2); + } + .ml-3 { + margin-left: calc(var(--spacing) * 3); + } + .ml-4 { + margin-left: calc(var(--spacing) * 4); + } + .ml-8 { + margin-left: calc(var(--spacing) * 8); + } + .ml-auto { + margin-left: auto; + } + .block { + display: block; + } + .contents { + display: contents; + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .inline-flex { + display: inline-flex; + } + .table { + display: table; + } + .h-0 { + height: calc(var(--spacing) * 0); + } + .h-2 { + height: calc(var(--spacing) * 2); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .h-6 { + height: calc(var(--spacing) * 6); + } + .h-7 { + height: calc(var(--spacing) * 7); + } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-10 { + height: calc(var(--spacing) * 10); + } + .h-12 { + height: calc(var(--spacing) * 12); + } + .h-14 { + height: calc(var(--spacing) * 14); + } + .h-16 { + height: calc(var(--spacing) * 16); + } + .h-20 { + height: calc(var(--spacing) * 20); + } + .h-24 { + height: calc(var(--spacing) * 24); + } + .h-32 { + height: calc(var(--spacing) * 32); + } + .h-full { + height: 100%; + } + .max-h-24 { + max-height: calc(var(--spacing) * 24); + } + .max-h-60 { + max-height: calc(var(--spacing) * 60); + } + .min-h-0 { + min-height: calc(var(--spacing) * 0); + } + .min-h-full { + min-height: 100%; + } + .w-2 { + width: calc(var(--spacing) * 2); + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-6 { + width: calc(var(--spacing) * 6); + } + .w-8 { + width: calc(var(--spacing) * 8); + } + .w-10 { + width: calc(var(--spacing) * 10); + } + .w-11 { + width: calc(var(--spacing) * 11); + } + .w-12 { + width: calc(var(--spacing) * 12); + } + .w-14 { + width: calc(var(--spacing) * 14); + } + .w-16 { + width: calc(var(--spacing) * 16); + } + .w-24 { + width: calc(var(--spacing) * 24); + } + .w-32 { + width: calc(var(--spacing) * 32); + } + .w-80 { + width: calc(var(--spacing) * 80); + } + .w-96 { + width: calc(var(--spacing) * 96); + } + .w-auto { + width: auto; + } + .w-full { + width: 100%; + } + .w-screen { + width: 100vw; + } + .max-w-7xl { + max-width: var(--container-7xl); + } + .max-w-24 { + max-width: calc(var(--spacing) * 24); + } + .max-w-fit { + max-width: fit-content; + } + .max-w-full { + max-width: 100%; + } + .max-w-lg { + max-width: var(--container-lg); + } + .max-w-md { + max-width: var(--container-md); + } + .max-w-screen-lg { + max-width: var(--breakpoint-lg); + } + .max-w-screen-md { + max-width: var(--breakpoint-md); + } + .max-w-xl { + max-width: var(--container-xl); + } + .max-w-xs { + max-width: var(--container-xs); + } + .min-w-0 { + min-width: calc(var(--spacing) * 0); + } + .min-w-full { + min-width: 100%; + } + .flex-1 { + flex: 1; + } + .flex-none { + flex: none; + } + .flex-shrink { + flex-shrink: 1; + } + .flex-shrink-0 { + flex-shrink: 0; + } + .shrink { + flex-shrink: 1; + } + .shrink-0 { + flex-shrink: 0; + } + .flex-grow { + flex-grow: 1; + } + .grow { + flex-grow: 1; + } + .grow-0 { + flex-grow: 0; + } + .border-collapse { + border-collapse: collapse; + } + .-translate-x-full { + --tw-translate-x: -100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-x-0 { + --tw-translate-x: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-x-5 { + --tw-translate-x: calc(var(--spacing) * 5); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-x-full { + --tw-translate-x: 100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .rotate-180 { + rotate: 180deg; + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .animate-spin { + animation: var(--animate-spin); + } + .cursor-default { + cursor: default; + } + .cursor-pointer { + cursor: pointer; + } + .cursor-text { + cursor: text; + } + .resize { + resize: both; + } + .list-decimal { + list-style-type: decimal; + } + .appearance-none { + appearance: none; + } + .grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .flex-col { + flex-direction: column; + } + .flex-nowrap { + flex-wrap: nowrap; + } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .items-end { + align-items: flex-end; + } + .items-start { + align-items: flex-start; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .justify-end { + justify-content: flex-end; + } + .justify-start { + justify-content: flex-start; + } + .gap-4 { + gap: calc(var(--spacing) * 4); + } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .space-y-1 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-6 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-x-2 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-3 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-4 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); + } + } + .gap-y-4 { + row-gap: calc(var(--spacing) * 4); + } + .divide-x { + :where(& > :not(:last-child)) { + --tw-divide-x-reverse: 0; + border-inline-style: var(--tw-border-style); + border-inline-start-width: calc(1px * var(--tw-divide-x-reverse)); + border-inline-end-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); + } + } + .divide-y { + :where(& > :not(:last-child)) { + --tw-divide-y-reverse: 0; + border-bottom-style: var(--tw-border-style); + border-top-style: var(--tw-border-style); + border-top-width: calc(1px * var(--tw-divide-y-reverse)); + border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + } + } + .divide-gray-200 { + :where(& > :not(:last-child)) { + border-color: var(--color-gray-200); + } + } + .divide-gray-300 { + :where(& > :not(:last-child)) { + border-color: var(--color-gray-300); + } + } + .self-center { + align-self: center; + } + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .overflow-auto { + overflow: auto; + } + .overflow-hidden { + overflow: hidden; + } + .overflow-x-auto { + overflow-x: auto; + } + .overflow-x-hidden { + overflow-x: hidden; + } + .overflow-y-auto { + overflow-y: auto; + } + .rounded { + border-radius: 0.25rem; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } + .rounded-md { + border-radius: var(--radius-md); + } + .rounded-sm { + border-radius: var(--radius-sm); + } + .rounded-l-lg { + border-top-left-radius: var(--radius-lg); + border-bottom-left-radius: var(--radius-lg); + } + .rounded-r-md { + border-top-right-radius: var(--radius-md); + border-bottom-right-radius: var(--radius-md); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px; + } + .border-y { + border-block-style: var(--tw-border-style); + border-block-width: 1px; + } + .border-t { + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + .border-r { + border-right-style: var(--tw-border-style); + border-right-width: 1px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-l-4 { + border-left-style: var(--tw-border-style); + border-left-width: 4px; + } + .border-dashed { + --tw-border-style: dashed; + border-style: dashed; + } + .border-none { + --tw-border-style: none; + border-style: none; + } + .border-blue-400 { + border-color: var(--color-blue-400); + } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-gray-300 { + border-color: var(--color-gray-300); + } + .border-green-400 { + border-color: var(--color-green-400); + } + .border-indigo-500 { + border-color: var(--color-indigo-500); + } + .border-red-300 { + border-color: var(--color-red-300); + } + .border-red-400 { + border-color: var(--color-red-400); + } + .border-transparent { + border-color: transparent; + } + .border-yellow-400 { + border-color: var(--color-yellow-400); + } + .bg-blue-50 { + background-color: var(--color-blue-50); + } + .bg-blue-600 { + background-color: var(--color-blue-600); + } + .bg-cyan-600 { + background-color: var(--color-cyan-600); + } + .bg-gray-50 { + background-color: var(--color-gray-50); + } + .bg-gray-100 { + background-color: var(--color-gray-100); + } + .bg-gray-200 { + background-color: var(--color-gray-200); + } + .bg-gray-500 { + background-color: var(--color-gray-500); + } + .bg-gray-600 { + background-color: var(--color-gray-600); + } + .bg-gray-700 { + background-color: var(--color-gray-700); + } + .bg-gray-800 { + background-color: var(--color-gray-800); + } + .bg-green-50 { + background-color: var(--color-green-50); + } + .bg-green-100 { + background-color: var(--color-green-100); + } + .bg-green-600 { + background-color: var(--color-green-600); + } + .bg-indigo-50 { + background-color: var(--color-indigo-50); + } + .bg-indigo-100 { + background-color: var(--color-indigo-100); + } + .bg-indigo-600 { + background-color: var(--color-indigo-600); + } + .bg-purple-600 { + background-color: var(--color-purple-600); + } + .bg-red-50 { + background-color: var(--color-red-50); + } + .bg-red-100 { + background-color: var(--color-red-100); + } + .bg-red-400 { + background-color: var(--color-red-400); + } + .bg-red-600 { + background-color: var(--color-red-600); + } + .bg-sky-500 { + background-color: var(--color-sky-500); + } + .bg-sky-600 { + background-color: var(--color-sky-600); + } + .bg-white { + background-color: var(--color-white); + } + .bg-yellow-50 { + background-color: var(--color-yellow-50); + } + .mask-repeat { + mask-repeat: repeat; + } + .fill-gray-600 { + fill: var(--color-gray-600); + } + .object-cover { + object-fit: cover; + } + .p-0 { + padding: calc(var(--spacing) * 0); + } + .p-1 { + padding: calc(var(--spacing) * 1); + } + .p-1\.5 { + padding: calc(var(--spacing) * 1.5); + } + .p-3 { + padding: calc(var(--spacing) * 3); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-6 { + padding: calc(var(--spacing) * 6); + } + .px-1 { + padding-inline: calc(var(--spacing) * 1); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-2\.5 { + padding-inline: calc(var(--spacing) * 2.5); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } + .px-8 { + padding-inline: calc(var(--spacing) * 8); + } + .py-0\.5 { + padding-block: calc(var(--spacing) * 0.5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-1\.5 { + padding-block: calc(var(--spacing) * 1.5); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .py-6 { + padding-block: calc(var(--spacing) * 6); + } + .py-8 { + padding-block: calc(var(--spacing) * 8); + } + .pt-0\.5 { + padding-top: calc(var(--spacing) * 0.5); + } + .pt-1 { + padding-top: calc(var(--spacing) * 1); + } + .pt-1\.5 { + padding-top: calc(var(--spacing) * 1.5); + } + .pt-2 { + padding-top: calc(var(--spacing) * 2); + } + .pt-3 { + padding-top: calc(var(--spacing) * 3); + } + .pt-4 { + padding-top: calc(var(--spacing) * 4); + } + .pt-5 { + padding-top: calc(var(--spacing) * 5); + } + .pt-6 { + padding-top: calc(var(--spacing) * 6); + } + .pt-8 { + padding-top: calc(var(--spacing) * 8); + } + .pr-1 { + padding-right: calc(var(--spacing) * 1); + } + .pr-2 { + padding-right: calc(var(--spacing) * 2); + } + .pr-3 { + padding-right: calc(var(--spacing) * 3); + } + .pr-4 { + padding-right: calc(var(--spacing) * 4); + } + .pr-6 { + padding-right: calc(var(--spacing) * 6); + } + .pr-9 { + padding-right: calc(var(--spacing) * 9); + } + .pr-10 { + padding-right: calc(var(--spacing) * 10); + } + .pb-0 { + padding-bottom: calc(var(--spacing) * 0); + } + .pb-1 { + padding-bottom: calc(var(--spacing) * 1); + } + .pb-1\.5 { + padding-bottom: calc(var(--spacing) * 1.5); + } + .pb-2 { + padding-bottom: calc(var(--spacing) * 2); + } + .pb-4 { + padding-bottom: calc(var(--spacing) * 4); + } + .pb-5 { + padding-bottom: calc(var(--spacing) * 5); + } + .pl-1 { + padding-left: calc(var(--spacing) * 1); + } + .pl-2 { + padding-left: calc(var(--spacing) * 2); + } + .pl-2\.5 { + padding-left: calc(var(--spacing) * 2.5); + } + .pl-3 { + padding-left: calc(var(--spacing) * 3); + } + .pl-4 { + padding-left: calc(var(--spacing) * 4); + } + .pl-5 { + padding-left: calc(var(--spacing) * 5); + } + .pl-10 { + padding-left: calc(var(--spacing) * 10); + } + .text-center { + text-align: center; + } + .text-justify { + text-align: justify; + } + .text-left { + text-align: left; + } + .text-right { + text-align: right; + } + .align-bottom { + vertical-align: bottom; + } + .align-middle { + vertical-align: middle; + } + .align-top { + vertical-align: top; + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } + .text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + .text-base { + font-size: var(--text-base); + line-height: var(--tw-leading, var(--text-base--line-height)); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .leading-6 { + --tw-leading: calc(var(--spacing) * 6); + line-height: calc(var(--spacing) * 6); + } + .leading-8 { + --tw-leading: calc(var(--spacing) * 8); + line-height: calc(var(--spacing) * 8); + } + .leading-tight { + --tw-leading: var(--leading-tight); + line-height: var(--leading-tight); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-extrabold { + --tw-font-weight: var(--font-weight-extrabold); + font-weight: var(--font-weight-extrabold); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-tight { + --tw-tracking: var(--tracking-tight); + letter-spacing: var(--tracking-tight); + } + .tracking-tighter { + --tw-tracking: var(--tracking-tighter); + letter-spacing: var(--tracking-tighter); + } + .tracking-wider { + --tw-tracking: var(--tracking-wider); + letter-spacing: var(--tracking-wider); + } + .whitespace-nowrap { + white-space: nowrap; + } + .whitespace-pre { + white-space: pre; + } + .text-black { + color: var(--color-black); + } + .text-blue-400 { + color: var(--color-blue-400); + } + .text-blue-600 { + color: var(--color-blue-600); + } + .text-blue-700 { + color: var(--color-blue-700); + } + .text-danger { + color: rgb(153 27 27); + } + .text-gray-100 { + color: var(--color-gray-100); + } + .text-gray-200 { + color: var(--color-gray-200); + } + .text-gray-300 { + color: var(--color-gray-300); + } + .text-gray-400 { + color: var(--color-gray-400); + } + .text-gray-500 { + color: var(--color-gray-500); + } + .text-gray-600 { + color: var(--color-gray-600); + } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-gray-900 { + color: var(--color-gray-900); + } + .text-green-400 { + color: var(--color-green-400); + } + .text-green-500 { + color: var(--color-green-500); + } + .text-green-600 { + color: var(--color-green-600); + } + .text-green-700 { + color: var(--color-green-700); + } + .text-green-800 { + color: var(--color-green-800); + } + .text-green-900 { + color: var(--color-green-900); + } + .text-indigo-400 { + color: var(--color-indigo-400); + } + .text-indigo-600 { + color: var(--color-indigo-600); + } + .text-indigo-700 { + color: var(--color-indigo-700); + } + .text-red-400 { + color: var(--color-red-400); + } + .text-red-500 { + color: var(--color-red-500); + } + .text-red-600 { + color: var(--color-red-600); + } + .text-red-700 { + color: var(--color-red-700); + } + .text-red-800 { + color: var(--color-red-800); + } + .text-red-900 { + color: var(--color-red-900); + } + .text-sky-500 { + color: var(--color-sky-500); + } + .text-sky-600 { + color: var(--color-sky-600); + } + .text-slate-500 { + color: var(--color-slate-500); + } + .text-white { + color: var(--color-white); + } + .text-yellow-400 { + color: var(--color-yellow-400); + } + .text-yellow-500 { + color: var(--color-yellow-500); + } + .text-yellow-700 { + color: var(--color-yellow-700); + } + .text-yellow-800 { + color: var(--color-yellow-800); + } + .normal-case { + text-transform: none; + } + .uppercase { + text-transform: uppercase; + } + .line-through { + text-decoration-line: line-through; + } + .underline { + text-decoration-line: underline; + } + .placeholder-red-300 { + &::placeholder { + color: var(--color-red-300); + } + } + .opacity-0 { + opacity: 0%; + } + .opacity-100 { + opacity: 100%; + } + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-lg { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-xl { + --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .ring-0 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .ring-1 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .ring-black\/5 { + --tw-ring-color: color-mix(in srgb, #000 5%, transparent); + @supports (color: color-mix(in lab, red, red)) { + & { + --tw-ring-color: color-mix(in oklab, var(--color-black) 5%, transparent); + } + } + } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } + .blur { + --tw-blur: blur(8px); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-opacity { + transition-property: opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-transform { + transition-property: transform, translate, scale, rotate; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-100 { + --tw-duration: 100ms; + transition-duration: 100ms; + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } + .duration-500 { + --tw-duration: 500ms; + transition-duration: 500ms; + } + .ease-in { + --tw-ease: var(--ease-in); + transition-timing-function: var(--ease-in); + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + .ease-out { + --tw-ease: var(--ease-out); + transition-timing-function: var(--ease-out); + } + .select-none { + -webkit-user-select: none; + user-select: none; + } + .group-hover\:inline { + &:is(:where(.group):hover *) { + @media (hover: hover) { + display: inline; + } + } + } + .group-hover\:text-gray-500 { + &:is(:where(.group):hover *) { + @media (hover: hover) { + color: var(--color-gray-500); + } + } + } + .file\:mr-4 { + &::file-selector-button { + margin-right: calc(var(--spacing) * 4); + } + } + .file\:rounded-full { + &::file-selector-button { + border-radius: calc(infinity * 1px); + } + } + .file\:border-0 { + &::file-selector-button { + border-style: var(--tw-border-style); + border-width: 0px; + } + } + .file\:bg-violet-50 { + &::file-selector-button { + background-color: var(--color-violet-50); + } + } + .file\:px-4 { + &::file-selector-button { + padding-inline: calc(var(--spacing) * 4); + } + } + .file\:py-2 { + &::file-selector-button { + padding-block: calc(var(--spacing) * 2); + } + } + .file\:text-sm { + &::file-selector-button { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + } + .file\:font-semibold { + &::file-selector-button { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + } + .file\:text-violet-700 { + &::file-selector-button { + color: var(--color-violet-700); + } + } + .focus-within\:border-indigo-500 { + &:focus-within { + border-color: var(--color-indigo-500); + } + } + .focus-within\:border-red-500 { + &:focus-within { + border-color: var(--color-red-500); + } + } + .focus-within\:border-transparent { + &:focus-within { + border-color: transparent; + } + } + .focus-within\:ring-1 { + &:focus-within { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus-within\:ring-2 { + &:focus-within { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus-within\:ring-indigo-500 { + &:focus-within { + --tw-ring-color: var(--color-indigo-500); + } + } + .focus-within\:ring-red-500 { + &:focus-within { + --tw-ring-color: var(--color-red-500); + } + } + .focus-within\:ring-offset-2 { + &:focus-within { + --tw-ring-offset-width: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + } + } + .focus-within\:outline-none { + &:focus-within { + --tw-outline-style: none; + outline-style: none; + } + } + .hover\:border-gray-400 { + &:hover { + @media (hover: hover) { + border-color: var(--color-gray-400); + } + } + } + .hover\:bg-blue-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + .hover\:bg-cyan-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-cyan-700); + } + } + } + .hover\:bg-gray-50 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-50); + } + } + } + .hover\:bg-gray-100 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-100); + } + } + } + .hover\:bg-green-100 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-100); + } + } + } + .hover\:bg-green-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-700); + } + } + } + .hover\:bg-indigo-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-indigo-200); + } + } + } + .hover\:bg-indigo-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-indigo-700); + } + } + } + .hover\:bg-purple-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-purple-700); + } + } + } + .hover\:bg-red-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-200); + } + } + } + .hover\:bg-red-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-700); + } + } + } + .hover\:bg-sky-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-sky-700); + } + } + } + .hover\:bg-yellow-50 { + &:hover { + @media (hover: hover) { + background-color: var(--color-yellow-50); + } + } + } + .hover\:bg-yellow-100 { + &:hover { + @media (hover: hover) { + background-color: var(--color-yellow-100); + } + } + } + .hover\:text-blue-500 { + &:hover { + @media (hover: hover) { + color: var(--color-blue-500); + } + } + } + .hover\:text-blue-700 { + &:hover { + @media (hover: hover) { + color: var(--color-blue-700); + } + } + } + .hover\:text-blue-800 { + &:hover { + @media (hover: hover) { + color: var(--color-blue-800); + } + } + } + .hover\:text-gray-500 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-500); + } + } + } + .hover\:text-gray-700 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-700); + } + } + } + .hover\:text-gray-900 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-900); + } + } + } + .hover\:text-green-500 { + &:hover { + @media (hover: hover) { + color: var(--color-green-500); + } + } + } + .hover\:text-green-600 { + &:hover { + @media (hover: hover) { + color: var(--color-green-600); + } + } + } + .hover\:text-indigo-500 { + &:hover { + @media (hover: hover) { + color: var(--color-indigo-500); + } + } + } + .hover\:text-indigo-600 { + &:hover { + @media (hover: hover) { + color: var(--color-indigo-600); + } + } + } + .hover\:text-red-500 { + &:hover { + @media (hover: hover) { + color: var(--color-red-500); + } + } + } + .hover\:text-sky-500 { + &:hover { + @media (hover: hover) { + color: var(--color-sky-500); + } + } + } + .hover\:no-underline { + &:hover { + @media (hover: hover) { + text-decoration-line: none; + } + } + } + .hover\:shadow-2xl { + &:hover { + @media (hover: hover) { + --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + } + .hover\:file\:bg-violet-100 { + &:hover { + @media (hover: hover) { + &::file-selector-button { + background-color: var(--color-violet-100); + } + } + } + } + .focus\:z-10 { + &:focus { + z-index: 10; + } + } + .focus\:\!border-none { + &:focus { + --tw-border-style: none !important; + border-style: none !important; + } + } + .focus\:border-indigo-500 { + &:focus { + border-color: var(--color-indigo-500); + } + } + .focus\:border-red-500 { + &:focus { + border-color: var(--color-red-500); + } + } + .focus\:bg-indigo-500 { + &:focus { + background-color: var(--color-indigo-500); + } + } + .focus\:bg-red-500 { + &:focus { + background-color: var(--color-red-500); + } + } + .focus\:text-blue-700 { + &:focus { + color: var(--color-blue-700); + } + } + .focus\:text-white { + &:focus { + color: var(--color-white); + } + } + .focus\:ring-1 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring))); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:ring-blue-700 { + &:focus { + --tw-ring-color: var(--color-blue-700); + } + } + .focus\:ring-cyan-300 { + &:focus { + --tw-ring-color: var(--color-cyan-300); + } + } + .focus\:ring-cyan-500 { + &:focus { + --tw-ring-color: var(--color-cyan-500); + } + } + .focus\:ring-green-300 { + &:focus { + --tw-ring-color: var(--color-green-300); + } + } + .focus\:ring-green-500 { + &:focus { + --tw-ring-color: var(--color-green-500); + } + } + .focus\:ring-green-600 { + &:focus { + --tw-ring-color: var(--color-green-600); + } + } + .focus\:ring-indigo-500 { + &:focus { + --tw-ring-color: var(--color-indigo-500); + } + } + .focus\:ring-purple-500 { + &:focus { + --tw-ring-color: var(--color-purple-500); + } + } + .focus\:ring-red-500 { + &:focus { + --tw-ring-color: var(--color-red-500); + } + } + .focus\:ring-sky-300 { + &:focus { + --tw-ring-color: var(--color-sky-300); + } + } + .focus\:ring-sky-500 { + &:focus { + --tw-ring-color: var(--color-sky-500); + } + } + .focus\:ring-white { + &:focus { + --tw-ring-color: var(--color-white); + } + } + .focus\:ring-yellow-600 { + &:focus { + --tw-ring-color: var(--color-yellow-600); + } + } + .focus\:ring-offset-2 { + &:focus { + --tw-ring-offset-width: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + } + } + .focus\:ring-offset-green-50 { + &:focus { + --tw-ring-offset-color: var(--color-green-50); + } + } + .focus\:ring-offset-yellow-50 { + &:focus { + --tw-ring-offset-color: var(--color-yellow-50); + } + } + .focus\:\!outline-none { + &:focus { + --tw-outline-style: none !important; + outline-style: none !important; + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .focus\:ring-inset { + &:focus { + --tw-ring-inset: inset; + } + } + .disabled\:bg-gray-100 { + &:disabled { + background-color: var(--color-gray-100); + } + } + .disabled\:shadow-none { + &:disabled { + --tw-shadow: 0 0 #0000; + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .sm\:col-span-3 { + @media (width >= 40rem) { + grid-column: span 3 / span 3; + } + } + .sm\:-mx-6 { + @media (width >= 40rem) { + margin-inline: calc(var(--spacing) * -6); + } + } + .sm\:mx-4 { + @media (width >= 40rem) { + margin-inline: calc(var(--spacing) * 4); + } + } + .sm\:my-8 { + @media (width >= 40rem) { + margin-block: calc(var(--spacing) * 8); + } + } + .sm\:mt-0 { + @media (width >= 40rem) { + margin-top: calc(var(--spacing) * 0); + } + } + .sm\:mt-24 { + @media (width >= 40rem) { + margin-top: calc(var(--spacing) * 24); + } + } + .sm\:ml-3 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 3); + } + } + .sm\:block { + @media (width >= 40rem) { + display: block; + } + } + .sm\:flex { + @media (width >= 40rem) { + display: flex; + } + } + .sm\:inline { + @media (width >= 40rem) { + display: inline; + } + } + .sm\:inline-block { + @media (width >= 40rem) { + display: inline-block; + } + } + .sm\:table-cell { + @media (width >= 40rem) { + display: table-cell; + } + } + .sm\:h-screen { + @media (width >= 40rem) { + height: 100vh; + } + } + .sm\:w-full { + @media (width >= 40rem) { + width: 100%; + } + } + .sm\:max-w-prose { + @media (width >= 40rem) { + max-width: 65ch; + } + } + .sm\:flex-row-reverse { + @media (width >= 40rem) { + flex-direction: row-reverse; + } + } + .sm\:items-center { + @media (width >= 40rem) { + align-items: center; + } + } + .sm\:justify-between { + @media (width >= 40rem) { + justify-content: space-between; + } + } + .sm\:justify-center { + @media (width >= 40rem) { + justify-content: center; + } + } + .sm\:space-x-5 { + @media (width >= 40rem) { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 5) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse))); + } + } + } + .sm\:overflow-hidden { + @media (width >= 40rem) { + overflow: hidden; + } + } + .sm\:rounded { + @media (width >= 40rem) { + border-radius: 0.25rem; + } + } + .sm\:rounded-md { + @media (width >= 40rem) { + border-radius: var(--radius-md); + } + } + .sm\:p-0 { + @media (width >= 40rem) { + padding: calc(var(--spacing) * 0); + } + } + .sm\:p-6 { + @media (width >= 40rem) { + padding: calc(var(--spacing) * 6); + } + } + .sm\:px-6 { + @media (width >= 40rem) { + padding-inline: calc(var(--spacing) * 6); + } + } + .sm\:py-16 { + @media (width >= 40rem) { + padding-block: calc(var(--spacing) * 16); + } + } + .sm\:pt-1 { + @media (width >= 40rem) { + padding-top: calc(var(--spacing) * 1); + } + } + .sm\:pt-3 { + @media (width >= 40rem) { + padding-top: calc(var(--spacing) * 3); + } + } + .sm\:pr-6 { + @media (width >= 40rem) { + padding-right: calc(var(--spacing) * 6); + } + } + .sm\:pb-0 { + @media (width >= 40rem) { + padding-bottom: calc(var(--spacing) * 0); + } + } + .sm\:pb-4 { + @media (width >= 40rem) { + padding-bottom: calc(var(--spacing) * 4); + } + } + .sm\:pl-3 { + @media (width >= 40rem) { + padding-left: calc(var(--spacing) * 3); + } + } + .sm\:pl-16 { + @media (width >= 40rem) { + padding-left: calc(var(--spacing) * 16); + } + } + .sm\:text-left { + @media (width >= 40rem) { + text-align: left; + } + } + .sm\:align-middle { + @media (width >= 40rem) { + vertical-align: middle; + } + } + .sm\:text-2xl { + @media (width >= 40rem) { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + } + .sm\:text-4xl { + @media (width >= 40rem) { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + } + .sm\:text-5xl { + @media (width >= 40rem) { + font-size: var(--text-5xl); + line-height: var(--tw-leading, var(--text-5xl--line-height)); + } + } + .sm\:text-lg { + @media (width >= 40rem) { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + } + .sm\:text-sm { + @media (width >= 40rem) { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + } + .sm\:shadow { + @media (width >= 40rem) { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .sm\:duration-700 { + @media (width >= 40rem) { + --tw-duration: 700ms; + transition-duration: 700ms; + } + } + .md\:fixed { + @media (width >= 48rem) { + position: fixed; + } + } + .md\:inset-y-0 { + @media (width >= 48rem) { + inset-block: calc(var(--spacing) * 0); + } + } + .md\:mt-5 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 5); + } + } + .md\:mt-8 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 8); + } + } + .md\:mb-12 { + @media (width >= 48rem) { + margin-bottom: calc(var(--spacing) * 12); + } + } + .md\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .md\:hidden { + @media (width >= 48rem) { + display: none; + } + } + .md\:inline { + @media (width >= 48rem) { + display: inline; + } + } + .md\:table-cell { + @media (width >= 48rem) { + display: table-cell; + } + } + .md\:w-64 { + @media (width >= 48rem) { + width: calc(var(--spacing) * 64); + } + } + .md\:max-w-3xl { + @media (width >= 48rem) { + max-width: var(--container-3xl); + } + } + .md\:max-w-xl { + @media (width >= 48rem) { + max-width: var(--container-xl); + } + } + .md\:flex-col { + @media (width >= 48rem) { + flex-direction: column; + } + } + .md\:rounded-lg { + @media (width >= 48rem) { + border-radius: var(--radius-lg); + } + } + .md\:p-4 { + @media (width >= 48rem) { + padding: calc(var(--spacing) * 4); + } + } + .md\:px-6 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 6); + } + } + .md\:px-8 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .md\:px-10 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 10); + } + } + .md\:py-4 { + @media (width >= 48rem) { + padding-block: calc(var(--spacing) * 4); + } + } + .md\:pr-8 { + @media (width >= 48rem) { + padding-right: calc(var(--spacing) * 8); + } + } + .md\:pl-64 { + @media (width >= 48rem) { + padding-left: calc(var(--spacing) * 64); + } + } + .md\:text-4xl { + @media (width >= 48rem) { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + } + .md\:text-6xl { + @media (width >= 48rem) { + font-size: var(--text-6xl); + line-height: var(--tw-leading, var(--text-6xl--line-height)); + } + } + .md\:text-lg { + @media (width >= 48rem) { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + } + .md\:text-xl { + @media (width >= 48rem) { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + } + .lg\:-mx-8 { + @media (width >= 64rem) { + margin-inline: calc(var(--spacing) * -8); + } + } + .lg\:block { + @media (width >= 64rem) { + display: block; + } + } + .lg\:inline { + @media (width >= 64rem) { + display: inline; + } + } + .lg\:table-cell { + @media (width >= 64rem) { + display: table-cell; + } + } + .lg\:max-w-screen-md { + @media (width >= 64rem) { + max-width: var(--breakpoint-md); + } + } + .lg\:rounded-md { + @media (width >= 64rem) { + border-radius: var(--radius-md); + } + } + .lg\:p-2 { + @media (width >= 64rem) { + padding: calc(var(--spacing) * 2); + } + } + .lg\:px-8 { + @media (width >= 64rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .lg\:pr-8 { + @media (width >= 64rem) { + padding-right: calc(var(--spacing) * 8); + } + } + .lg\:hover\:bg-gray-50 { + @media (width >= 64rem) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-50); + } + } + } + } + .xl\:col-span-6 { + @media (width >= 80rem) { + grid-column: span 6 / span 6; + } + } + .xl\:inline { + @media (width >= 80rem) { + display: inline; + } + } + .xl\:table-cell { + @media (width >= 80rem) { + display: table-cell; + } + } + .xl\:max-w-3xl { + @media (width >= 80rem) { + max-width: var(--container-3xl); + } + } + .xl\:max-w-screen-lg { + @media (width >= 80rem) { + max-width: var(--breakpoint-lg); + } + } + .xl\:grid-cols-4 { + @media (width >= 80rem) { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + .\32 xl\:table-cell { + @media (width >= 96rem) { + display: table-cell; + } + } + .\32 xl\:max-w-screen-xl { + @media (width >= 96rem) { + max-width: var(--breakpoint-xl); + } + } + .dark\:divide-gray-700 { + &:where(.dark, .dark *) { + :where(& > :not(:last-child)) { + border-color: var(--color-gray-700); + } + } + } + .dark\:divide-gray-800 { + &:where(.dark, .dark *) { + :where(& > :not(:last-child)) { + border-color: var(--color-gray-800); + } + } + } + .dark\:border { + &:where(.dark, .dark *) { + border-style: var(--tw-border-style); + border-width: 1px; + } + } + .dark\:border-2 { + &:where(.dark, .dark *) { + border-style: var(--tw-border-style); + border-width: 2px; + } + } + .dark\:border-gray-600 { + &:where(.dark, .dark *) { + border-color: var(--color-gray-600); + } + } + .dark\:border-gray-700 { + &:where(.dark, .dark *) { + border-color: var(--color-gray-700); + } + } + .dark\:border-gray-800 { + &:where(.dark, .dark *) { + border-color: var(--color-gray-800); + } + } + .dark\:border-indigo-400 { + &:where(.dark, .dark *) { + border-color: var(--color-indigo-400); + } + } + .dark\:border-pink-600 { + &:where(.dark, .dark *) { + border-color: var(--color-pink-600); + } + } + .dark\:bg-black { + &:where(.dark, .dark *) { + background-color: var(--color-black); + } + } + .dark\:bg-blue-200 { + &:where(.dark, .dark *) { + background-color: var(--color-blue-200); + } + } + .dark\:bg-blue-600 { + &:where(.dark, .dark *) { + background-color: var(--color-blue-600); + } + } + .dark\:bg-blue-800 { + &:where(.dark, .dark *) { + background-color: var(--color-blue-800); + } + } + .dark\:bg-cyan-600 { + &:where(.dark, .dark *) { + background-color: var(--color-cyan-600); + } + } + .dark\:bg-gray-400 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-400); + } + } + .dark\:bg-gray-400\/75 { + &:where(.dark, .dark *) { + background-color: color-mix(in srgb, oklch(70.7% 0.022 261.325) 75%, transparent); + @supports (color: color-mix(in lab, red, red)) { + & { + background-color: color-mix(in oklab, var(--color-gray-400) 75%, transparent); + } + } + } + } + .dark\:bg-gray-700 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-700); + } + } + .dark\:bg-gray-800 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-800); + } + } + .dark\:bg-gray-900 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-900); + } + } + .dark\:bg-green-200 { + &:where(.dark, .dark *) { + background-color: var(--color-green-200); + } + } + .dark\:bg-green-600 { + &:where(.dark, .dark *) { + background-color: var(--color-green-600); + } + } + .dark\:bg-green-800 { + &:where(.dark, .dark *) { + background-color: var(--color-green-800); + } + } + .dark\:bg-green-900 { + &:where(.dark, .dark *) { + background-color: var(--color-green-900); + } + } + .dark\:bg-indigo-400 { + &:where(.dark, .dark *) { + background-color: var(--color-indigo-400); + } + } + .dark\:bg-indigo-600 { + &:where(.dark, .dark *) { + background-color: var(--color-indigo-600); + } + } + .dark\:bg-indigo-800 { + &:where(.dark, .dark *) { + background-color: var(--color-indigo-800); + } + } + .dark\:bg-indigo-900 { + &:where(.dark, .dark *) { + background-color: var(--color-indigo-900); + } + } + .dark\:bg-purple-600 { + &:where(.dark, .dark *) { + background-color: var(--color-purple-600); + } + } + .dark\:bg-red-200 { + &:where(.dark, .dark *) { + background-color: var(--color-red-200); + } + } + .dark\:bg-red-600 { + &:where(.dark, .dark *) { + background-color: var(--color-red-600); + } + } + .dark\:bg-red-900 { + &:where(.dark, .dark *) { + background-color: var(--color-red-900); + } + } + .dark\:bg-sky-600 { + &:where(.dark, .dark *) { + background-color: var(--color-sky-600); + } + } + .dark\:bg-transparent { + &:where(.dark, .dark *) { + background-color: transparent; + } + } + .dark\:bg-yellow-200 { + &:where(.dark, .dark *) { + background-color: var(--color-yellow-200); + } + } + .dark\:fill-gray-300 { + &:where(.dark, .dark *) { + fill: var(--color-gray-300); + } + } + .dark\:text-black { + &:where(.dark, .dark *) { + color: var(--color-black); + } + } + .dark\:text-blue-300 { + &:where(.dark, .dark *) { + color: var(--color-blue-300); + } + } + .dark\:text-blue-400 { + &:where(.dark, .dark *) { + color: var(--color-blue-400); + } + } + .dark\:text-gray-50 { + &:where(.dark, .dark *) { + color: var(--color-gray-50); + } + } + .dark\:text-gray-100 { + &:where(.dark, .dark *) { + color: var(--color-gray-100); + } + } + .dark\:text-gray-200 { + &:where(.dark, .dark *) { + color: var(--color-gray-200); + } + } + .dark\:text-gray-300 { + &:where(.dark, .dark *) { + color: var(--color-gray-300); + } + } + .dark\:text-gray-400 { + &:where(.dark, .dark *) { + color: var(--color-gray-400); + } + } + .dark\:text-gray-500 { + &:where(.dark, .dark *) { + color: var(--color-gray-500); + } + } + .dark\:text-gray-600 { + &:where(.dark, .dark *) { + color: var(--color-gray-600); + } + } + .dark\:text-green-100 { + &:where(.dark, .dark *) { + color: var(--color-green-100); + } + } + .dark\:text-green-300 { + &:where(.dark, .dark *) { + color: var(--color-green-300); + } + } + .dark\:text-green-400 { + &:where(.dark, .dark *) { + color: var(--color-green-400); + } + } + .dark\:text-green-500 { + &:where(.dark, .dark *) { + color: var(--color-green-500); + } + } + .dark\:text-green-600 { + &:where(.dark, .dark *) { + color: var(--color-green-600); + } + } + .dark\:text-indigo-300 { + &:where(.dark, .dark *) { + color: var(--color-indigo-300); + } + } + .dark\:text-indigo-400 { + &:where(.dark, .dark *) { + color: var(--color-indigo-400); + } + } + .dark\:text-indigo-500 { + &:where(.dark, .dark *) { + color: var(--color-indigo-500); + } + } + .dark\:text-red-100 { + &:where(.dark, .dark *) { + color: var(--color-red-100); + } + } + .dark\:text-red-300 { + &:where(.dark, .dark *) { + color: var(--color-red-300); + } + } + .dark\:text-red-800 { + &:where(.dark, .dark *) { + color: var(--color-red-800); + } + } + .dark\:text-sky-300 { + &:where(.dark, .dark *) { + color: var(--color-sky-300); + } + } + .dark\:text-slate-400 { + &:where(.dark, .dark *) { + color: var(--color-slate-400); + } + } + .dark\:text-white { + &:where(.dark, .dark *) { + color: var(--color-white); + } + } + .dark\:ring-offset-black { + &:where(.dark, .dark *) { + --tw-ring-offset-color: var(--color-black); + } + } + .dark\:ring-offset-green-200 { + &:where(.dark, .dark *) { + --tw-ring-offset-color: var(--color-green-200); + } + } + .dark\:file\:bg-violet-900 { + &:where(.dark, .dark *) { + &::file-selector-button { + background-color: var(--color-violet-900); + } + } + } + .dark\:file\:text-violet-200 { + &:where(.dark, .dark *) { + &::file-selector-button { + color: var(--color-violet-200); + } + } + } + .dark\:hover\:border-blue-600 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + border-color: var(--color-blue-600); + } + } + } + } + .dark\:hover\:bg-blue-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + } + .dark\:hover\:bg-blue-900 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-900); + } + } + } + } + .dark\:hover\:bg-cyan-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-cyan-700); + } + } + } + } + .dark\:hover\:bg-gray-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-700); + } + } + } + } + .dark\:hover\:bg-gray-900 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-900); + } + } + } + } + .dark\:hover\:bg-green-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-700); + } + } + } + } + .dark\:hover\:bg-indigo-300 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-indigo-300); + } + } + } + } + .dark\:hover\:bg-indigo-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-indigo-700); + } + } + } + } + .dark\:hover\:bg-indigo-800 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-indigo-800); + } + } + } + } + .dark\:hover\:bg-purple-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-purple-700); + } + } + } + } + .dark\:hover\:bg-red-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-700); + } + } + } + } + .dark\:hover\:bg-sky-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-sky-700); + } + } + } + } + .dark\:hover\:text-blue-200 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-blue-200); + } + } + } + } + .dark\:hover\:text-blue-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-blue-400); + } + } + } + } + .dark\:hover\:text-gray-50 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-gray-50); + } + } + } + } + .dark\:hover\:text-gray-300 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-gray-300); + } + } + } + } + .dark\:hover\:text-gray-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-gray-400); + } + } + } + } + .dark\:hover\:text-green-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-green-400); + } + } + } + } + .dark\:hover\:text-indigo-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-indigo-400); + } + } + } + } + .dark\:hover\:text-red-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-red-400); + } + } + } + } + .dark\:hover\:text-sky-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-sky-400); + } + } + } + } + .dark\:hover\:text-white { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-white); + } + } + } + } + .dark\:hover\:file\:bg-violet-800 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + &::file-selector-button { + background-color: var(--color-violet-800); + } + } + } + } + } + .dark\:focus\:text-black { + &:where(.dark, .dark *) { + &:focus { + color: var(--color-black); + } + } + } + .dark\:focus\:ring-black { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-black); + } + } + } + .dark\:focus\:ring-blue-800 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-blue-800); + } + } + } + .dark\:focus\:ring-cyan-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-cyan-500); + } + } + } + .dark\:focus\:ring-green-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-green-500); + } + } + } + .dark\:focus\:ring-indigo-600 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-indigo-600); + } + } + } + .dark\:focus\:ring-indigo-800 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-indigo-800); + } + } + } + .dark\:focus\:ring-purple-800 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-purple-800); + } + } + } + .dark\:focus\:ring-red-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-red-500); + } + } + } + .dark\:focus\:ring-sky-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-sky-500); + } + } + } + .dark\:disabled\:bg-gray-800 { + &:where(.dark, .dark *) { + &:disabled { + background-color: var(--color-gray-800); + } + } + } + .dark\:lg\:hover\:bg-gray-900 { + &:where(.dark, .dark *) { + @media (width >= 64rem) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-900); + } + } + } + } + } +} +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + } + :root.dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } + *, ::after, ::before, ::backdrop, ::file-selector-button { + border-color: hsl(var(--border)); + } +} +@layer base { + [data-collapsed] [data-collapse=hidden] { + display: none; + } + [data-collapse-off=opacity-100] { + opacity: 1; + } + [data-collapsed] [data-collapse=opacity-0] { + opacity: 0; + } + [data-collapse-off=-translate-x-full] { + transform: translateX(0); + } + [data-collapsed] [data-collapse=-translate-x-full] { + transform: translateX(-100%); + } + @media (min-width: 640px) { + .youtube { + width: 761px; + height: 428px; + } + } + [v-cloak] { + display: none; + } + b, strong { + font-weight: 600; + } + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } + ::-webkit-scrollbar-thumb { + background-color: #ccc; + } + [role=dialog].z-10 { + z-index: 60; + } + em { + color: #3b82f6; + font-weight: 400; + background-color: #eff6ff; + border-radius: 0.25rem; + padding: 0.125em 0.5rem; + margin-left: 0.125em; + margin-right: 0.125em; + font-style: normal; + } + [type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='week'],[type='search'],[type='tel'],[type='time'],[type='color'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-width: 1px; + padding: 0.5rem 0.75rem; + font-size: 1rem; + } + [type='text']:focus,[type='email']:focus,[type='url']:focus,[type='password']:focus,[type='number']:focus,[type='date']:focus,[type='datetime-local']:focus,[type='month']:focus,[type='week']:focus,[type='search']:focus,[type='tel']:focus,[type='time']:focus,[type='color']:focus,[multiple]:focus,textarea:focus,select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000); + border-color: #2563eb; + } + input::-moz-placeholder,textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; + } + input:-ms-input-placeholder,textarea:-ms-input-placeholder { + color: #6b7280; + opacity: 1; + } + input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; + } + select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + color-adjust: exact; + } + [multiple] { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + color-adjust: unset; + } + [type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-width: 1px; + } + [type='radio'] { + border-radius: 100%; + } + [type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + [type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + } + [type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + } + [type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + } + [type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; + } + [type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + } + [type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; + } + [type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; + } + [type='file']:focus { + outline: 1px auto -webkit-focus-ring-color; + } + [type='color'] { + height: 2.4rem; + padding: 2px 3px; + } + [type='range'] { + height: 2.4rem; + } + @media (min-width: 640px) { + [type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='week'],[type='search'],[type='tel'],[type='time'],[type='color'],[multiple],textarea,select { + font-size: .875rem; + line-height: 1.25rem; + } + } + .dark input:-webkit-autofill, .dark input:-webkit-autofill:hover, .dark input:-webkit-autofill:focus, .dark input:-webkit-autofill:active { + transition: background-color 5000s ease-in-out 0s; + -webkit-text-fill-color: #ffffff; + } + .dark input[data-autocompleted] { + background-color: transparent !important; + } + .aspect-h-9 { + --tw-aspect-h: 9; + } + .aspect-w-16 { + position: relative; + padding-bottom: calc(var(--tw-aspect-h) / var(--tw-aspect-w) * 100%); + --tw-aspect-w: 16; + } + .aspect-w-16 > * { + position: absolute; + height: 100%; + width: 100%; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-divide-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-divide-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-divide-x-reverse: 0; + --tw-border-style: solid; + --tw-divide-y-reverse: 0; + --tw-leading: initial; + --tw-font-weight: initial; + --tw-tracking: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-outline-style: solid; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-duration: initial; + --tw-ease: initial; + } + } +} diff --git a/ServiceStack/tests/Blazor/wwwroot/css/highlight.css b/ServiceStack/tests/Blazor/wwwroot/css/highlight.css new file mode 100644 index 00000000000..8332dac213e --- /dev/null +++ b/ServiceStack/tests/Blazor/wwwroot/css/highlight.css @@ -0,0 +1,62 @@ +/* highlight.js - vs.css */ +.hljs {background:white;color:black} +.hljs-comment,.hljs-quote,.hljs-variable{color:#008000} +.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-name,.hljs-tag{color:#00f} +.hljs-string,.hljs-title,.hljs-section,.hljs-attribute,.hljs-literal,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-addition{color:#a31515} +.hljs-deletion,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-meta{color:#2b91af} +.hljs-doctag{color:#808080} +.hljs-attr{color: #f00} +.hljs-symbol,.hljs-bullet,.hljs-link{color:#00b0e8} +.hljs-emphasis{font-style:italic} +.hljs-strong{font-weight:bold} + +/* https://unpkg.com/@highlightjs/cdn-assets/styles/atom-one-dark.min.css */ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34} +.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd} +.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst,.hljs-tag{color:#e06c75} +.hljs-literal{color:#56b6c2} +.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379} +.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66} +.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee} +.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} +.hljs-link{text-decoration:underline} + +/*highlightjs*/ +.hljs, .prose :where(pre):not(:where([class~="not-prose"] *)) .hljs { + color: #e5e7eb !important; + background-color: #282c34 !important; +} +.hljs-comment, .hljs-quote { + color: rgb(148 163 184); /*text-slate-400*/ +} + +pre { + overflow-x: auto; + font-weight: 400; + font-size: .875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: .375rem; + padding: .8571429em 1.1428571em; + max-width: calc(100vw - 1rem); + min-width: fit-content; + background-color: #282c34 !important; +} +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; +} + +/* atom-one-dark */ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34} +.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd} +.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2} +.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379} +.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66} +.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee} +.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b} +.hljs-emphasis{font-style:italic} +.hljs-strong{font-weight:700} +.hljs-link{text-decoration:underline} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/wwwroot/css/typography.css b/ServiceStack/tests/Blazor/wwwroot/css/typography.css new file mode 100644 index 00000000000..382af0df488 --- /dev/null +++ b/ServiceStack/tests/Blazor/wwwroot/css/typography.css @@ -0,0 +1,240 @@ +/* @tailwindcss/typography */ +.prose {color: inherit;max-width: 65ch }.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-lead);font-size: 1.25em;line-height: 1.6;margin-top: 1.2em;margin-bottom: 1.2em }.prose :where(a):not(:where([class~="not-prose"] *)) {color: inherit;text-decoration: underline;font-weight: 500 }.prose :where(a):not(:where([class~="not-prose"] *)):hover {opacity: .8;color: #4b5563 }.prose :where(strong):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600 }.prose :where(ol):not(:where([class~="not-prose"] *)) {list-style-type: decimal;padding-left: 1.625em }.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-alpha }.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-alpha }.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-alpha }.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-alpha }.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-roman }.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-roman }.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-roman }.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-roman }.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) {list-style-type: decimal }.prose :where(ul):not(:where([class~="not-prose"] *)) {list-style-type: disc;padding-left: 1.625em }.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker {font-weight: 400;color: var(--tw-prose-counters) }.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker {color: var(--tw-prose-bullets) }.prose :where(hr):not(:where([class~="not-prose"] *)) {border-color: var(--tw-prose-hr);border-top-width: 1px;margin-top: 3em;margin-bottom: 3em }.prose :where(blockquote):not(:where([class~="not-prose"] *)) {font-weight: 500;font-style: italic;color: var(--tw-prose-quotes);border-left-width: .25rem;border-left-color: var(--tw-prose-quote-borders);quotes: "\201c""\201d""\2018""\2019";margin-top: 1.6em;margin-bottom: 1.6em;padding-left: 1em }.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *)):before {content: open-quote }.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *)):after {content: close-quote }.prose :where(h1):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 800;font-size: 2.25em;margin-top: 0;margin-bottom: .8888889em;line-height: 1.1111111 }.prose :where(h1 strong):not(:where([class~="not-prose"] *)) {font-weight: 900 }.prose :where(h2):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 700;font-size: 1.5em;margin-top: 2em;margin-bottom: 1em;line-height: 1.3333333 }.prose :where(h2 strong):not(:where([class~="not-prose"] *)) {font-weight: 800 }.prose :where(h3):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600;font-size: 1.25em;margin-top: 1.6em;margin-bottom: .6em;line-height: 1.6 }.prose :where(h3 strong):not(:where([class~="not-prose"] *)) {font-weight: 700 }.prose :where(h4):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600;margin-top: 1.5em;margin-bottom: .5em;line-height: 1.5 }.prose :where(h4 strong):not(:where([class~="not-prose"] *)) {font-weight: 700 }.prose :where(figure > *):not(:where([class~="not-prose"] *)) {margin-top: 0;margin-bottom: 0 }.prose :where(figcaption):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-captions);font-size: .875em;line-height: 1.4285714;margin-top: .8571429em }.prose :where(code):not(:where([class~="not-prose"] *)) {color: #3b82f6;font-weight: 400;font-size: .875em;background-color: #eff6ff;border-radius: .25rem;padding: .25em .5rem }.prose :where(code):not(:where([class~="not-prose"] *)):before {content: "" }.prose :where(code):not(:where([class~="not-prose"] *)):after {content: "" }.prose :where(a code):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-links) }.prose :where(pre):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-pre-code);background-color: var(--tw-prose-pre-bg);overflow-x: auto;font-weight: 400;font-size: .875em;line-height: 1.7142857;margin-top: 1.7142857em;margin-bottom: 1.7142857em;border-radius: .375rem;padding: .8571429em 1.1428571em;max-width: calc(100vw - 1rem) }.prose :where(pre code):not(:where([class~="not-prose"] *)) {background-color: transparent;border-width: 0;border-radius: 0;padding: 0;font-weight: inherit;color: inherit;font-size: inherit;font-family: inherit;line-height: inherit }.prose :where(pre code):not(:where([class~="not-prose"] *)):before {content: none }.prose :where(pre code):not(:where([class~="not-prose"] *)):after {content: none }.prose :where(table):not(:where([class~="not-prose"] *)) {width: 100%;table-layout: auto;text-align: left;margin-top: 2em;margin-bottom: 2em;font-size: .875em;line-height: 1.7142857 }.prose :where(thead):not(:where([class~="not-prose"] *)) {border-bottom-width: 1px;border-bottom-color: var(--tw-prose-th-borders) }.prose :where(thead th):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-headings);font-weight: 600;vertical-align: bottom;padding-right: .5714286em;padding-bottom: .5714286em;padding-left: .5714286em }.prose :where(tbody tr):not(:where([class~="not-prose"] *)) {border-bottom-width: 1px;border-bottom-color: var(--tw-prose-td-borders) }.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) {border-bottom-width: 0 }.prose :where(tbody td):not(:where([class~="not-prose"] *)) {vertical-align: baseline;padding: .5714286em }.prose {--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size: 1rem;line-height: 1.75 }.prose :where(p):not(:where([class~="not-prose"] *)) {margin-top: 1.25em;margin-bottom: 1.25em }.prose :where(img):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }.prose :where(video):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }.prose :where(figure):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }.prose :where(h2 code):not(:where([class~="not-prose"] *)) {font-size: .875em }.prose :where(h3 code):not(:where([class~="not-prose"] *)) {font-size: .9em }.prose :where(li):not(:where([class~="not-prose"] *)) {margin-top: .5em;margin-bottom: .5em }.prose :where(ol > li):not(:where([class~="not-prose"] *)) {padding-left: .375em }.prose :where(ul > li):not(:where([class~="not-prose"] *)) {padding-left: .375em }.prose>:where(ul > li p):not(:where([class~="not-prose"] *)) {margin-top: .75em;margin-bottom: .75em }.prose>:where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) {margin-top: 1.25em }.prose>:where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 1.25em }.prose>:where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) {margin-top: 1.25em }.prose>:where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 1.25em }.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"] *)) {margin-top: .75em;margin-bottom: .75em }.prose :where(hr + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }.prose :where(h2 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }.prose :where(h3 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }.prose :where(h4 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)) {padding-left: 0 }.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)) {padding-right: 0 }.prose :where(tbody td:first-child):not(:where([class~="not-prose"] *)) {padding-left: 0 }.prose :where(tbody td:last-child):not(:where([class~="not-prose"] *)) {padding-right: 0 }.prose>:where(:first-child):not(:where([class~="not-prose"] *)) {margin-top: 0 }.prose>:where(:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 0 }.prose :where(b):not(:where([class~="not-prose"] *)) {color: inherit }.prose :where(em):not(:where([class~="not-prose"] *)) {color: inherit } + +.prose :where(img):not(:where([class~="not-prose"] *)) { + max-width: 100%; +} +.prose :where(pre):not(:where([class~="not-prose"] *)) { + background-color: var(--tw-prose-pre-bg) !important; + min-width: fit-content; +} +.prose :where(code):not(:where([class~="not-prose"] *))::before, .prose :where(code):not(:where([class~="not-prose"] *))::after { + content: "" +} +.prose :where(a):not(:where([class~="not-prose"] *)) { + color: inherit; + font-weight: 500; + text-decoration: underline; +} +.prose :where(a):not(:where([class~="not-prose"] *)):hover { + opacity: .8; + color: #4b5563; +} +.prose :where(blockquote):not(:where([class~="not-prose"] *)) { + border-left-style: solid; +} + +.dark .prose :not(:where([class~="not-prose"] *)), .dark .prose :where(td):not(:where([class~="not-prose"] *)) { + color: rgb(209 213 219); /*text-gray-300*/ +} +.dark .prose :where(h1,h2,h3,h4,h5,h6,th):not(:where([class~="not-prose"] *)) { + color: rgb(243 244 246); /*text-gray-100*/ +} +.dark .prose :where(code):not(:where([class~="not-prose"] *)) { + background-color: rgb(30 58 138); /*text-blue-900*/ + color: rgb(243 244 246); /*text-gray-100*/ +} +.dark .prose :where(pre code):not(:where([class~="not-prose"] *)) { + background-color: unset; +} +.prose :where(pre):not(:where([class~="not-prose"] *)) > code { + background-color: transparent; +} + +/* override typography */ +:root { + --content-width: 54rem; +} + +.prose { max-width: var(--content-width); } +.prose :where(code):not(:where([class~="not-prose"] *)) { + width: var(--content-width); + max-width: var(--content-width); +} +.dark .prose :where(a):not(:where([class~="not-prose"] *)):hover { + opacity: .8; + color: rgb(165 180 252); /* text-slate-300 */ +} +.not-prose { max-width: unset; } +.prose-table { max-width: 56rem; width: 56rem; padding-left: 1px; overflow-x: auto; } +.hide-h2+h2 { display:none } +.hljs, .prose :where(pre):not(:where([class~="not-prose"] *)) .hljs { + color: var(--tw-prose-pre-code) !important; + background-color: var(--tw-prose-pre-bg) !important; +} +@media (min-width: 1024px) { + .lg\:prose-xl { + font-size: 1.25rem; + line-height: 1.8; + } + .lg\:prose-xl .not-prose { + font-size: 1rem; + line-height: 1.75; + } + .lg\:prose-xl :where(h3):not(:where([class~="not-prose"] *)) { + font-size: 1.5em; + margin-top: 1.6em; + margin-bottom: 0.6666667em; + line-height: 1.3333333; + } +} + +/* custom containers */ +.copy p, .copy p code { color:#fff } +.sh-copy { max-height: 34px; } +.copied { display: none} +.copying .copied { display: block } +.copying .nocopy { display: none } + +.cp p, .cp p code { margin:0; padding:0 } + +.sh-copy code { font-size: 16px } +.sh-copy p, .sh-copy p code { color: rgb(243 244 246) } +.sh-copy p::before { content:'$ '; color: rgb(156 163 175) } +.sh-copy a { color: rgb(243 244 246) } +.sh-copy a:hover { text-decoration: none } + +/* Custom Info Containers*/ +.custom-block.tip,.custom-block.info,.custom-block.warning,.custom-block.danger { + margin: 1rem 0; + border-left: .5rem solid; + padding: .1rem 1.5rem; + overflow-x: auto; +} +.custom-block.tip { + background-color: #f3f5f7; + border-color: #007bff +} +.custom-block.info { + background-color: #f3f5f7; + border-color: #476582 +} +.custom-block.warning { + border-color: #e7c000; + color: #6b5900; + background-color: #ffe5644d +} +.custom-block.warning .custom-block-title { + color: #b29400 +} +.custom-block.warning a { + color: #2c3e50 +} +.custom-block.danger { + border-color: #c00; + color: #4d0000; + background-color: #ffe6e6 +} +.custom-block.danger .custom-block-title { + color: #900 +} +.custom-block.danger a { + color: #2c3e50 +} +.dark .custom-block { + background: #111827; +} +.custom-block.details { + position: relative; + display: block; + border-radius: 2px; + margin: 1.6em 0; + padding: 1.6em; + background-color: #eee +} +.custom-block.details h4 { + margin-top: 0 +} +.custom-block.details figure:last-child,.custom-block.details p:last-child { + margin-bottom: 0; + padding-bottom: 0 +} +.custom-block.details summary { + outline: none; + cursor: pointer +} +.custom-block-title { + margin-bottom: -.4rem; + font-weight: 600; + text-transform: uppercase; +} +.table tr { + border-top: 1px solid #dfe2e5; +} +.table-striped thead tr, .table-striped tr:nth-child(2n), .table-bordered tr:nth-child(2n) { + background-color: #f6f8fa; +} +.dark .table-striped thead tr, .dark .table-striped tr:nth-child(2n), .dark .table-bordered tr:nth-child(2n) { + background-color: #111827; +} + +.table-striped thead tr th, .table-striped thead tr td { + font-weight: 500; + color: #6b7280; /*text-gray-500*/ +} +.dark .table-striped thead tr th, .dark .table-striped thead tr td { + color: #f3f4f6; +} + +.table th,.table td { + border: 1px solid #dfe2e5; + padding: .6em 1em +} +.dark .table th,.dark .table td { + border: 1px solid #1f2937; +} + +.youtube { + width: 768px; + height: 432px; +} +@media (max-width: 896px) { + .youtube { + width: 100%; + height: auto; + } +} +.prose pre::-webkit-scrollbar, .prose code::-webkit-scrollbar { + width: 8px; + height: 8px; + background: #2d3748; +} +.prose pre::-webkit-scrollbar-thumb, .prose code::-webkit-scrollbar-thumb { + background-color: rgb(100 116 139); +} + +.html-format { + max-width: unset; +} + +.svg-external { + color: #007bff; + background: url("data:image/svg+xml,%3Csvg width='1.25rem' height='1.25rem' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none'%3E%3Cpath d='M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14' stroke='%23007bff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3C/svg%3E") no-repeat bottom right; + padding-right: 1.35rem; +} +.svg-external:hover { + text-decoration: underline; +} + + +.header-anchor { + float: left; + margin-left: -.87em; + padding-right: .23em; + font-weight: 500; + user-select: none; + opacity: 0; + transition: color .25s,opacity .25s; + color: rgb(14 165 233); + text-decoration: none; +} +.header-anchor:before { + content: "#" +} +.header-anchor:hover:before { + color: rgb(14 165 233); + text-decoration: underline; +} +h1:hover .header-anchor, h1 .header-anchor:focus, h2:hover .header-anchor, h2 .header-anchor:focus, h3:hover .header-anchor, h3 .header-anchor:focus, h4:hover .header-anchor, h4 .header-anchor:focus { + opacity: 1; +} \ No newline at end of file diff --git a/ServiceStack/tests/Blazor/wwwroot/favicon.png b/ServiceStack/tests/Blazor/wwwroot/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8422b59695935d180d11d5dbe99653e711097819 GIT binary patch literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~
+ + +@foreach (var column in VisibleColumns) +{ +var allowFiltering = AllowFiltering && column.AllowFiltering && !TextUtils.IsComplexType(column.FieldType) && !column.IsComputed; +var isOpen = column == ShowFilters; + +} + + + +@{ var i = 0; } +@foreach (var item in Items) +{ + +@foreach (var column in VisibleColumns) +{ +@column.CellTemplate(item) +} + +} + +
+@if (!allowFiltering) +{ +
+@column.HeaderTemplate +
+} +else +{ +
+@column.HeaderTemplate + +
+} +
+ + + + +} +@typeparam TValue +@inherits DateTimeInputBase +
+@if (!string.IsNullOrEmpty(UseLabel)) +{ + +} +
+ +@if (HasErrorField) +{ +
+ + +
+} +
+@if (HasErrorField) +{ +

@ErrorFieldMessage

+} +else if (!string.IsNullOrEmpty(UseHelp)) +{ +

@UseHelp

+} +
+@inherits DynamicInputBase +@if (IsSelect) +{ + +} +else if (IsCheckbox) +{ + +} +else if (UseType == "file") +{ +string? value = null; +ICollection? values = null; +UploadedFile? file = null; +List? files = null; +if (ValueObject != null) +{ +value = ValueObject as string; +if (value == null) +{ +values = ValueObject as ICollection; +if (values == null) +{ +if (ValueObject is System.Collections.IEnumerable) +{ +files = ValueObject.ConvertTo>(); +} +else +{ +file = ValueObject.ConvertTo(); +} +} +} +} + +} +else if (UseType == "tag") +{ + +} +else +{ + +} +@inherits AuthBlazorComponentBase + + +@if (!IsAuthenticated) +{ +
+ +
+} +
+@code { +public static string ButtonClasses { get; set; } = "flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"; +} +@inherits ErrorSummaryBase +@if (UseStatus.ShowSummary(UseExcept)) +{ + +} +@code { +public static string Classes { get; set; } = "bg-red-50 dark:bg-red-200 border-l-4 border-red-400 p-4"; +public static string MessageClasses { get; set; } = "text-sm text-red-700 dark:text-red-800"; +} +@using Microsoft.AspNetCore.Components.Forms +@inherits TextInputBase +@implements IDisposable +
+
+@if (!string.IsNullOrEmpty(UseLabel)) +{ + +} +
+UseHelp ?? UseLabel +@{ +var cls = @ClassNames(InputBaseClasses, CssClass(InputValidClasses, InputInvalidClasses)); +} + +@if (HasErrorField) +{ +
+ + +
+} +
+@if (HasErrorField) +{ +

@ErrorFieldMessage

+} +else if (!string.IsNullOrEmpty(UseHelp)) +{ +

@UseHelp

+} +
+@if (!Multiple) +{ +var src = imgSrc(); +if (!string.IsNullOrEmpty(src)) +{ +
+@($"Current +
+} +} +else if (FileList.Count > 0) +{ +
+ +@foreach (var file in FileList) +{ +var filePath = file.FilePath ?? ""; +var src = FileIcons.FilePathUri(filePath)!; + + + + +} +
+
+ +@if (!isDataUri(filePath)) +{ + +@file.FileName + +} +else +{ +@file.FileName +} +
+
+@if (file.ContentLength > 0) +{ + +@TextUtils.FormatBytes(file.ContentLength) + +} +
+
+} +
+@code { +string? imgSrc() +{ +var inputFile = inputFiles?.FirstOrDefault(); +if (inputFile != null) +return inputFile.FilePath; +var filePath = Value ?? Values?.FirstOrDefault() ?? File?.FilePath ?? Files?.FirstOrDefault()?.FilePath; +if (filePath != null) +{ +var resolvedFilePath = BlazorConfig.Instance.AssetsPathResolver(filePath); +return FileIcons.FilePathUri(resolvedFilePath); +} +return null; +} +bool isDataUri(string? src) => src == null ? false : src.StartsWith("data:") || src.StartsWith("blob:"); +string imgCls(string? src) => string.IsNullOrEmpty(src) || src.StartsWith("data:") || src.EndsWith(".svg") +? "" +: "rounded-full object-cover"; +public static string ErrorClasses { get; set; } = "mt-2 text-sm text-red-500"; +public static string LabelClasses { get; set; } = "block text-sm font-medium text-gray-700 dark:text-gray-300"; +public static string InputBaseClasses { get; set; } = "block w-full sm:text-sm rounded-md dark:text-white dark:bg-gray-900"; +public static string InputValidClasses { get; set; } = "block w-full text-sm text-slate-500 dark:text-slate-400 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 dark:file:bg-violet-900 file:text-violet-700 dark:file:text-violet-200 hover:file:bg-violet-100 dark:hover:file:bg-violet-800"; +public static string InputInvalidClasses { get; set; } = "pr-10 border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500"; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +@using Microsoft.AspNetCore.Components.Forms +@typeparam TReq +@inherits BlazorComponentBase +
+ +
+ + + +} +@code { +public static string LabelClasses { get; set; } = "flex justify-center w-full h-32 px-4 transition border-2 border-gray-300 border-dashed rounded-md appearance-none cursor-pointer hover:border-gray-400 focus:outline-none"; +} +@inherits UiComponentLiteBase +

@ChildContent

+@code { +[Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } +public static string Classes { get; set; } = "mb-4 text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50 sm:text-4xl"; +} +@inherits UiComponentLiteBase +

@ChildContent

+@code { +[Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } +public static string Classes { get; set; } = "mb-4 text-3xl md:text-4xl font-bold tracking-tighter leading-tight md:pr-8 whitespace-nowrap text-center pt-8"; +} +@inherits UiComponentLiteBase +

@ChildContent

+@code { +[Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } +public static string Classes { get; set; } = "mb-4 text-2xl font-semibold text-gray-900 dark:text-gray-100 leading-tight"; +} +@inherits UiComponentLiteBase +

@ChildContent

+@code { +[Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } +public static string Classes { get; set; } = "text-xl font-semibold text-gray-900 dark:text-gray-100 leading-tight"; +} +
+@((MarkupString) HtmlUtils.HtmlDump(Value ?? "")) +
+@inherits UiComponentLiteBase +@ChildContent +@code { +[Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } +public static string Classes => GetStyleClass(LinkStyle.Indigo); +[Parameter] public LinkStyle Style { get; set; } = LinkStyle.Indigo; +public static string GetStyleClass(LinkStyle style) => style switch +{ +LinkStyle.Blue => "text-blue-600 dark:text-blue-300 hover:text-blue-500 dark:hover:text-blue-400", +LinkStyle.Red => "text-red-600 dark:text-red-300 hover:text-red-500 dark:hover:text-red-400", +LinkStyle.Green => "text-green-600 dark:text-green-300 hover:text-green-500 dark:hover:text-green-400", +LinkStyle.Sky => "text-sky-600 dark:text-sky-300 hover:text-sky-500 dark:hover:text-sky-400", +_ => "text-indigo-600 dark:text-indigo-300 hover:text-indigo-500 dark:hover:text-indigo-400" +}; +} +@inherits UiComponentBase +@((MarkupString)ToHtml()) +@inherits UiComponentBase +@if (!string.IsNullOrEmpty(prerenderedHtml)) +{ +@((MarkupString)prerenderedHtml) +} +else +{ +
+

+ + + + +@Message +

+
+} +@code { +public static string Classes = "mb-4 text-2xl font-semibold text-gray-900 dark:text-gray-300"; +} +@inherits TextInputBase +@implements IHasJsonApiClient +@if (AppMetadata != null) +{ +
+ +@if (!string.IsNullOrEmpty(UseLabel)) +{ +
+ +@if (!string.IsNullOrEmpty(Value)) +{ +
+@Value + +
+} +
+} +@if (Property.Ref != null) +{ +
+ +
+} +else if (ErrorFieldMessage != null) +{ +

@ErrorFieldMessage

+} +
+ +} +@inherits UiComponentBase + +@typeparam Model +@inherits ModalLookup + +
+
+ +
+ + + + + +
+
+
+@if (apiLoading) +{ +Querying... +} +else if (Results.Count > 0) +{ + + +@(Skip + 1) - @Math.Min(Skip + Results.Count, Total) of @Total + +} +else if (Api != null) +{ +No Results +} +
+
+@if (hasPrefs) +{ +
+ +
+} +@if (filtersCount > 0) +{ +
+ +
+} +
+@if (open == Features.Filters) +{ + +} +@if (errorSummary != null) +{ + +} +else if (apiLoading) +{ + +} +else +{ +
+@if (Results.Count > 0) +{ +
+ + + +@Columns + + + +
+} +
+} +
+
+ +@inherits UiComponentBase +@if (!string.IsNullOrEmpty(Title)) +{ +

+@Title +

+} +
    +@ChildContent +
+@code { +public static string TitleClasses { get; set; } = "text-base font-semibold text-gray-500 dark:text-gray-400"; +public static string ListClasses { get; set; } = "mt-4 divide-y divide-gray-200 dark:divide-gray-800 border-t border-b border-gray-200 dark:border-gray-800"; +} +@inherits UiComponentBase +
  • +
    + + + +
    +
    +

    + + + +@Title + + +

    +

    +@ChildContent +

    +
    +
    + +
    +
  • +@using System.Collections +@using ServiceStack.Text +@{ +void RenderValue(object? value) +{ +if (Format?.Method == null) +{ +@FormatValue(value) +} +else +{ +var str = $"{value}"; +var attrs = (Format.Options != null +? BlazorConfig.Instance.JSParseObject(Format.Options) +: null) ?? new(); +var cls = attrs.Remove("cls", out var oClass) ? oClass as string : null; +if (Format.Method == FormatMethods.IconRounded) +{ +var src = str; + +} +else if (Format.Method == FormatMethods.Icon) +{ +var src = str; + +} +else if (Format.Method == FormatMethods.Currency) +{ +@($"{value.ConvertTo():C}") +} +else if (Format.Method == FormatMethods.Bytes) +{ +@TextUtils.FormatBytes(value.ConvertTo()) +} +else if (Format.Method.StartsWith("link")) +{ +if (Format.Method == FormatMethods.Link) +{ +@str +} +else if (Format.Method == FormatMethods.LinkPhone) +{ +@str +} +else if (Format.Method == FormatMethods.LinkEmail) +{ +var href = str; +if (attrs.Remove("subject", out var oSubject) && oSubject is string subject) +href = href.AddQueryParam("subject", subject.UrlEncode().Replace("+", "%20"), encode: false); +if (attrs.Remove("body", out var oBody) && oBody is string body) +href = href.AddQueryParam("body", body.UrlEncode().Replace("+","%20"), encode:false); +@str +} +} +else if (Format.Method == FormatMethods.Attachment) +{ +var url = str; +var fileName = FileIcons.GetFileName(url)!; +var ext = FileIcons.GetExt(fileName); +var imgSrc = ext == null || FileIcons.CanPreview(url) +? BlazorConfig.Instance.AssetsPathResolver(url) +: FileIcons.IconFallbackSrc(url); + + +@fileName + +} +else if (Format.Method != FormatMethods.Hidden) +{ +@FormatValue(value) +} +} +} +} +@if (TextUtils.IsComplexType(Value?.GetType())) +{ +var useValue = Value; +var e = Value as IEnumerable; +var isCollection = e != null && Value is not IDictionary; +var count = e != null && isCollection && IncludeCount ? EnumerableUtils.Count(e) : 0; + +@if (isCollection) +{ +useValue = EnumerableUtils.FirstOrDefault(e); +@count +} +@if (IncludeIcon) +{ + +} + +@if (isCollection) +{ +@("[ ") +} +@if (!isCollection || count > 0) +{ +@("{ ") +@if (TextUtils.IsComplexType(useValue?.GetType())) +{ +var dict = useValue.ToObjectDictionary(); +var keys = dict.Keys.ToList(); +var len = Math.Min(MaxNestedFields, keys.Count); +for (var i = 0; i < len; i++) +{ +var key = keys[i]; +var val = dict[key]; +var value = FormatValue(val); +var str = TextUtils.Truncate(value, MaxNestedFieldLength); +if (i > 0) +{ +@(", ") +} +{ +@key +@($": {str}") +} +} +if (keys.Count > len) +{ +@("...") +} +} +else +{ +RenderValue(useValue); +} +@(" }") +} +@if (isCollection) +{ +@(" ]") +} + + +} +else +{ +RenderValue(Value); +} +@inherits UiComponentBase +@if (!string.IsNullOrEmpty(href)) +{ + +@ChildContent + +} +else +{ + +} +@code { +public static string BaseClass { get; set; } = "inline-flex justify-center rounded-md border border-transparent py-2 px-4 text-sm font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:ring-offset-black"; +public static string GetStyleClass(ButtonStyle style) => style switch +{ +ButtonStyle.Blue => "text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800", +ButtonStyle.Purple => "text-white bg-purple-600 hover:bg-purple-700 focus:ring-purple-500 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-800", +ButtonStyle.Red => "focus:ring-red-500 text-white bg-red-600 hover:bg-red-700 focus:ring-red-500 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-500", +ButtonStyle.Green => "focus:ring-green-300 text-white bg-green-600 hover:bg-green-700 focus:ring-green-500 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-500", +ButtonStyle.Sky => "focus:ring-sky-300 text-white bg-sky-600 hover:bg-sky-700 focus:ring-sky-500 dark:bg-sky-600 dark:hover:bg-sky-700 dark:focus:ring-sky-500", +ButtonStyle.Cyan => "focus:ring-cyan-300 text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-cyan-500 dark:bg-cyan-600 dark:hover:bg-cyan-700 dark:focus:ring-cyan-500", +_ => "focus:ring-2 focus:ring-offset-2 text-white bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500 dark:bg-indigo-600 dark:hover:bg-indigo-700 dark:focus:ring-indigo-800" +}; +public static string Classes(ButtonStyle style) => BaseClass + " " + GetStyleClass(style); +} +@inherits UiComponentBase +@if (!string.IsNullOrEmpty(href)) +{ + +@ChildContent + +} +else +{ + +} +@code { +public static string BaseClass { get; set; } = "inline-flex justify-center rounded-md border border-gray-300 py-2 px-4 text-sm font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2"; +public static string SecondaryClass { get; set; } = "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-400 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:ring-offset-black"; +public static string Classes => BaseClass + " " + SecondaryClass; +} +@typeparam TValue +@inherits SelectInputBase +
    +@if (!string.IsNullOrEmpty(UseLabel)) +{ + +} + +@if (HasErrorField) +{ +

    @ErrorFieldMessage

    +} +
    +@inherits BlazorComponentBase + +@inherits TextInputBase> +
    +@if (!string.IsNullOrEmpty(UseLabel)) +{ + +} +
    +@{ +var cls = ClassNames(InputBaseClasses, CssClass(InputValidClasses, InputInvalidClasses)); +} + + +
    +} +
    + +
    +
    + +@if (HasErrorField) +{ +
    + +
    +} + +@if (HasErrorField) +{ +

    @ErrorFieldMessage

    +} +else if (!string.IsNullOrEmpty(UseHelp)) +{ +

    @UseHelp

    +} + +@code { +public static string ErrorClasses { get; set; } = "mt-2 text-sm text-red-500"; +public static string LabelClasses { get; set; } = "block text-sm font-medium text-gray-700 dark:text-gray-300"; +public static string InputBaseClasses { get; set; } = "w-full cursor-text flex flex-wrap sm:text-sm rounded-md dark:text-white dark:bg-gray-900 border focus-within:border-transparent focus-within:ring-1 focus-within:outline-none"; +public static string InputValidClasses { get; set; } = "shadow-sm border-gray-300 dark:border-gray-600 focus-within:ring-indigo-500 focus-within:border-indigo-500"; +public static string InputInvalidClasses { get; set; } = "pr-10 border-red-300 text-red-900 placeholder-red-300 focus-within:outline-none focus-within:ring-red-500 focus-within:border-red-500"; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +@typeparam TValue +@inherits TextInputBase +
    +@if (!string.IsNullOrEmpty(UseLabel)) +{ + +} +
    + +} +else +{ + +} +@if (HasErrorField) +{ +
    + +
    +} +
    +@if (HasErrorField) +{ +

    @ErrorFieldMessage

    +} +else if (!string.IsNullOrEmpty(UseHelp)) +{ +

    @UseHelp

    +} +
    +@inherits UiComponentBase +@ChildContent +@code { +public static string Classes { get; set; } = "text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200"; +} +using ServiceStack.Blazor.Components; +namespace ServiceStack.Blazor; +/// +/// For CSS classes used in *.cs so they're exported to tailwind.html +/// +public static class CssDefaults +{ +public static class Grid +{ +public const TableStyle DefaultTableStyle = TableStyle.StripedRows; +public const string GridClass = "mt-4 flex flex-col"; +public const string Grid2Class = "-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"; +public const string Grid3Class = "inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"; +public const string Grid4Class = "overflow-hidden shadow ring-1 ring-black/5 md:rounded-lg"; +public const string TableClass = "min-w-full divide-y divide-gray-200 dark:divide-gray-700"; +public const string TableHeadClass = "bg-gray-50 dark:bg-gray-900"; +public const string TableCellClass = "px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"; +public const string TableHeaderRowClass = "select-none"; +public const string TableHeaderCellClass = "px-6 py-4 text-left text-sm font-medium tracking-wider whitespace-nowrap"; +public const string TableBodyClass = ""; +public static string GetGridClass(TableStyle style = DefaultTableStyle) => GridClass; +public static string GetGrid2Class(TableStyle style = DefaultTableStyle) => style.HasFlag(TableStyle.FullWidth) +? "overflow-x-auto" +: Grid2Class; +public static string GetGrid3Class(TableStyle style = DefaultTableStyle) => style.HasFlag(TableStyle.FullWidth) +? "inline-block min-w-full py-2 align-middle" +: Grid3Class; +public static string GetGrid4Class(TableStyle style = DefaultTableStyle) => style.HasFlag(TableStyle.WhiteBackground) +? "" +: style.HasFlag(TableStyle.FullWidth) +? "overflow-hidden shadow-sm ring-1 ring-black/5" +: Grid4Class; +public static string GetTableClass(TableStyle style = DefaultTableStyle) => style.HasFlag(TableStyle.FullWidth) || style.HasFlag(TableStyle.VerticalLines) +? "min-w-full divide-y divide-gray-300" +: TableClass; +public static string GetTableHeadClass(TableStyle style = DefaultTableStyle) => style.HasFlag(TableStyle.WhiteBackground) +? "" +: TableHeadClass; +public static string GetTableHeaderRowClass(TableStyle style = DefaultTableStyle) => +TableHeaderRowClass + (style.HasFlag(TableStyle.VerticalLines) ? " divide-x divide-gray-200 dark:divide-gray-700" : ""); +public static string GetTableHeaderCellClass(TableStyle style = DefaultTableStyle) => +TableHeaderCellClass + (style.HasFlag(TableStyle.UppercaseHeadings) ? " uppercase" : ""); +public static string GetTableBodyClass(TableStyle style = DefaultTableStyle) => +(style.HasFlag(TableStyle.WhiteBackground) || style.HasFlag(TableStyle.VerticalLines) +? "divide-y divide-gray-200 dark:divide-gray-800" +: "") ++ (style.HasFlag(TableStyle.VerticalLines) +? " bg-white" +: ""); +public static string GetTableRowClass(TableStyle style, int i, bool selected, bool allowSelection) => +(allowSelection ? "cursor-pointer " : "") + +(selected ? "bg-indigo-100 dark:bg-blue-800" : (allowSelection ? "hover:bg-yellow-50 dark:hover:bg-blue-900 " : "") + (style.HasFlag(TableStyle.StripedRows) +? (i % 2 == 0 ? "bg-white dark:bg-black" : "bg-gray-50 dark:bg-gray-800") +: "bg-white dark:bg-black")) + +(style.HasFlag(TableStyle.VerticalLines) ? " divide-x divide-gray-200 dark:divide-gray-700" : ""); +} +public static class Form +{ +public const FormStyle DefaultFormStyle = FormStyle.SlideOver; +public static string GetPanelClass(FormStyle style = FormStyle.SlideOver) => style == FormStyle.Card ? Card.PanelClass : SlideOver.PanelClass; +public static string GetFormClass(FormStyle style = FormStyle.SlideOver) => style == FormStyle.Card ? Card.FormClass : SlideOver.FormClass; +public static string GetHeadingClass(FormStyle style = FormStyle.SlideOver) => style == FormStyle.Card ? Card.HeadingClass : SlideOver.HeadingClass; +public static string GetSubHeadingClass(FormStyle style = FormStyle.SlideOver) => style == FormStyle.Card ? Card.SubHeadingClass : SlideOver.SubHeadingClass; +public const string ButtonsClass = "px-4 py-3 bg-gray-50 dark:bg-gray-900 sm:px-6 flex flex-wrap justify-between"; +public const string LegendClass = "text-base font-medium text-gray-900 dark:text-gray-100 text-center mb-4"; +public static class SlideOver +{ +public const string PanelClass = "pointer-events-auto w-screen xl:max-w-3xl md:max-w-xl max-w-lg"; +public const string FormClass = "flex h-full flex-col divide-y divide-gray-200 dark:divide-gray-700 shadow-xl bg-white dark:bg-black"; +public const string TitlebarClass = "bg-gray-50 dark:bg-gray-900 px-4 py-6 sm:px-6"; +public const string HeadingClass = "text-lg font-medium text-gray-900 dark:text-gray-100"; +public const string SubHeadingClass = "mt-1 text-sm text-gray-500 dark:text-gray-400"; +public const string CloseButtonClass = "cursor-pointer rounded-md bg-gray-50 dark:bg-gray-900 text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:ring-offset-black"; +public static DataTransition SlideOverTransition = new DataTransition( +entering: new(@class: "transform transition ease-in-out duration-500 sm:duration-700", from: "translate-x-full", to: "translate-x-0"), +leaving: new(@class: "transform transition ease-in-out duration-500 sm:duration-700", from: "translate-x-0", to: "translate-x-full"), +visible: false); +} +public static class Card +{ +public const string PanelClass = "shadow sm:overflow-hidden sm:rounded-md"; +public const string FormClass = "space-y-6 bg-white dark:bg-black py-6 px-4 sm:p-6"; +public const string HeadingClass = "text-lg font-medium leading-6 text-gray-900 dark:text-gray-100"; +public const string SubHeadingClass = "mt-1 text-sm text-gray-500 dark:text-gray-400"; +} +} +public static class Modal +{ +public const string SizeClass = "sm:max-w-prose lg:max-w-screen-md xl:max-w-screen-lg 2xl:max-w-screen-xl sm:w-full"; +} +public static class SlideOver +{ +public const string SlideOverClass = "relative z-10"; +public const string DialogClass = "pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16"; +public const string PanelClass = "pointer-events-auto w-screen xl:max-w-3xl md:max-w-xl max-w-lg"; +public const string FormClass = "flex h-full flex-col divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-black shadow-xl"; +public const string TitlebarClass = "bg-gray-50 dark:bg-gray-900 p-3 sm:p-6"; +public const string HeadingClass = "text-lg font-medium text-gray-900 dark:text-gray-50"; +public const string CloseButtonClass = "cursor-pointer rounded-md bg-gray-50 dark:bg-black text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:ring-offset-black"; +public const string TransitionClass = "transform transition ease-in-out duration-500 sm:duration-700"; +} +public static class HtmlFormat +{ +public const string Class = "prose html-format"; +} +public static class PreviewFormat +{ +public const string Class = "flex items-center"; +public const string IconClass = "w-6 h-6"; +public const string IconRoundedClass = "w-8 h-8 rounded-full"; +public const string ValueIconClass = "w-5 h-5 mr-1"; +} +public static string ToBreakpointCellClass(this Breakpoint breakpoint) => breakpoint switch +{ +// Use full class names so tailwindcss can find it +Breakpoint.ExtraSmall => "xs:table-cell", +Breakpoint.Small => "sm:table-cell", +Breakpoint.Medium => "md:table-cell", +Breakpoint.Large => "lg:table-cell", +Breakpoint.ExtraLarge => "xl:table-cell", +Breakpoint.ExtraLarge2x => "2xl:table-cell", +_ => throw new NotSupportedException(), +}; +} +public enum Breakpoint +{ +ExtraSmall, +Small, +Medium, +Large, +ExtraLarge, +ExtraLarge2x, +} +/* CSS Classes for Generic Components */ +public static class TextInput +{ +public static string ErrorClasses { get; set; } = "mt-2 text-sm text-red-500"; +public static string LabelClasses { get; set; } = "block text-sm font-medium text-gray-700 dark:text-gray-300"; +public static string InputBaseClasses { get; set; } = "block w-full sm:text-sm rounded-md dark:text-white dark:bg-gray-900 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:shadow-none"; +public static string InputValidClasses { get; set; } = "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-600"; +public static string InputInvalidClasses { get; set; } = "pr-10 border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500"; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +public static class DateTimeInput +{ +public static string ErrorClasses { get; set; } = TextInput.ErrorClasses; +public static string LabelClasses { get; set; } = TextInput.LabelClasses; +public static string InputBaseClasses { get; set; } = TextInput.InputBaseClasses; +public static string InputValidClasses { get; set; } = TextInput.InputValidClasses; +public static string InputInvalidClasses { get; set; } = TextInput.InputInvalidClasses; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +public static class TextAreaInput +{ +public static string ErrorClasses { get; set; } = TextInput.ErrorClasses; +public static string LabelClasses { get; set; } = TextInput.LabelClasses; +public static string InputBaseClasses { get; set; } = "shadow-sm block w-full sm:text-sm rounded-md dark:text-white dark:bg-gray-900 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:shadow-none"; +public static string InputValidClasses { get; set; } = "text-gray-900 focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-600"; +public static string InputInvalidClasses { get; set; } = "text-red-900 focus:ring-red-500 focus:border-red-500 border-red-300"; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +public static class SelectInput +{ +public static string ErrorClasses { get; set; } = TextInput.ErrorClasses; +public static string LabelClasses { get; set; } = TextInput.LabelClasses; +public static string InputBaseClasses { get; set; } = "mt-1 block w-full pl-3 pr-10 py-2 text-base focus:outline-none border-gray-300 sm:text-sm rounded-md dark:text-white dark:bg-gray-900 dark:border-gray-600 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:shadow-none"; +public static string InputValidClasses { get; set; } = "shadow-sm text-gray-900 focus:ring-indigo-500 focus:border-indigo-500"; +public static string InputInvalidClasses { get; set; } = "text-red-900 focus:ring-red-500 focus:border-red-500"; +public static string InputClasses { get; set; } = InputBaseClasses + " " + InputValidClasses; +} +public static class CheckboxInput +{ +public static string ErrorClasses { get; set; } = "text-gray-500"; +public static string LabelClasses { get; set; } = "font-medium text-gray-700 dark:text-gray-300"; +public static string InputClasses { get; set; } = "focus:ring-indigo-500 h-4 w-4 text-indigo-600 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:ring-offset-black"; +} From 1515a79be561b7b270c8a3df9003dd2c86f25761 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 24 Jan 2026 10:18:28 +0800 Subject: [PATCH 106/140] Update ServiceStack.sln --- ServiceStack/src/ServiceStack.sln | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ServiceStack/src/ServiceStack.sln b/ServiceStack/src/ServiceStack.sln index 6d79d7d8ad1..b0a59254dee 100644 --- a/ServiceStack/src/ServiceStack.sln +++ b/ServiceStack/src/ServiceStack.sln @@ -140,6 +140,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiScalar", "..\tests\O EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.OpenApi.Microsoft", "ServiceStack.OpenApi.Microsoft\ServiceStack.OpenApi.Microsoft.csproj", "{03EDA213-6C39-4DB9-BF03-DEB67BDF3286}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazor", "..\tests\Blazor\Blazor.csproj", "{A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -864,6 +866,18 @@ Global {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|Mixed Platforms.Build.0 = Release|Any CPU {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|x86.ActiveCfg = Release|Any CPU {03EDA213-6C39-4DB9-BF03-DEB67BDF3286}.Release|x86.Build.0 = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|x86.ActiveCfg = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Debug|x86.Build.0 = Debug|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|Any CPU.Build.0 = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|x86.ActiveCfg = Release|Any CPU + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -905,6 +919,7 @@ Global {0E5DB4A9-89A0-4F0A-A14D-E776FE4C21D5} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} {ABE5BD4B-D33A-4E1C-AFC1-1087323491DD} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} {D0024195-711E-4B32-8F39-40D34BFDB57B} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} + {A294C9E9-B31E-4B62-B4CB-3AC0069EAEDE} = {4EBD29DD-21A1-47B5-80DB-F9D58C91BED5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55441DE1-559F-4064-BB8F-907A9E898A6C} From bf020aced4cd0247afcecfc8209fd70ed71d3848 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 24 Jan 2026 10:29:34 +0800 Subject: [PATCH 107/140] Only use HtmlEncoded strings for variables embedded in HTML attrs --- ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs | 10 +++++++++- .../src/ServiceStack/Templates/HtmlFormat.html | 2 +- .../src/ServiceStack/Templates/HtmlFormatLegacy.html | 12 ++++++------ .../src/ServiceStack/Templates/HtmlTemplates.cs | 9 ++++++--- ServiceStack/src/ServiceStack/Templates/auth.html | 4 ++-- ServiceStack/src/ServiceStack/Templates/login.html | 12 ++++++------ .../NorthwindAuto/wwwroot/Templates/HtmlFormat.html | 2 +- 7 files changed, 31 insertions(+), 20 deletions(-) diff --git a/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs b/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs index fe8f67120ab..5f085967b4a 100644 --- a/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs +++ b/ServiceStack/src/ServiceStack/Formats/HtmlFormat.cs @@ -138,6 +138,7 @@ public async Task SerializeToStreamAsync(IRequest req, object response, Stream o .Replace("${MvcIncludes}", MiniProfiler.Profiler.RenderIncludes().ToString()) .Replace("${Header}", string.Format(HtmlTitleFormat, requestName, now)) .Replace("${ServiceUrl}", EncodeForJavaScriptString(url)) + .Replace("${HtmlServiceUrl}", EncodeForHtmlAttribute(url)) .Replace("${Humanize}", Humanize.ToString().ToLower()) ; } @@ -152,13 +153,20 @@ public static string ReplaceTokens(string html, IRequest req) html = html .Replace("${BaseUrl}", EncodeForJavaScriptString(req.GetBaseUrl().WithTrailingSlash())) + .Replace("${HtmlBaseUrl}", EncodeForHtmlAttribute(req.GetBaseUrl().WithTrailingSlash())) .Replace("${AuthRedirect}", EncodeForJavaScriptString(req.ResolveAbsoluteUrl(HostContext.AppHost.GetPlugin()?.HtmlRedirect))) .Replace("${AllowOrigins}", EncodeForJavaScriptString(HostContext.AppHost.GetPlugin()?.AllowOriginWhitelist?.Join(";"))) .Replace("${NoProfileImgUrl}", EncodeForJavaScriptString(req.TryResolve()?.GetProfileUrl(null)) ?? JwtClaimTypes.DefaultProfileUrl) ; return html; } - + + /// + /// Encodes a string so it can be safely embedded within HTML attributes. + /// This process ensures that special characters are properly escaped to prevent XSS vulnerabilities. + /// + public static string EncodeForHtmlAttribute(string input) => string.IsNullOrEmpty(input) ? "" : input.HtmlEncode(); + /// /// Encodes a string so it can be safely embedded within double-quoted JavaScript strings. /// Handles escape sequences, Unicode characters, and line breaks. diff --git a/ServiceStack/src/ServiceStack/Templates/HtmlFormat.html b/ServiceStack/src/ServiceStack/Templates/HtmlFormat.html index 79ee4618363..2feec0bf69e 100644 --- a/ServiceStack/src/ServiceStack/Templates/HtmlFormat.html +++ b/ServiceStack/src/ServiceStack/Templates/HtmlFormat.html @@ -4,7 +4,7 @@ ${Title} - + - \ No newline at end of file + diff --git a/ServiceStack/src/ServiceStack/Templates/HtmlTemplates.cs b/ServiceStack/src/ServiceStack/Templates/HtmlTemplates.cs index 3f9c13c5f36..b39212adeec 100644 --- a/ServiceStack/src/ServiceStack/Templates/HtmlTemplates.cs +++ b/ServiceStack/src/ServiceStack/Templates/HtmlTemplates.cs @@ -1,4 +1,5 @@ using System.IO; +using ServiceStack; namespace ServiceStack.Templates; @@ -35,8 +36,10 @@ public static string GetMetadataDebugTemplate() public static string GetLoginTemplate() { var appHost = HostContext.AppHost; + var baseUrl = appHost.ResolveStaticBaseUrl(); return LoadTemplate("login.html") - .Replace("${BaseUrl}", appHost.ResolveStaticBaseUrl()) + .Replace("${BaseUrl}", Formats.HtmlFormat.EncodeForJavaScriptString(baseUrl)) + .Replace("${HtmlBaseUrl}", Formats.HtmlFormat.EncodeForHtmlAttribute(baseUrl)) .Replace("${ServiceName}", appHost.ServiceName); } @@ -60,7 +63,7 @@ private static string LoadTemplate(string templateName) public static string GetHtmlRedirectTemplate(string url) { var html = LoadTemplate("Redirect.html"); - return html.Replace("{Url}", url); + return html.Replace("{Url}", Formats.HtmlFormat.EncodeForJavaScriptString(url)); } public static string Format(string template, params object[] args) @@ -71,4 +74,4 @@ public static string Format(string template, params object[] args) } return template; } -} \ No newline at end of file +} diff --git a/ServiceStack/src/ServiceStack/Templates/auth.html b/ServiceStack/src/ServiceStack/Templates/auth.html index 653efc4b00a..6342e75d876 100644 --- a/ServiceStack/src/ServiceStack/Templates/auth.html +++ b/ServiceStack/src/ServiceStack/Templates/auth.html @@ -3,7 +3,7 @@ Authenticated Session - +