format
This commit is contained in:
parent
9270bc1aa5
commit
1596e95eba
12 changed files with 182 additions and 166 deletions
|
|
@ -31,24 +31,11 @@ config :aprsme, AprsmeWeb.Gettext,
|
|||
locales: ~w(en es de fr),
|
||||
default_locale: "en"
|
||||
|
||||
# Configure Exq for background jobs
|
||||
# Redis connection settings are configured in runtime.exs
|
||||
config :exq,
|
||||
name: Exq,
|
||||
concurrency: :infinite,
|
||||
queues: [
|
||||
{"default", 10},
|
||||
{"maintenance", 2}
|
||||
],
|
||||
scheduler_enable: true,
|
||||
scheduler_poll_timeout: 200,
|
||||
poll_timeout: 50,
|
||||
redis_timeout: 5000
|
||||
|
||||
# Configure periodic cleanup job
|
||||
config :aprsme, :cleanup_scheduler,
|
||||
enabled: true,
|
||||
interval: 6 * 60 * 60 * 1000 # 6 hours in milliseconds
|
||||
# 6 hours in milliseconds
|
||||
interval: 6 * 60 * 60 * 1000
|
||||
|
||||
config :aprsme,
|
||||
ecto_repos: [Aprsme.Repo]
|
||||
|
|
@ -128,6 +115,20 @@ config :esbuild,
|
|||
cd: Path.expand("../assets", __DIR__)
|
||||
]
|
||||
|
||||
# Configure Exq for background jobs
|
||||
# Redis connection settings are configured in runtime.exs
|
||||
config :exq,
|
||||
name: Exq,
|
||||
concurrency: :infinite,
|
||||
queues: [
|
||||
{"default", 10},
|
||||
{"maintenance", 2}
|
||||
],
|
||||
scheduler_enable: true,
|
||||
scheduler_poll_timeout: 200,
|
||||
poll_timeout: 50,
|
||||
redis_timeout: 5000
|
||||
|
||||
config :gettext, :plural_forms, GettextPseudolocalize.Plural
|
||||
|
||||
# Configures Elixir's Logger
|
||||
|
|
|
|||
|
|
@ -49,6 +49,55 @@ if config_env() == :prod do
|
|||
# Configure clustering based on environment
|
||||
cluster_enabled = System.get_env("CLUSTER_ENABLED", "false") == "true"
|
||||
|
||||
# Configure Exq with Redis connection from environment
|
||||
redis_url = System.get_env("REDIS_URL", "redis://localhost:6379")
|
||||
redis_uri = URI.parse(redis_url)
|
||||
redis_host = redis_uri.host || "localhost"
|
||||
redis_port = redis_uri.port || 6379
|
||||
|
||||
redis_password =
|
||||
case redis_uri.userinfo do
|
||||
nil ->
|
||||
""
|
||||
|
||||
userinfo ->
|
||||
case String.split(userinfo, ":") do
|
||||
[_, password] -> password
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
# Extract database from path if present
|
||||
redis_database =
|
||||
try do
|
||||
case redis_uri.path do
|
||||
nil ->
|
||||
0
|
||||
|
||||
"/" ->
|
||||
0
|
||||
|
||||
path ->
|
||||
path |> String.trim_leading("/") |> String.to_integer()
|
||||
end
|
||||
rescue
|
||||
_ -> 0
|
||||
end
|
||||
|
||||
exq_config = [
|
||||
host: redis_host,
|
||||
port: redis_port,
|
||||
database: redis_database
|
||||
]
|
||||
|
||||
# Only add password if it's not empty
|
||||
exq_config =
|
||||
if redis_password != "" and redis_password != nil do
|
||||
Keyword.put(exq_config, :password, redis_password)
|
||||
else
|
||||
exq_config
|
||||
end
|
||||
|
||||
# ## Configuring the mailer
|
||||
#
|
||||
# Configure Resend for email delivery
|
||||
|
|
@ -119,48 +168,6 @@ if config_env() == :prod do
|
|||
config :aprsme,
|
||||
default_from_email: "w5isp@aprs.me"
|
||||
|
||||
# Configure Exq with Redis connection from environment
|
||||
redis_url = System.get_env("REDIS_URL", "redis://localhost:6379")
|
||||
redis_uri = URI.parse(redis_url)
|
||||
|
||||
redis_host = redis_uri.host || "localhost"
|
||||
redis_port = redis_uri.port || 6379
|
||||
redis_password = case redis_uri.userinfo do
|
||||
nil -> ""
|
||||
userinfo ->
|
||||
case String.split(userinfo, ":") do
|
||||
[_, password] -> password
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
# Extract database from path if present
|
||||
redis_database = try do
|
||||
case redis_uri.path do
|
||||
nil -> 0
|
||||
"/" -> 0
|
||||
path ->
|
||||
path |> String.trim_leading("/") |> String.to_integer()
|
||||
end
|
||||
rescue
|
||||
_ -> 0
|
||||
end
|
||||
|
||||
exq_config = [
|
||||
host: redis_host,
|
||||
port: redis_port,
|
||||
database: redis_database
|
||||
]
|
||||
|
||||
# Only add password if it's not empty
|
||||
exq_config = if redis_password != "" and redis_password != nil do
|
||||
Keyword.put(exq_config, :password, redis_password)
|
||||
else
|
||||
exq_config
|
||||
end
|
||||
|
||||
config :exq, exq_config
|
||||
|
||||
config :aprsme,
|
||||
ecto_repos: [Aprsme.Repo],
|
||||
aprs_is_server: System.get_env("APRS_SERVER", "dallas.aprs2.net"),
|
||||
|
|
@ -171,6 +178,8 @@ if config_env() == :prod do
|
|||
aprs_is_password: System.get_env("APRS_PASSCODE"),
|
||||
env: :prod
|
||||
|
||||
config :exq, exq_config
|
||||
|
||||
# Configure Hammer for production environment
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ config :aprsme, AprsmeWeb.Endpoint,
|
|||
secret_key_base: "IV9+ENaw9i8xjReRk4sULRvRgsmFVTGQwQGGrf4G+Q/SFMeHBCNWRlPXQ2YvT36R",
|
||||
server: false
|
||||
|
||||
# Disable Exq during tests to prevent background job execution
|
||||
config :exq, mode: :inline
|
||||
|
||||
# Disable cleanup scheduler in test environment
|
||||
config :aprsme, :cleanup_scheduler, enabled: false
|
||||
|
||||
|
|
@ -54,6 +51,9 @@ config :aprsme, auto_migrate: false
|
|||
# Only in tests, remove the complexity from the password hashing algorithm
|
||||
config :bcrypt_elixir, :log_rounds, 1
|
||||
|
||||
# Disable Exq during tests to prevent background job execution
|
||||
config :exq, mode: :inline
|
||||
|
||||
# Configure ExVCR
|
||||
config :exvcr,
|
||||
vcr_cassette_library_dir: "test/fixtures/vcr_cassettes",
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ defmodule Aprsme.Application do
|
|||
do_migrate(true)
|
||||
else
|
||||
require Logger
|
||||
|
||||
if cluster_enabled do
|
||||
Logger.info("Skipping auto-migration in cluster mode")
|
||||
else
|
||||
|
|
@ -176,7 +177,6 @@ defmodule Aprsme.Application do
|
|||
Logger.info("Database migrations completed")
|
||||
end
|
||||
|
||||
|
||||
defp pubsub_config do
|
||||
cluster_enabled = Application.get_env(:aprsme, :cluster_enabled, false)
|
||||
redis_url = System.get_env("REDIS_URL")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Aprsme.CleanupScheduler do
|
|||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
def start_link(opts) do
|
||||
|
|
@ -17,7 +18,8 @@ defmodule Aprsme.CleanupScheduler do
|
|||
def init(_opts) do
|
||||
config = Application.get_env(:aprsme, :cleanup_scheduler, [])
|
||||
enabled = Keyword.get(config, :enabled, true)
|
||||
interval = Keyword.get(config, :interval, 6 * 60 * 60 * 1000) # 6 hours
|
||||
# 6 hours
|
||||
interval = Keyword.get(config, :interval, 6 * 60 * 60 * 1000)
|
||||
|
||||
if enabled do
|
||||
Logger.info("Starting packet cleanup scheduler with #{interval}ms interval")
|
||||
|
|
@ -37,6 +39,7 @@ defmodule Aprsme.CleanupScheduler do
|
|||
case Exq.enqueue(Exq, "maintenance", Aprsme.Workers.PacketCleanupWorker, []) do
|
||||
{:ok, _job_id} ->
|
||||
Logger.info("Packet cleanup job scheduled successfully")
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to schedule packet cleanup job: #{inspect(reason)}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,13 +24,16 @@ defmodule Aprsme.Encoding do
|
|||
"""
|
||||
@spec to_float_safe(binary()) :: {:ok, float()} | nil
|
||||
def to_float_safe(value) when is_binary(value) do
|
||||
sanitized = value
|
||||
|> String.trim()
|
||||
|> String.slice(0, 30) # Reasonable max length for a number
|
||||
sanitized =
|
||||
value
|
||||
|> String.trim()
|
||||
# Reasonable max length for a number
|
||||
|> String.slice(0, 30)
|
||||
|
||||
case Float.parse(sanitized) do
|
||||
{f, _} when f > -9.0e15 and f < 9.0e15 ->
|
||||
{:ok, f}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
|
@ -56,48 +59,48 @@ defmodule Aprsme.Encoding do
|
|||
@spec has_weather_data(any(), any(), any(), any()) :: boolean()
|
||||
def has_weather_data(temperature, humidity, wind_speed, pressure) do
|
||||
not is_nil(temperature) or
|
||||
not is_nil(humidity) or
|
||||
not is_nil(wind_speed) or
|
||||
not is_nil(pressure)
|
||||
not is_nil(humidity) or
|
||||
not is_nil(wind_speed) or
|
||||
not is_nil(pressure)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get encoding information about a binary
|
||||
"""
|
||||
@spec encoding_info(binary()) :: %{
|
||||
valid_utf8: boolean(),
|
||||
byte_count: non_neg_integer(),
|
||||
char_count: non_neg_integer() | nil,
|
||||
invalid_at: non_neg_integer() | nil
|
||||
}
|
||||
valid_utf8: boolean(),
|
||||
byte_count: non_neg_integer(),
|
||||
char_count: non_neg_integer() | nil,
|
||||
invalid_at: non_neg_integer() | nil
|
||||
}
|
||||
def encoding_info(input) when is_binary(input) do
|
||||
byte_count = byte_size(input)
|
||||
|
||||
case String.valid?(input) do
|
||||
true ->
|
||||
%{
|
||||
valid_utf8: true,
|
||||
byte_count: byte_count,
|
||||
char_count: String.length(input),
|
||||
invalid_at: nil
|
||||
}
|
||||
false ->
|
||||
invalid_pos = find_invalid_byte_position(input)
|
||||
%{
|
||||
valid_utf8: false,
|
||||
byte_count: byte_count,
|
||||
char_count: nil,
|
||||
invalid_at: invalid_pos
|
||||
}
|
||||
if String.valid?(input) do
|
||||
%{
|
||||
valid_utf8: true,
|
||||
byte_count: byte_count,
|
||||
char_count: String.length(input),
|
||||
invalid_at: nil
|
||||
}
|
||||
else
|
||||
invalid_pos = find_invalid_byte_position(input)
|
||||
|
||||
%{
|
||||
valid_utf8: false,
|
||||
byte_count: byte_count,
|
||||
char_count: nil,
|
||||
invalid_at: invalid_pos
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@spec encoding_info(any()) :: %{
|
||||
valid_utf8: boolean(),
|
||||
byte_count: non_neg_integer(),
|
||||
char_count: nil,
|
||||
invalid_at: nil
|
||||
}
|
||||
valid_utf8: boolean(),
|
||||
byte_count: non_neg_integer(),
|
||||
char_count: nil,
|
||||
invalid_at: nil
|
||||
}
|
||||
def encoding_info(_) do
|
||||
%{
|
||||
valid_utf8: false,
|
||||
|
|
@ -121,14 +124,12 @@ defmodule Aprsme.Encoding do
|
|||
|
||||
@spec latin1_to_utf8(binary()) :: binary()
|
||||
defp latin1_to_utf8(input) do
|
||||
try do
|
||||
input
|
||||
|> :binary.bin_to_list()
|
||||
|> Enum.map(&latin1_char_to_utf8/1)
|
||||
|> IO.iodata_to_binary()
|
||||
rescue
|
||||
_ -> ""
|
||||
end
|
||||
input
|
||||
|> :binary.bin_to_list()
|
||||
|> Enum.map(&latin1_char_to_utf8/1)
|
||||
|> IO.iodata_to_binary()
|
||||
rescue
|
||||
_ -> ""
|
||||
end
|
||||
|
||||
@spec latin1_char_to_utf8(0..255) :: binary()
|
||||
|
|
@ -166,6 +167,7 @@ defmodule Aprsme.Encoding do
|
|||
c when c >= 128 and c <= 159 -> false
|
||||
_ -> true
|
||||
end
|
||||
|
||||
_ ->
|
||||
# Multi-codepoint grapheme, keep it
|
||||
true
|
||||
|
|
@ -186,11 +188,9 @@ defmodule Aprsme.Encoding do
|
|||
|
||||
@spec valid_utf8_continuation?(binary(), non_neg_integer()) :: boolean()
|
||||
defp valid_utf8_continuation?(binary, pos) do
|
||||
try do
|
||||
<<_::binary-size(pos), _char::utf8, _::binary>> = binary
|
||||
true
|
||||
rescue
|
||||
_ -> false
|
||||
end
|
||||
<<_::binary-size(pos), _char::utf8, _::binary>> = binary
|
||||
true
|
||||
rescue
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
|
@ -49,14 +49,12 @@ defmodule Aprsme.EncodingUtils do
|
|||
"""
|
||||
@spec to_float(any()) :: float() | nil
|
||||
def to_float(value) when is_float(value) do
|
||||
if finite_float?(value), do: value, else: nil
|
||||
if finite_float?(value), do: value
|
||||
end
|
||||
|
||||
def to_float(value) when is_integer(value) do
|
||||
if value >= -9.0e15 and value <= 9.0e15 do
|
||||
value * 1.0
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -72,7 +70,7 @@ defmodule Aprsme.EncodingUtils do
|
|||
|
||||
def to_float(%Decimal{} = value) do
|
||||
float = Decimal.to_float(value)
|
||||
if finite_float?(float), do: float, else: nil
|
||||
if finite_float?(float), do: float
|
||||
end
|
||||
|
||||
def to_float(_), do: nil
|
||||
|
|
|
|||
|
|
@ -23,14 +23,16 @@ defmodule Aprsme.Release do
|
|||
cluster_enabled = Application.get_env(:aprsme, :cluster_enabled, false)
|
||||
|
||||
# Configure longer timeouts for migration operations
|
||||
migration_timeout = String.to_integer(System.get_env("MIGRATION_TIMEOUT", "7200000")) # 2 hours default
|
||||
# 2 hours default
|
||||
migration_timeout = String.to_integer(System.get_env("MIGRATION_TIMEOUT", "7200000"))
|
||||
|
||||
if cluster_enabled do
|
||||
Logger.info("Running migrations with distributed lock...")
|
||||
# Ensure repo is started for advisory lock with extended timeout
|
||||
repo_config = Aprsme.Repo.config()
|
||||
|> Keyword.put(:timeout, migration_timeout)
|
||||
|> Keyword.put(:pool_timeout, 60_000)
|
||||
repo_config =
|
||||
Aprsme.Repo.config()
|
||||
|> Keyword.put(:timeout, migration_timeout)
|
||||
|> Keyword.put(:pool_timeout, 60_000)
|
||||
|
||||
{:ok, _} = Aprsme.Repo.start_link(repo_config)
|
||||
|
||||
|
|
@ -95,15 +97,19 @@ defmodule Aprsme.Release do
|
|||
Logger.info("Running migrations with timeout: #{timeout}ms")
|
||||
|
||||
# Run with extended timeout configuration
|
||||
_repo_config = Aprsme.Repo.config()
|
||||
|> Keyword.put(:timeout, timeout)
|
||||
|> Keyword.put(:pool_timeout, 60_000)
|
||||
_repo_config =
|
||||
Aprsme.Repo.config()
|
||||
|> Keyword.put(:timeout, timeout)
|
||||
|> Keyword.put(:pool_timeout, 60_000)
|
||||
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(Aprsme.Repo, fn repo ->
|
||||
# Set session-level timeout for this connection
|
||||
Ecto.Adapters.SQL.query!(repo, "SET statement_timeout = '#{div(timeout, 1000)}s'")
|
||||
Ecto.Migrator.run(repo, :up, all: true)
|
||||
end, [timeout: timeout])
|
||||
{:ok, _, _} =
|
||||
Ecto.Migrator.with_repo(
|
||||
Aprsme.Repo,
|
||||
fn repo ->
|
||||
# Set session-level timeout for this connection
|
||||
Ecto.Adapters.SQL.query!(repo, "SET statement_timeout = '#{div(timeout, 1000)}s'")
|
||||
Ecto.Migrator.run(repo, :up, all: true)
|
||||
end, timeout: timeout)
|
||||
end
|
||||
|
||||
defp read_deployment_timestamp do
|
||||
|
|
|
|||
3
mix.exs
3
mix.exs
|
|
@ -52,7 +52,6 @@ defmodule Aprsme.MixProject do
|
|||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
|
||||
# Specifies your project dependencies.
|
||||
#
|
||||
# Type `mix help deps` for examples and options.
|
||||
|
|
@ -107,7 +106,7 @@ defmodule Aprsme.MixProject do
|
|||
{:hammer, "~> 7.0"},
|
||||
{:cachex, "~> 4.1"},
|
||||
{:gettext_pseudolocalize, "~> 0.1"},
|
||||
{:sentry, "~> 11.0"},
|
||||
{:sentry, "~> 11.0"}
|
||||
# Gleam dependencies
|
||||
]
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue