Replace raw environment variable reads with IConfiguration in backend

This commit is contained in:
Aaron Po
2026-08-22 20:26:13 -04:00
parent 9e9fc98d33
commit c514eb9814
17 changed files with 291 additions and 100 deletions

View File

@@ -32,7 +32,7 @@ public class JwtAuthenticationHandler(
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Use the same access-token secret source as TokenService to avoid mismatched validation.
string? secret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET");
string? secret = configuration["ACCESS_TOKEN_SECRET"];
if (string.IsNullOrWhiteSpace(secret))
secret = configuration["Jwt:SecretKey"];

View File

@@ -2,6 +2,8 @@ using System.Text;
using System.Text.Json;
using FluentAssertions;
using Infrastructure.Jwt;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Reqnroll;
namespace API.Specs.Steps;
@@ -19,11 +21,8 @@ public class AuthSteps(ScenarioContext scenario)
private const string PreviousAccessTokenKey = "previousAccessToken";
private const string PreviousRefreshTokenKey = "previousRefreshToken";
private HttpClient GetClient()
private TestApiFactory GetFactory()
{
if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client))
return client;
TestApiFactory? factory = scenario.TryGetValue<TestApiFactory>(
FactoryKey,
out TestApiFactory? f
@@ -31,16 +30,28 @@ public class AuthSteps(ScenarioContext scenario)
? f
: new TestApiFactory();
scenario[FactoryKey] = factory;
return factory;
}
client = factory.CreateClient();
private HttpClient GetClient()
{
if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client))
return client;
client = GetFactory().CreateClient();
scenario[ClientKey] = client;
return client;
}
private static string GetRequiredEnvVar(string name)
/// <summary>
/// Reads a required configuration value from the running <see cref="TestApiFactory" />'s own
/// <see cref="IConfiguration" />, so forged tokens are always signed with the exact secret the
/// app under test resolved -- rather than assuming this process's environment variables match it.
/// </summary>
private string GetRequiredConfigValue(string key)
{
return Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException($"{name} environment variable is not set");
return GetFactory().Services.GetRequiredService<IConfiguration>()[key]
?? throw new InvalidOperationException($"{key} configuration value is not set");
}
private static string GenerateJwtToken(
@@ -420,7 +431,7 @@ public class AuthSteps(ScenarioContext scenario)
? user
: throw new InvalidOperationException("registered username not found in scenario");
string secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET");
string secret = GetRequiredConfigValue("ACCESS_TOKEN_SECRET");
scenario["accessToken"] = GenerateJwtToken(
userId,
username,
@@ -439,7 +450,7 @@ public class AuthSteps(ScenarioContext scenario)
? user
: throw new InvalidOperationException("registered username not found in scenario");
string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
string secret = GetRequiredConfigValue("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
@@ -458,7 +469,7 @@ public class AuthSteps(ScenarioContext scenario)
? user
: throw new InvalidOperationException("registered username not found in scenario");
string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
string secret = GetRequiredConfigValue("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,

View File

@@ -11,6 +11,11 @@
<ItemGroup>
<PackageReference Include="dbup" Version="5.0.41" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference
Include="Microsoft.Extensions.Configuration.EnvironmentVariables"
Version="9.0.0"
/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />

View File

@@ -4,13 +4,14 @@ using DbUp;
using DbUp.Engine;
using Infrastructure.Sql;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace Database.Migrations;
/// <summary>
/// Entry point for the database migration runner. Reads connection details from environment
/// variables, optionally drops and recreates the target database, and applies all SQL
/// migration scripts embedded in this assembly using DbUp.
/// Entry point for the database migration runner. Reads connection details from configuration
/// (backed by environment variables), optionally drops and recreates the target database, and
/// applies all SQL migration scripts embedded in this assembly using DbUp.
/// </summary>
public static class Program
{
@@ -27,7 +28,10 @@ public static class Program
try
{
DatabaseSettings settings = DatabaseSettings.FromEnvironment();
IConfiguration configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
DatabaseSettings settings = DatabaseSettings.FromConfiguration(configuration);
if (settings.ClearDatabase)
{
@@ -159,28 +163,28 @@ public static class Program
/// <summary>
/// Builds the settings from the <c>DB_SERVER</c>, <c>DB_NAME</c>, <c>DB_USER</c>,
/// <c>DB_PASSWORD</c>, <c>DB_TRUST_SERVER_CERTIFICATE</c>, and <c>CLEAR_DATABASE</c>
/// environment variables.
/// configuration values.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when a required environment variable is not set.
/// Thrown when a required configuration value is not set.
/// </exception>
public static DatabaseSettings FromEnvironment()
public static DatabaseSettings FromConfiguration(IConfiguration configuration)
{
string databaseName =
Environment.GetEnvironmentVariable("DB_NAME")
configuration["DB_NAME"]
?? throw new InvalidOperationException(
"The DB_NAME environment variable is not set."
);
bool clearDatabase = string.Equals(
Environment.GetEnvironmentVariable("CLEAR_DATABASE"),
configuration["CLEAR_DATABASE"],
"true",
StringComparison.OrdinalIgnoreCase
);
return new DatabaseSettings(
SqlConnectionStringHelper.BuildConnectionString(databaseName),
SqlConnectionStringHelper.BuildMasterConnectionString(),
SqlConnectionStringHelper.BuildConnectionString(configuration, databaseName),
SqlConnectionStringHelper.BuildMasterConnectionString(configuration),
databaseName,
clearDatabase
);

View File

@@ -1,6 +1,7 @@
#region
using Infrastructure.Sql;
using Microsoft.Extensions.Configuration;
#endregion
@@ -8,7 +9,9 @@ namespace Database.Seed.DatabaseHelpers;
public class ConnectionStrings
{
public static string SqlServerConnectionString => SqlConnectionStringHelper.BuildConnectionString();
public static string GetSqlServerConnectionString(IConfiguration configuration) =>
SqlConnectionStringHelper.BuildConnectionString(configuration);
public static string SqliteConnectionString =>
"Data Source=SeedData/biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite";
}

View File

@@ -22,9 +22,12 @@ static async Task<int> RunAsync()
{
try
{
IConfiguration configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
ServiceCollection services = [];
services.AddSingleton<IConfiguration>(
new ConfigurationBuilder().Build());
services.AddSingleton(configuration);
services.AddInfrastructureSql();
services.AddFeaturesBreweries();
services.AddFeaturesLocations();

View File

@@ -7,6 +7,7 @@ using Features.Auth.Tests.TestSupport;
using FluentAssertions;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Moq;
namespace Features.Auth.Tests.Services;
@@ -22,21 +23,23 @@ public class TokenServiceRefreshTests
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_userManagerMock = UserManagerMockFactory.Create();
// Set environment variables for tokens
Environment.SetEnvironmentVariable(
"ACCESS_TOKEN_SECRET",
"test-access-secret-that-is-very-long-1234567890"
);
Environment.SetEnvironmentVariable(
"REFRESH_TOKEN_SECRET",
"test-refresh-secret-that-is-very-long-1234567890"
);
Environment.SetEnvironmentVariable(
"CONFIRMATION_TOKEN_SECRET",
"test-confirmation-secret-that-is-very-long-1234567890"
);
IConfiguration configuration = new ConfigurationBuilder()
.AddInMemoryCollection(
new Dictionary<string, string?>
{
["ACCESS_TOKEN_SECRET"] = "test-access-secret-that-is-very-long-1234567890",
["REFRESH_TOKEN_SECRET"] = "test-refresh-secret-that-is-very-long-1234567890",
["CONFIRMATION_TOKEN_SECRET"] =
"test-confirmation-secret-that-is-very-long-1234567890",
}
)
.Build();
_tokenService = new TokenService(_tokenInfraMock.Object, _userManagerMock.Object);
_tokenService = new TokenService(
_tokenInfraMock.Object,
_userManagerMock.Object,
configuration
);
}
[Fact]

View File

@@ -5,6 +5,7 @@ using Features.Auth.Services;
using Features.Auth.Tests.TestSupport;
using FluentAssertions;
using Infrastructure.Jwt;
using Microsoft.Extensions.Configuration;
using Moq;
namespace Features.Auth.Tests.Services;
@@ -18,23 +19,22 @@ public class TokenServiceValidationTests
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
// Set environment variables for tokens
Environment.SetEnvironmentVariable(
"ACCESS_TOKEN_SECRET",
"test-access-secret-that-is-very-long-1234567890"
);
Environment.SetEnvironmentVariable(
"REFRESH_TOKEN_SECRET",
"test-refresh-secret-that-is-very-long-1234567890"
);
Environment.SetEnvironmentVariable(
"CONFIRMATION_TOKEN_SECRET",
"test-confirmation-secret-that-is-very-long-1234567890"
);
IConfiguration configuration = new ConfigurationBuilder()
.AddInMemoryCollection(
new Dictionary<string, string?>
{
["ACCESS_TOKEN_SECRET"] = "test-access-secret-that-is-very-long-1234567890",
["REFRESH_TOKEN_SECRET"] = "test-refresh-secret-that-is-very-long-1234567890",
["CONFIRMATION_TOKEN_SECRET"] =
"test-confirmation-secret-that-is-very-long-1234567890",
}
)
.Build();
_tokenService = new TokenService(
_tokenInfraMock.Object,
UserManagerMockFactory.Create().Object
UserManagerMockFactory.Create().Object,
configuration
);
}

View File

@@ -4,12 +4,13 @@ using Domain.Exceptions;
using Features.Auth.Identity;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
namespace Features.Auth.Services;
/// <summary>
/// Default implementation of <see cref="ITokenService" /> that generates and validates JWTs
/// for access, refresh, and confirmation flows using secrets read from environment variables.
/// for access, refresh, and confirmation flows using secrets read from configuration.
/// </summary>
public class TokenService : ITokenService
{
@@ -22,30 +23,31 @@ public class TokenService : ITokenService
/// <summary>Initializes a new instance of the <see cref="TokenService" /> class.</summary>
/// <exception cref="InvalidOperationException">
/// Thrown when any of the <c>ACCESS_TOKEN_SECRET</c>, <c>REFRESH_TOKEN_SECRET</c>, or
/// <c>CONFIRMATION_TOKEN_SECRET</c> environment variables are not set.
/// <c>CONFIRMATION_TOKEN_SECRET</c> configuration values are not set.
/// </exception>
public TokenService(
ITokenInfrastructure tokenInfrastructure,
UserManager<ApplicationUser> userManager
UserManager<ApplicationUser> userManager,
IConfiguration configuration
)
{
_tokenInfrastructure = tokenInfrastructure;
_userManager = userManager;
_accessTokenSecret =
Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET")
configuration["ACCESS_TOKEN_SECRET"]
?? throw new InvalidOperationException(
"ACCESS_TOKEN_SECRET environment variable is not set"
);
_refreshTokenSecret =
Environment.GetEnvironmentVariable("REFRESH_TOKEN_SECRET")
configuration["REFRESH_TOKEN_SECRET"]
?? throw new InvalidOperationException(
"REFRESH_TOKEN_SECRET environment variable is not set"
);
_confirmationTokenSecret =
Environment.GetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET")
configuration["CONFIRMATION_TOKEN_SECRET"]
?? throw new InvalidOperationException(
"CONFIRMATION_TOKEN_SECRET environment variable is not set"
);

View File

@@ -6,6 +6,13 @@
<RootNamespace>Features.Emails</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="10.0.1"
/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email.Templates\Infrastructure.Email.Templates.csproj" />

View File

@@ -1,5 +1,6 @@
using Infrastructure.Email;
using Infrastructure.Email.Templates.Rendering;
using Microsoft.Extensions.Configuration;
namespace Features.Emails.Services;
@@ -8,18 +9,19 @@ namespace Features.Emails.Services;
/// </summary>
public class EmailDispatcher(
IEmailProvider emailProvider,
IEmailTemplateProvider emailTemplateProvider
IEmailTemplateProvider emailTemplateProvider,
IConfiguration configuration
) : IEmailDispatcher
{
/// <summary>
/// The base URL of the website, used to build confirmation links. Read from the
/// <c>WEBSITE_BASE_URL</c> environment variable.
/// <c>WEBSITE_BASE_URL</c> configuration value.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown at type initialization time when the <c>WEBSITE_BASE_URL</c> environment variable is not set.
/// Thrown when the <c>WEBSITE_BASE_URL</c> configuration value is not set.
/// </exception>
private static readonly string WebsiteBaseUrl =
Environment.GetEnvironmentVariable("WEBSITE_BASE_URL")
private string WebsiteBaseUrl =>
configuration["WEBSITE_BASE_URL"]
?? throw new InvalidOperationException("WEBSITE_BASE_URL environment variable is not set");
/// <inheritdoc />

View File

@@ -8,5 +8,9 @@
<ItemGroup>
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="10.0.1"
/>
</ItemGroup>
</Project>

View File

@@ -1,12 +1,13 @@
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Configuration;
using MimeKit;
namespace Infrastructure.Email;
/// <summary>
/// SMTP email service implementation using MailKit.
/// Configured via environment variables.
/// Configured via application configuration (backed by environment variables).
/// </summary>
public class SmtpEmailProvider : IEmailProvider
{
@@ -20,35 +21,35 @@ public class SmtpEmailProvider : IEmailProvider
/// <summary>
/// Initializes a new instance of the <see cref="SmtpEmailProvider" /> class, reading SMTP configuration
/// from environment variables (<c>SMTP_HOST</c>, <c>SMTP_PORT</c>, <c>SMTP_USERNAME</c>,
/// <c>SMTP_PASSWORD</c>, <c>SMTP_USE_SSL</c>, <c>SMTP_FROM_EMAIL</c>, <c>SMTP_FROM_NAME</c>).
/// (<c>SMTP_HOST</c>, <c>SMTP_PORT</c>, <c>SMTP_USERNAME</c>, <c>SMTP_PASSWORD</c>, <c>SMTP_USE_SSL</c>,
/// <c>SMTP_FROM_EMAIL</c>, <c>SMTP_FROM_NAME</c>) from <paramref name="configuration" />.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when <c>SMTP_HOST</c> or <c>SMTP_FROM_EMAIL</c> is not set, or when <c>SMTP_PORT</c> is not a valid integer.
/// </exception>
public SmtpEmailProvider()
public SmtpEmailProvider(IConfiguration configuration)
{
_host =
Environment.GetEnvironmentVariable("SMTP_HOST")
configuration["SMTP_HOST"]
?? throw new InvalidOperationException("SMTP_HOST environment variable is not set");
string portString = Environment.GetEnvironmentVariable("SMTP_PORT") ?? "587";
string portString = configuration["SMTP_PORT"] ?? "587";
if (!int.TryParse(portString, out _port))
throw new InvalidOperationException($"SMTP_PORT '{portString}' is not a valid integer");
_username = Environment.GetEnvironmentVariable("SMTP_USERNAME");
_password = Environment.GetEnvironmentVariable("SMTP_PASSWORD");
_username = configuration["SMTP_USERNAME"];
_password = configuration["SMTP_PASSWORD"];
string useSslString = Environment.GetEnvironmentVariable("SMTP_USE_SSL") ?? "true";
string useSslString = configuration["SMTP_USE_SSL"] ?? "true";
_useSsl = bool.Parse(useSslString);
_fromEmail =
Environment.GetEnvironmentVariable("SMTP_FROM_EMAIL")
configuration["SMTP_FROM_EMAIL"]
?? throw new InvalidOperationException(
"SMTP_FROM_EMAIL environment variable is not set"
);
_fromName = Environment.GetEnvironmentVariable("SMTP_FROM_NAME") ?? "The Biergarten";
_fromName = configuration["SMTP_FROM_NAME"] ?? "The Biergarten";
}
/// <inheritdoc/>

View File

@@ -20,21 +20,21 @@ public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlCon
/// <summary>
/// Resolves the SQL Server connection string, preferring (in order): the <c>DB_CONNECTION_STRING</c>
/// environment variable, a connection string built from individual <c>DB_*</c> environment variables,
/// configuration value, a connection string built from individual <c>DB_*</c> configuration values,
/// and finally the <c>"Default"</c> connection string from configuration.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no connection string can be resolved from any source.</exception>
private static string GetConnectionString(IConfiguration configuration)
{
// Check for full connection string first
string? fullConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
string? fullConnectionString = configuration["DB_CONNECTION_STRING"];
if (!string.IsNullOrEmpty(fullConnectionString))
return fullConnectionString;
// Try to build from individual environment variables (preferred method for Docker)
// Try to build from individual configuration values (preferred method for Docker)
try
{
return SqlConnectionStringHelper.BuildConnectionString();
return SqlConnectionStringHelper.BuildConnectionString(configuration);
}
catch (InvalidOperationException)
{

View File

@@ -1,23 +1,29 @@
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace Infrastructure.Sql;
/// <summary>
/// Helper for building SQL Server connection strings from environment variables.
/// Helper for building SQL Server connection strings from configuration
/// (backed by the <c>DB_*</c> environment variables).
/// </summary>
public static class SqlConnectionStringHelper
{
/// <summary>
/// Builds a SQL Server connection string from environment variables.
/// Builds a SQL Server connection string from configuration.
/// </summary>
/// <remarks>Throws <see cref="InvalidOperationException" /> if DB_SERVER, DB_NAME, DB_USER, or DB_PASSWORD is unset.</remarks>
/// <param name="databaseName">If null, falls back to the DB_NAME environment variable.</param>
public static string BuildConnectionString(string? databaseName = null)
/// <param name="configuration">The configuration to read the <c>DB_*</c> values from.</param>
/// <param name="databaseName">If null, falls back to the DB_NAME configuration value.</param>
public static string BuildConnectionString(
IConfiguration configuration,
string? databaseName = null
)
{
string server = Required("DB_SERVER");
string dbName = databaseName ?? Required("DB_NAME");
string user = Required("DB_USER");
string password = Required("DB_PASSWORD");
string server = Required(configuration, "DB_SERVER");
string dbName = databaseName ?? Required(configuration, "DB_NAME");
string user = Required(configuration, "DB_USER");
string password = Required(configuration, "DB_PASSWORD");
SqlConnectionStringBuilder builder = new()
{
@@ -25,7 +31,7 @@ public static class SqlConnectionStringHelper
InitialCatalog = dbName,
UserID = user,
Password = password,
TrustServerCertificate = ResolveTrustServerCertificate(),
TrustServerCertificate = ResolveTrustServerCertificate(configuration),
Encrypt = true,
};
@@ -33,29 +39,27 @@ public static class SqlConnectionStringHelper
}
/// <summary>
/// Builds a connection string to the master database using environment variables.
/// Builds a connection string to the master database using configuration.
/// </summary>
public static string BuildMasterConnectionString()
public static string BuildMasterConnectionString(IConfiguration configuration)
{
return BuildConnectionString("master");
return BuildConnectionString(configuration, "master");
}
/// <summary>
/// Resolves the <c>DB_TRUST_SERVER_CERTIFICATE</c> environment variable, defaulting to
/// Resolves the <c>DB_TRUST_SERVER_CERTIFICATE</c> configuration value, defaulting to
/// <c>true</c> when it is unset or not a valid boolean.
/// </summary>
private static bool ResolveTrustServerCertificate()
private static bool ResolveTrustServerCertificate(IConfiguration configuration)
{
return !bool.TryParse(
Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE"),
configuration["DB_TRUST_SERVER_CERTIFICATE"],
out bool trustServerCertificate
)
|| trustServerCertificate;
}
private static string Required(string variableName) =>
Environment.GetEnvironmentVariable(variableName)
?? throw new InvalidOperationException(
$"The {variableName} environment variable is not set."
);
private static string Required(IConfiguration configuration, string key) =>
configuration[key]
?? throw new InvalidOperationException($"The {key} environment variable is not set.");
}

View File

@@ -0,0 +1,71 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../account.js")
type Info = GetInfo<{
file: "routes/account.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/account";
module: typeof import("../account.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// ServerHydrateFallback
export type ServerHydrateFallbackProps = Annotations["ServerHydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ServerComponent
export type ServerComponentProps = Annotations["ServerComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
// ServerErrorBoundary
export type ServerErrorBoundaryProps = Annotations["ServerErrorBoundaryProps"];
}

View File

@@ -0,0 +1,71 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../brewery-detail.js")
type Info = GetInfo<{
file: "routes/brewery-detail.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/brewery-detail";
module: typeof import("../brewery-detail.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// ServerHydrateFallback
export type ServerHydrateFallbackProps = Annotations["ServerHydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ServerComponent
export type ServerComponentProps = Annotations["ServerComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
// ServerErrorBoundary
export type ServerErrorBoundaryProps = Annotations["ServerErrorBoundaryProps"];
}