chore: add contributor guide and harden cache/csrf helpers
This commit is contained in:
parent
751b8a5f1b
commit
1b638cf739
3 changed files with 37 additions and 23 deletions
16
AGENTS.md
Normal file
16
AGENTS.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
Elixir application code lives in `lib/`: `lib/aprsme` holds domain contexts, `lib/aprsme_web` contains LiveView, controllers, and templates, and `lib/mix` exposes project-specific Mix tasks. Front-end assets are in `assets/` (split into `js/`, `css/`, and component bundles under `assets/`). Database artifacts sit in `priv/repo/` with migrations and seeds, while `config/` provides environment-specific settings. Tests reside in `test/` with shared helpers in `test/support/`. Supporting tooling (Docker, scripts, CI manifests) lives under `scripts/`, `rel/`, and `k8s/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
Run `mix setup` once to fetch dependencies and prepare the Postgres database. Use `mix phx.server` (or `iex -S mix phx.server`) for local development. Execute `mix test` for the full ExUnit suite; `mix test.watch` keeps rerunning on file changes. For quality gates, `mix credo --strict`, `mix dialyzer`, and `mix sobelow` mirror CI checks. Before releases or deployments, build static assets with `MIX_ENV=prod mix assets.deploy` and create a release via `mix release`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Follow idiomatic Elixir with 2-space indentation and modules in `PascalCase`. The top-level `.formatter.exs` enforces a 120-character line limit, imports Phoenix HTML formatting, and loads the Styler plugin—always run `mix format` before committing. Keep function names in `snake_case`, LiveView hooks under `assets/js` in `camelCase`, and CSS utilities organized by Tailwind conventions. Prefer contexts for new domain modules and colocate schemas with their contexts inside `lib/aprsme`.
|
||||
|
||||
## Testing Guidelines
|
||||
The project uses ExUnit with database sandboxing; aliases ensure `ecto.create` and `ecto.migrate` run before tests. Property-based tests live alongside unit tests using `StreamData`, and browser flows use Wallaby helpers in `test/support`. Name test files `<feature>_test.exs` and ensure async-friendly tests opt in with `use ExUnit.Case, async: true`. Check coverage locally with `mix coveralls.html` and review the generated report in `cover/`.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Commit history favors short, imperative subjects with optional Conventional Commit prefixes (`fix:`, `refactor:`). Keep each commit focused, formatted, and passing the suite. PRs should outline the change, reference related issues, note DB or config impacts, and include screenshots or logs for UI-facing updates. Confirm that formatting, tests, and static analysis commands above complete successfully before requesting review.
|
||||
|
|
@ -20,7 +20,7 @@ defmodule Aprsme.Cache do
|
|||
"""
|
||||
def put(cache_name, key, value, opts \\ []) do
|
||||
if using_redis?() do
|
||||
# Convert TTL from milliseconds to seconds for Redis
|
||||
# Convert TTL from milliseconds to seconds for Redis, preserving sub-second values
|
||||
opts = convert_ttl_to_seconds(opts)
|
||||
Aprsme.RedisCache.put(cache_name, key, value, opts)
|
||||
else
|
||||
|
|
@ -68,7 +68,10 @@ defmodule Aprsme.Cache do
|
|||
if using_redis?() do
|
||||
Aprsme.RedisCache.exists?(cache_name, key)
|
||||
else
|
||||
Cachex.exists?(cache_name, key)
|
||||
case Cachex.exists?(cache_name, key) do
|
||||
{:ok, exists?} -> exists?
|
||||
{:error, _reason} -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -94,9 +97,12 @@ defmodule Aprsme.Cache do
|
|||
nil ->
|
||||
opts
|
||||
|
||||
ttl_ms when is_integer(ttl_ms) and ttl_ms > 0 ->
|
||||
ttl_seconds = Integer.ceil_div(ttl_ms, 1000) |> max(1)
|
||||
Keyword.put(opts, :ttl, ttl_seconds)
|
||||
|
||||
ttl_ms when is_integer(ttl_ms) ->
|
||||
# Convert milliseconds to seconds
|
||||
Keyword.put(opts, :ttl, div(ttl_ms, 1000))
|
||||
Keyword.put(opts, :ttl, ttl_ms)
|
||||
|
||||
_ ->
|
||||
opts
|
||||
|
|
|
|||
|
|
@ -45,28 +45,20 @@ defmodule AprsmeWeb.Plugs.ApiCSRF do
|
|||
end
|
||||
|
||||
defp validate_csrf_token(conn, token) do
|
||||
# Get session token for comparison
|
||||
session_token = get_session(conn, "_csrf_token")
|
||||
case get_session(conn, "_csrf_token") do
|
||||
nil ->
|
||||
reject_request(conn)
|
||||
|
||||
# Use Phoenix.Controller CSRF token validation
|
||||
if session_token && valid_csrf_token?(conn, token) do
|
||||
conn
|
||||
else
|
||||
reject_request(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_csrf_token?(conn, token) do
|
||||
session_token = get_session(conn, "_csrf_token")
|
||||
|
||||
if session_token do
|
||||
# Use a simple comparison for now
|
||||
Phoenix.Token.verify(conn, "_csrf_token", token, max_age: 86_400) == {:ok, session_token}
|
||||
else
|
||||
false
|
||||
session_token ->
|
||||
if Plug.CSRFProtection.verify_csrf_token(token, session_token) do
|
||||
conn
|
||||
else
|
||||
reject_request(conn)
|
||||
end
|
||||
end
|
||||
rescue
|
||||
_ -> false
|
||||
_ ->
|
||||
reject_request(conn)
|
||||
end
|
||||
|
||||
defp reject_request(conn) do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue