diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/.dialyzer_ignore.exs @@ -0,0 +1 @@ +[] diff --git a/.github/github_workflows.ex b/.github/github_workflows.ex new file mode 100644 index 0000000..f7b482f --- /dev/null +++ b/.github/github_workflows.ex @@ -0,0 +1,323 @@ +defmodule GithubWorkflows do + @moduledoc """ + Used by a custom tool to generate GitHub workflows. + Reduces repetition. + """ + + def get do + %{ + "main.yml" => main_workflow(), + "pr.yml" => pr_workflow() + } + end + + defp main_workflow do + [ + [ + name: "Main", + on: [ + push: [ + branches: ["main"] + ] + ], + jobs: [ + compile: compile_job(), + credo: credo_job(), + deps_audit: deps_audit_job(), + dialyzer: dialyzer_job(), + format: format_job(), + hex_audit: hex_audit_job(), + migrations: migrations_job(), + prettier: prettier_job(), + sobelow: sobelow_job(), + test: test_job(), + unused_deps: unused_deps_job() + ] + ] + ] + end + + defp pr_workflow() do + [ + [ + name: "PR", + on: [ + pull_request: [ + branches: ["main"], + types: ["opened", "reopened", "synchronize"] + ] + ], + jobs: [ + compile: compile_job(), + credo: credo_job(), + deps_audit: deps_audit_job(), + dialyzer: dialyzer_job(), + format: format_job(), + hex_audit: hex_audit_job(), + migrations: migrations_job(), + prettier: prettier_job(), + sobelow: sobelow_job(), + test: test_job(), + unused_deps: unused_deps_job() + ] + ] + ] + end + + defp checkout_step do + [ + name: "Checkout", + uses: "actions/checkout@v2" + ] + end + + defp compile_job do + elixir_job("Install deps and compile", + steps: [ + [ + name: "Install Elixir dependencies", + env: [MIX_ENV: "test"], + run: "mix deps.get" + ], + [ + name: "Compile", + env: [MIX_ENV: "test"], + run: "mix compile" + ] + ] + ) + end + + defp credo_job do + elixir_job("Credo", + needs: :compile, + steps: [ + [ + name: "Check code style", + env: [MIX_ENV: "test"], + run: "mix credo --strict" + ] + ] + ) + end + + defp deps_audit_job do + elixir_job("Deps audit", + needs: :compile, + steps: [ + [ + name: "Check for vulnerable Mix dependencies", + env: [MIX_ENV: "test"], + run: "mix deps.audit" + ] + ] + ) + end + + defp dialyzer_job do + elixir_job("Dialyzer", + needs: :compile, + steps: [ + [ + # Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones + # Cache key based on Elixir & Erlang version (also useful when running in matrix) + name: "Restore PLT cache", + uses: "actions/cache@v3", + id: "plt_cache", + with: [ + key: "${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt", + "restore-keys": + "${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt", + path: "priv/plts" + ] + ], + [ + # Create PLTs if no cache was found + name: "Create PLTs", + if: "steps.plt_cache.outputs.cache-hit != 'true'", + env: [MIX_ENV: "test"], + run: "mix dialyzer --plt" + ], + [ + name: "Run dialyzer", + env: [MIX_ENV: "test"], + run: "mix dialyzer --format short 2>&1" + ] + ] + ) + end + + defp elixir_job(name, opts) do + needs = Keyword.get(opts, :needs) + steps = Keyword.get(opts, :steps, []) + services = Keyword.get(opts, :services) + + job = [ + name: name, + "runs-on": "ubuntu-latest", + env: [ + "elixir-version": "1.16.2", + "otp-version": "25.3.2.10" + ], + steps: + [ + checkout_step(), + [ + name: "Set up Elixir", + uses: "erlef/setup-beam@v1", + with: [ + "elixir-version": "${{ env.elixir-version }}", + "otp-version": "${{ env.otp-version }}" + ] + ], + [ + uses: "actions/cache@v3", + with: [ + path: "_build\ndeps", + key: "${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}", + "restore-keys": "${{ runner.os }}-mix" + ] + ] + ] ++ steps + ] + + job + |> then(fn job -> + if needs do + Keyword.put(job, :needs, needs) + else + job + end + end) + |> then(fn job -> + if services do + Keyword.put(job, :services, services) + else + job + end + end) + end + + defp format_job do + elixir_job("Format", + needs: :compile, + steps: [ + [ + name: "Check Elixir formatting", + env: [MIX_ENV: "test"], + run: "mix format --check-formatted" + ] + ] + ) + end + + defp hex_audit_job do + elixir_job("Hex audit", + needs: :compile, + steps: [ + [ + name: "Check for retired Hex packages", + env: [MIX_ENV: "test"], + run: "mix hex.audit" + ] + ] + ) + end + + defp migrations_job do + elixir_job("Migrations", + needs: :compile, + services: [ + db: db_service() + ], + steps: [ + [ + name: "Check if migrations are reversible", + env: [MIX_ENV: "test"], + run: "mix ci.migrations" + ] + ] + ) + end + + defp prettier_job do + [ + name: "Check formatting using Prettier", + "runs-on": "ubuntu-latest", + steps: [ + checkout_step(), + [ + name: "Restore npm cache", + uses: "actions/cache@v3", + id: "npm-cache", + with: [ + path: "~/.npm", + key: "${{ runner.os }}-node", + "restore-keys": "${{ runner.os }}-node" + ] + ], + [ + name: "Install Prettier", + if: "steps.npm-cache.outputs.cache-hit != 'true'", + run: "npm i -g prettier" + ], + [ + name: "Run Prettier", + run: "npx prettier -c ." + ] + ] + ] + end + + defp sobelow_job do + elixir_job("Security check", + needs: :compile, + steps: [ + [ + name: "Check for security issues using sobelow", + env: [MIX_ENV: "test"], + run: "mix sobelow --config .sobelow-conf" + ] + ] + ) + end + + defp test_job do + elixir_job("Test", + needs: :compile, + services: [ + db: db_service() + ], + steps: [ + [ + name: "Run tests", + env: [MIX_ENV: "test"], + run: "mix test --cover --warnings-as-errors" + ] + ] + ) + end + + defp unused_deps_job do + elixir_job("Check unused deps", + needs: :compile, + steps: [ + [ + name: "Check for unused Mix dependencies", + env: [MIX_ENV: "test"], + run: "mix deps.unlock --check-unused" + ] + ] + ) + end + + defp db_service do + [ + image: "postgres:13", + ports: ["5432:5432"], + env: [POSTGRES_PASSWORD: "postgres"], + options: + "--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5" + ] + end +end diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 90c018d..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: Elixir CI - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -permissions: - contents: read - -jobs: - test: - # Set up a Postgres DB service. By default, Phoenix applications - # use Postgres. - services: - db: - image: postgres:15 - ports: ["5432:5432"] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - runs-on: ubuntu-latest - name: Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} - strategy: - matrix: - otp: ["25.3.2"] - elixir: ["1.15.7"] - steps: - - name: Set up Elixir - uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0 - with: - otp-version: ${{matrix.otp}} - elixir-version: ${{matrix.elixir}} - - # Step: Check out the code. - - name: Checkout code - uses: actions/checkout@v3 - - # Step: Define how to cache deps. Restores existing cache if present. - - name: Cache deps - id: cache-deps - uses: actions/cache@v3 - env: - cache-name: cache-elixir-deps - with: - path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - # Step: Define how to cache the `_build` directory. After the first run, - # this speeds up tests runs a lot. This includes not re-compiling our - # project's downloaded deps every run. - - name: Cache compiled build - id: cache-build - uses: actions/cache@v3 - env: - cache-name: cache-compiled-build - with: - path: _build - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- - - # Step: Conditionally bust the cache when job is re-run. - # Sometimes, we may have issues with incremental builds that are fixed by - # doing a full recompile. In order to not waste dev time on such trivial - # issues (while also reaping the time savings of incremental builds for - # *most* day-to-day development), force a full recompile only on builds - # that are retried. - - name: Keep it clean to avoid incremental build problems (flakiness) - if: github.run_attempt != '1' - run: | - mix deps.clean --all - mix clean - shell: sh - - - name: Install dependencies - run: mix deps.get - - - name: Compiles without warnings - run: mix compile --warnings-as-errors - - - name: Check Formatting - run: mix format --check-formatted - - - name: Credo Check - run: mix credo - - - name: Run tests - run: mix test --cover --warnings-as-errors \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b7a418e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,295 @@ +name: Main +on: + push: + branches: + - main +jobs: + compile: + name: Install deps and compile + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Install Elixir dependencies + env: + MIX_ENV: test + run: mix deps.get + - name: Compile + env: + MIX_ENV: test + run: mix compile + credo: + needs: compile + name: Credo + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check code style + env: + MIX_ENV: test + run: mix credo --strict + deps_audit: + needs: compile + name: Deps audit + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for vulnerable Mix dependencies + env: + MIX_ENV: test + run: mix deps.audit + dialyzer: + needs: compile + name: Dialyzer + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Restore PLT cache + uses: actions/cache@v3 + id: plt_cache + with: + key: ${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt + restore-keys: ${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt + path: priv/plts + - name: Create PLTs + if: steps.plt_cache.outputs.cache-hit != 'true' + env: + MIX_ENV: test + run: mix dialyzer --plt + - name: Run dialyzer + env: + MIX_ENV: test + run: mix dialyzer --format short 2>&1 + format: + needs: compile + name: Format + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check Elixir formatting + env: + MIX_ENV: test + run: mix format --check-formatted + hex_audit: + needs: compile + name: Hex audit + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for retired Hex packages + env: + MIX_ENV: test + run: mix hex.audit + migrations: + services: + db: + image: postgres:13 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + needs: compile + name: Migrations + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check if migrations are reversible + env: + MIX_ENV: test + run: mix ci.migrations + prettier: + name: Check formatting using Prettier + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Restore npm cache + uses: actions/cache@v3 + id: npm-cache + with: + path: ~/.npm + key: ${{ runner.os }}-node + restore-keys: ${{ runner.os }}-node + - name: Install Prettier + if: steps.npm-cache.outputs.cache-hit != 'true' + run: npm i -g prettier + - name: Run Prettier + run: npx prettier -c . + sobelow: + needs: compile + name: Security check + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for security issues using sobelow + env: + MIX_ENV: test + run: mix sobelow --config .sobelow-conf + test: + services: + db: + image: postgres:13 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + needs: compile + name: Test + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Run tests + env: + MIX_ENV: test + run: mix test --cover --warnings-as-errors + unused_deps: + needs: compile + name: Check unused deps + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for unused Mix dependencies + env: + MIX_ENV: test + run: mix deps.unlock --check-unused diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..846ee42 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,299 @@ +name: PR +on: + pull_request: + branches: + - main + types: + - opened + - reopened + - synchronize +jobs: + compile: + name: Install deps and compile + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Install Elixir dependencies + env: + MIX_ENV: test + run: mix deps.get + - name: Compile + env: + MIX_ENV: test + run: mix compile + credo: + needs: compile + name: Credo + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check code style + env: + MIX_ENV: test + run: mix credo --strict + deps_audit: + needs: compile + name: Deps audit + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for vulnerable Mix dependencies + env: + MIX_ENV: test + run: mix deps.audit + dialyzer: + needs: compile + name: Dialyzer + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Restore PLT cache + uses: actions/cache@v3 + id: plt_cache + with: + key: ${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt + restore-keys: ${{ runner.os }}-${{ env.elixir-version }}-${{ env.otp-version }}-plt + path: priv/plts + - name: Create PLTs + if: steps.plt_cache.outputs.cache-hit != 'true' + env: + MIX_ENV: test + run: mix dialyzer --plt + - name: Run dialyzer + env: + MIX_ENV: test + run: mix dialyzer --format short 2>&1 + format: + needs: compile + name: Format + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check Elixir formatting + env: + MIX_ENV: test + run: mix format --check-formatted + hex_audit: + needs: compile + name: Hex audit + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for retired Hex packages + env: + MIX_ENV: test + run: mix hex.audit + migrations: + services: + db: + image: postgres:13 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + needs: compile + name: Migrations + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check if migrations are reversible + env: + MIX_ENV: test + run: mix ci.migrations + prettier: + name: Check formatting using Prettier + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Restore npm cache + uses: actions/cache@v3 + id: npm-cache + with: + path: ~/.npm + key: ${{ runner.os }}-node + restore-keys: ${{ runner.os }}-node + - name: Install Prettier + if: steps.npm-cache.outputs.cache-hit != 'true' + run: npm i -g prettier + - name: Run Prettier + run: npx prettier -c . + sobelow: + needs: compile + name: Security check + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for security issues using sobelow + env: + MIX_ENV: test + run: mix sobelow --config .sobelow-conf + test: + services: + db: + image: postgres:13 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + needs: compile + name: Test + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Run tests + env: + MIX_ENV: test + run: mix test --cover --warnings-as-errors + unused_deps: + needs: compile + name: Check unused deps + runs-on: ubuntu-latest + env: + elixir-version: 1.16.2 + otp-version: 25.3.2.10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.elixir-version }} + otp-version: ${{ env.otp-version }} + - uses: actions/cache@v3 + with: + path: "_build\ndeps" + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix + - name: Check for unused Mix dependencies + env: + MIX_ENV: test + run: mix deps.unlock --check-unused diff --git a/.gitignore b/.gitignore index 7b1ee76..a3ec1f7 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ npm-debug.log # Ignore Dialyzer PLTs /priv/plts/ +# Ignore uploaded files +/priv/static/uploads/ \ No newline at end of file diff --git a/README.md b/README.md index d10cd1b..41fd477 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ To start your Phoenix server: - * Run `mix setup` to install and setup dependencies - * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` +- Run `mix setup` to install and setup dependencies +- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. @@ -11,8 +11,8 @@ Ready to run in production? Please [check our deployment guides](https://hexdocs ## Learn more - * Official website: https://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Forum: https://elixirforum.com/c/phoenix-forum - * Source: https://github.com/phoenixframework/phoenix +- Official website: https://www.phoenixframework.org/ +- Guides: https://hexdocs.pm/phoenix/overview.html +- Docs: https://hexdocs.pm/phoenix +- Forum: https://elixirforum.com/c/phoenix-forum +- Source: https://github.com/phoenixframework/phoenix diff --git a/assets/.prettierignore b/assets/.prettierignore new file mode 100644 index 0000000..3932972 --- /dev/null +++ b/assets/.prettierignore @@ -0,0 +1,22 @@ +/../_build/ +/../.elixir_ls/ +/../.git/ +/../config/ +/../cover/ +/../deps/ +/../doc/ +/../lib/ +/../priv/ +/../rel/ +/../test/ +/../.credo.exs +/../.dialyzer_ignore.exs +/../.dockerignore +/../.env +/../.env.prod.sample +/../.env.sample +/../.formatter.exs +/../.gitignore +/../.iex.exs +/../.erl_crash.dump +/vendor/topbar.js \ No newline at end of file diff --git a/assets/.prettierrc.js b/assets/.prettierrc.js new file mode 100644 index 0000000..0614ee7 --- /dev/null +++ b/assets/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + trailingComma: 'es5', + tabWidth: 2, + semi: false, + singleQuote: true, +} diff --git a/assets/css/app.css b/assets/css/app.css index 378c8f9..8ac0912 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,5 +1,11 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; -/* This file is for your main application CSS */ +@import url('https://fonts.googleapis.com/css2?family=League+Spartan:wght@300;400;500;600;700&display=swap'); /* This file is for your main application CSS */ + +@layer base { + html { + font-family: 'League Spartan', sans-serif; + } +} diff --git a/assets/js/app.js b/assets/js/app.js index df0cdd9..c55ebdd 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -16,19 +16,23 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +import 'phoenix_html' // Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" +import { Socket } from 'phoenix' +import { LiveSocket } from 'phoenix_live_view' +import topbar from '../vendor/topbar' -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") -let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) +let csrfToken = document + .querySelector("meta[name='csrf-token']") + .getAttribute('content') +let liveSocket = new LiveSocket('/live', Socket, { + params: { _csrf_token: csrfToken }, +}) // Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +topbar.config({ barColors: { 0: '#29d' }, shadowColor: 'rgba(0, 0, 0, .3)' }) +window.addEventListener('phx:page-loading-start', (_info) => topbar.show(300)) +window.addEventListener('phx:page-loading-stop', (_info) => topbar.hide()) // connect if there are any LiveViews on the page liveSocket.connect() @@ -38,4 +42,3 @@ liveSocket.connect() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket - diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index b927b7c..fa75e8a 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -1,68 +1,91 @@ // See the Tailwind configuration guide for advanced usage // https://tailwindcss.com/docs/configuration -const plugin = require("tailwindcss/plugin") -const fs = require("fs") -const path = require("path") +const plugin = require('tailwindcss/plugin') +const fs = require('fs') +const path = require('path') module.exports = { content: [ - "./js/**/*.js", - "../lib/invoice_app_web.ex", - "../lib/invoice_app_web/**/*.*ex" + './js/**/*.js', + '../lib/invoice_app_web.ex', + '../lib/invoice_app_web/**/*.*ex', ], theme: { extend: { colors: { - brand: "#FD4F00", - } + brand: '#FD4F00', + }, }, }, plugins: [ - require("@tailwindcss/forms"), + require('@tailwindcss/forms'), // Allows prefixing tailwind classes with LiveView classes to add rules // only when LiveView classes are applied, for example: // //