├── .github ├── config │ ├── trivyignore │ ├── markdown-lint-check.json │ ├── secretlintrc.json │ ├── markdown-link-check.json │ ├── megalinter.yaml │ ├── clj-kondo-ci-config.edn │ └── markdown-lint.jsonc ├── CODEOWNERS ├── FUNDING.yaml ├── pull_request_template.md └── workflows │ ├── changelog-check.yaml │ ├── lint-review.yaml │ ├── scheduled-version-check.yaml │ ├── quality-checks.yaml │ └── megalinter.yaml ├── .dir-locals.el ├── .gitattributes ├── tests.edn ├── .dockerignore ├── compose.yaml ├── CHANGELOG.md ├── dev ├── portal.clj ├── mulog_events.clj ├── system_repl.clj └── user.clj ├── .gitignore ├── src └── practicalli │ └── gameboard │ ├── spec.clj │ ├── middleware.clj │ ├── api │ ├── system_admin.clj │ ├── scoreboard.clj.template │ └── scoreboard.clj │ ├── service.clj │ ├── system.clj │ └── router.clj ├── test └── practicalli │ └── gameboard │ └── service_test.clj ├── pom.xml ├── .cljstyle ├── deps.edn ├── Dockerfile ├── README.md ├── Makefile └── LICENSE /.github/config/trivyignore: -------------------------------------------------------------------------------- 1 | # Accept the risk 2 | CVE-2024-22871 3 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((clojure-mode . ((cider-preferred-build-tool . clojure-cli) 2 | (cider-clojure-cli-aliases . ":build:test/env:dev/reloaded")))) 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Codeowners 2 | 3 | # Default owner accounts for the current repository 4 | # Automatically added as a reviewr to all pull requests (not including drafts) 5 | 6 | * @Practicalli 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Git Attributes 2 | 3 | # reclassifies `.edn` as Clojure files for Linguist statistics 4 | # https://github.com/github/linguist/blob/master/docs/overrides.md 5 | **/*.edn linguist-language=Clojure 6 | -------------------------------------------------------------------------------- /.github/config/markdown-lint-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectBaseUrl": "https://practical.li/clojure-web-services", 3 | "replacementPatterns": [ 4 | { 5 | "pattern": "^/", 6 | "replacement": "/github/workspace/" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; Kaocha test runner configuration 3 | ;; 4 | ;; Default configuration 5 | ;; - show current config using either command: 6 | ;; 7 | ;; make test-config 8 | ;; 9 | ;; clojure -M:test/env:test/run --print-config 10 | 11 | ;; --------------------------------------------------------- 12 | 13 | #kaocha/v1 {} 14 | -------------------------------------------------------------------------------- /.github/config/secretlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "id": "@secretlint/secretlint-rule-basicauth", 5 | "options": { 6 | "allows": [ 7 | "hostname.domain.com", 8 | "jdbc:postgresql://:port/?user=&password=", 9 | "postgres://postgres://username:password@hostname.domain.com:1234/database-name" 10 | ] 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/config/markdown-link-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^http://localhost" 5 | }, 6 | { 7 | "pattern": "^mailto:*" 8 | }, 9 | { 10 | "pattern": "^#*" 11 | }, 12 | { 13 | "pattern": "^https://127.0.0.0/" 14 | } 15 | ], 16 | "timeout": "20s", 17 | "retryOn429": true, 18 | "retryCount": 5, 19 | "fallbackRetryDelay": "30s", 20 | "aliveStatusCodes": [ 21 | 200, 22 | 206 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # ------------------------ 2 | # Docker Ignore file patterns 3 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file 4 | # 5 | # Ignore local files that match any pattern when using the COPY command in Dockerfile 6 | # reducing the amount of time required to copy files 7 | # ------------------------ 8 | 9 | 10 | # ------------------------ 11 | # Ignore all files 12 | * 13 | 14 | # ------------------------ 15 | # Include Clojure code and config 16 | !deps.edn 17 | !build.clj 18 | !Makefile 19 | !src/ 20 | !test/ 21 | !test-data/ 22 | !resources/ 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # GitHub Supported funding model platforms 3 | 4 | github: [{ { developer } }] 5 | # patreon: # Replace with a single Patreon username 6 | # open_collective: # Replace with a single Open Collective username 7 | # ko_fi: # Replace with a single Ko-fi username 8 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | # liberapay: # Replace with a single Liberapay username 11 | # issuehunt: # Replace with a single IssueHunt username 12 | # otechie: # Replace with a single Otechie username 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # --- Docker Compose Configuration --- # 3 | # - Docker Compose V2 4 | # - https://docs.docker.com/compose/compose-file/ 5 | # 6 | # Build the Clojure Service from source code 7 | # and run on port 8080 8 | # 9 | # Examples of persistence with Postgres and mysql docker images 10 | # and local data storage to facilitate data restoration 11 | 12 | name: "practicalli" 13 | 14 | services: 15 | # --- Clojure Service --- # 16 | gameboard-service: 17 | platform: linux/amd64 18 | # Build using Dockerfile - relative path or Git repository 19 | build: 20 | context: ./ # Use Dockerfile in project root 21 | environment: # host:container 22 | - COMPOSE_PROJECT_NAME 23 | ports: # host:container 24 | - 8080:8080 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # practicalli/gameboard Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 5 | 6 | * **Added** for new features 7 | * **Changed** for changes in existing functionality 8 | * **Deprecated** for soon-to-be removed features 9 | * **Resolved** resolved issue 10 | * **Security** vulnerability related change 11 | 12 | ## [Unreleased] 13 | 14 | ### Changed 15 | 16 | ## 0.1.0 - 2024-05-07 17 | 18 | ### Added 19 | 20 | * [#1](https://github.com/practicalli/clojure/issues/1) Created practicalli/gameboard project with deps-new using practicalli.template/service 21 | 22 | [Unreleased]: https://github.com/practicalli/gameboard/compare/0.1.1...HEAD 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 📓 Description 2 | 3 | _Summary of the change and link to any relevant tickets. New aliases should include details of why they are valuable_ 4 | 5 | # GitHub issues: 6 | # Resolve # 7 | # Refer # 8 | 9 | :octocat Type of change 10 | 11 | - [ ] New feature 12 | - [ ] Deprecate feature 13 | - [ ] Development workflow 14 | - [ ] Documentation 15 | - [ ] Continuous integration workflow 16 | 17 | :beetle How Has This Been Tested? 18 | 19 | - [ ] unit test 20 | - [ ] linter check 21 | - [x] GitHub Action checkers 22 | 23 | :eyes Checklist 24 | 25 | - [ ] Commits should be cryptographically signed (SSH or GPG) 26 | - [ ] Code follows the [Practicalli cljstyle configuration](https://practical.li/clojure/clojure-cli/clojure-style/#cljstyle) 27 | - [ ] Add / update alias docs and README where relevant 28 | - [ ] Changelog entry describing notable changes 29 | - [ ] Request maintainers review the PR 30 | -------------------------------------------------------------------------------- /dev/portal.clj: -------------------------------------------------------------------------------- 1 | (ns portal 2 | (:require 3 | ;; Data inspector 4 | [portal.api :as inspect])) 5 | 6 | 7 | ;; --------------------------------------------------------- 8 | ;; Start Portal and capture all evaluation results 9 | 10 | ;; Open Portal window in browser with dark theme 11 | ;; https://cljdoc.org/d/djblue/portal/0.37.1/doc/ui-concepts/themes 12 | ;; Portal options: 13 | ;; - light theme {:portal.colors/theme :portal.colors/solarized-light} 14 | ;; - dark theme {:portal.colors/theme :portal.colors/gruvbox} 15 | 16 | (def instance 17 | "Open portal window if no portal sessions have been created. 18 | A portal session is created when opening a portal window" 19 | (or (seq (inspect/sessions)) 20 | (inspect/open {:portal.colors/theme :portal.colors/gruvbox}))) 21 | 22 | ;; Add portal as tapsource (add to clojure.core/tapset) 23 | (add-tap #'portal.api/submit) 24 | ;; --------------------------------------------------------- 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ------------------------ 2 | # Clojure Project Git Ignore file patterns 3 | # 4 | # Ignore all except patterns starting with ! 5 | # Add comments on separate lines, not same line as pattern 6 | # ------------------------ 7 | 8 | # ------------------------ 9 | # Ignore everthing in root directory 10 | /* 11 | 12 | # ------------------------ 13 | # Common project files 14 | !CHANGELOG.md 15 | !README.md 16 | !LICENSE 17 | 18 | # ------------------------ 19 | # Include Clojure project & config 20 | !build.clj 21 | !deps.edn 22 | !pom.xml 23 | !dev/ 24 | !docs/ 25 | !resources/ 26 | !src/ 27 | !test/ 28 | 29 | # ------------------------ 30 | # Include Clojure tools 31 | !.cljstyle 32 | !.dir-locals.el 33 | !compose.yaml 34 | !Dockerfile 35 | !.dockerignore 36 | !.clj-kondo/config.edn 37 | !Makefile 38 | !tests.edn 39 | 40 | # ------------------------ 41 | # Include Git & CI workflow 42 | !.gitattributes 43 | !.gitignore 44 | !.github/ 45 | 46 | # ------------------------ 47 | # Include ClojureScript Figwheel 48 | !figwheel-main.edn 49 | !*.cljs.edn 50 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/spec.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.gameboard.spec 3 | ;; 4 | ;; Value specifications for gameboard 5 | ;; - API request / response validation 6 | ;; 7 | ;; Used in 8 | ;; - `practicalli.gameboard.api/scoreboard` 9 | ;; --------------------------------------------------------- 10 | 11 | 12 | (ns practicalli.gameboard.spec 13 | (:require [clojure.spec.alpha :as spec])) 14 | 15 | ;; --------------------------------------------------- 16 | ;; Value specifications 17 | 18 | (spec/def ::game-id string?) 19 | (spec/def ::game-name string?) 20 | (spec/def ::high-score string?) 21 | (spec/def ::comment string?) 22 | 23 | 24 | (spec/def ::scoreboard 25 | (spec/coll-of 26 | (spec/keys 27 | :req [::game-id ::game-name ::high-score] 28 | :opt [::comment]))) 29 | ;; --------------------------------------------------- 30 | 31 | 32 | (comment 33 | 34 | ;; true example 35 | (spec/valid? ::scoreboard [{::game-id "12345" ::game-name "Polybus" ::high-score "99999999997"}]) 36 | 37 | #_()) ; End of rich comment 38 | -------------------------------------------------------------------------------- /test/practicalli/gameboard/service_test.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.gameboard.service-test 3 | ;; 4 | ;; Example unit tests for practicalli.gameboard 5 | ;; 6 | ;; - `deftest` - test a specific function 7 | ;; - `testing` logically group assertions within a function test 8 | ;; - `is` assertion: expected value then function call 9 | ;; --------------------------------------------------------- 10 | 11 | (ns practicalli.gameboard.service-test 12 | (:require [clojure.test :refer [deftest is testing]] 13 | [practicalli.gameboard.service :as gameboard])) 14 | 15 | (deftest service-test 16 | (testing "TODO: Start with a failing test, make it pass, then refactor" 17 | 18 | ;; TODO: fix greet function to pass test 19 | (is (= "practicalli gameboard service developed by the secret engineering team" 20 | (gameboard/greet))) 21 | 22 | ;; TODO: fix test by calling greet with {:team-name "Practicalli Engineering"} 23 | (is (= (gameboard/greet "Practicalli Engineering") 24 | "practicalli gameboard service developed by the Practicalli Engineering team")))) 25 | -------------------------------------------------------------------------------- /.github/workflows/changelog-check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Check CHANGELOG.md file updated for every pull request 3 | 4 | name: Changelog Check 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - "README.md" 9 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 10 | 11 | jobs: 12 | changelog: 13 | name: Changelog Update Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 17 | - run: echo "🐧 Job running on ${{ runner.os }} server" 18 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 19 | 20 | # Git Checkout 21 | - name: Checkout Code 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 26 | 27 | # Changelog Enforcer 28 | - name: Changelog Enforcer 29 | uses: dangoslen/changelog-enforcer@v3 30 | with: 31 | changeLogPath: "CHANGELOG.md" 32 | skipLabels: "skip-changelog-check" 33 | 34 | # Summary and status 35 | - run: echo "🎨 Changelog Enforcer quality checks completed" 36 | - run: echo "🍏 Job status is ${{ job.status }}." 37 | -------------------------------------------------------------------------------- /.github/workflows/lint-review.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Clojure Lint with clj-kondo and reviewdog 3 | # 4 | # Lint errors raised as comments on pull request conversation 5 | 6 | name: Lint Review 7 | on: [pull_request] 8 | 9 | jobs: 10 | clj-kondo: 11 | name: runner / clj-kondo 12 | runs-on: ubuntu-latest 13 | steps: 14 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 15 | - run: echo "🐧 Job running on ${{ runner.os }} server" 16 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 17 | 18 | # Git Checkout 19 | - name: Checkout Code 20 | uses: actions/checkout@v4 21 | with: 22 | token: "${{ secrets.PAT || secrets.GITHUB_TOKEN }}" 23 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 24 | 25 | - name: clj-kondo 26 | uses: nnichols/clojure-lint-action@v2 27 | with: 28 | pattern: "*.clj" 29 | clj_kondo_config: ".clj-kondo/config-ci.edn" 30 | level: "error" 31 | exclude: ".cljstyle" 32 | github_token: ${{ secrets.github_token }} 33 | reporter: github-pr-review 34 | 35 | # Summary and status 36 | - run: echo "🎨 Lint Review checks completed" 37 | - run: echo "🍏 Job status is ${{ job.status }}." 38 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/middleware.clj: -------------------------------------------------------------------------------- 1 | (ns practicalli.gameboard.middleware 2 | (:require 3 | [com.brunobonacci.mulog :as mulog])) 4 | 5 | 6 | ;; -------------------------------------------------- 7 | ;; Logging middleware 8 | ;; https://github.com/BrunoBonacci/mulog/blob/master/doc/ring-tracking.md 9 | 10 | 11 | (defn wrap-trace-events 12 | "Log event trace for each api event with mulog/log." 13 | [handler id] 14 | (fn [request] 15 | ;; Add context of each request to all trace events generated for the specific request 16 | (mulog/with-context 17 | {:uri (get request :uri) 18 | :request-method (get request :request-method)} 19 | 20 | ;; track the request duration and outcome 21 | (mulog/trace :io.redefine.datawarp/http-request 22 | ;; add key/value pairs for tracking event only 23 | {:pairs [:content-type (get-in request [:headers "content-type"]) 24 | :content-encoding (get-in request [:headers "content-encoding"]) 25 | :middleware id] 26 | ;; capture http status code from the response 27 | :capture (fn [{:keys [status]}] {:http-status status})} 28 | 29 | ;; call the request handler 30 | (handler request))))) 31 | 32 | ;; -------------------------------------------------- 33 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/api/system_admin.clj: -------------------------------------------------------------------------------- 1 | ;; -------------------------------------------------- 2 | ;; System Administration and Status check 3 | ;; 4 | ;; - return service status response 5 | ;; -------------------------------------------------- 6 | 7 | 8 | (ns practicalli.gameboard.api.system-admin 9 | "Gameboard API system administration handlers" 10 | (:require [ring.util.response :refer [response]])) 11 | 12 | 13 | ;; -------------------------------------------------- 14 | ;; Status of Service 15 | 16 | (def status 17 | "Simple status report for external monitoring services, e.g. Pingdom 18 | Return: 19 | - `constantly` returns an anonymous function that returns a ring response hash-map" 20 | (constantly (response {:application "practicalli gameboard Service" :status "Alive"}))) 21 | 22 | ;; -------------------------------------------------- 23 | 24 | 25 | ;; -------------------------------------------------- 26 | ;; Router 27 | 28 | (defn routes 29 | "Reitit route configuration for system-admin endpoint" 30 | [] 31 | ["/system-admin" 32 | {:swagger {:tags ["Application Support"]}} 33 | ["/status" 34 | {:get {:summary "Status of practicalli gameboard service" 35 | :description "Ping practicalli gameboard service to see if is responding to a simple request and therefore alive" 36 | :responses {200 {:body {:application string? :status string?}}} 37 | :handler status}}]]) 38 | 39 | ;; -------------------------------------------------- 40 | -------------------------------------------------------------------------------- /.github/workflows/scheduled-version-check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # ------------------------------------------ 3 | # Scheduled check of versions 4 | # - use as non-urgent report on versions 5 | # - Uses POSIX Cron syntax 6 | # - Minute [0,59] 7 | # - Hour [0,23] 8 | # - Day of the month [1,31] 9 | # - Month of the year [1,12] 10 | # - Day of the week ([0,6] with 0=Sunday) 11 | # 12 | # Using liquidz/anta to check: 13 | # - GitHub workflows 14 | # - deps.edn 15 | # ------------------------------------------ 16 | 17 | name: "Scheduled Version Check" 18 | on: 19 | schedule: 20 | # - cron: "0 4 * * *" # at 04:04:04 ever day 21 | # - cron: "0 4 * * 5" # at 04:04:04 ever Friday 22 | - cron: "0 4 1 * *" # at 04:04:04 on first day of month 23 | workflow_dispatch: # Run manually via GitHub Actions Workflow page 24 | 25 | jobs: 26 | scheduled-version-check: 27 | name: "Scheduled Version Check" 28 | runs-on: ubuntu-latest 29 | steps: 30 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 31 | - run: echo "🐧 Job running on ${{ runner.os }} server" 32 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 33 | 34 | - name: Checkout Code 35 | uses: actions/checkout@v4 36 | - run: echo "🐙 ${{ github.repository }} repository sparse-checkout to the CI runner." 37 | - name: Antq Check Version 38 | uses: liquidz/antq-action@main 39 | with: 40 | excludes: "org.clojure/tools.deps.alpha" 41 | skips: "boot shadow-cljs leiningen" 42 | 43 | # Summary 44 | - run: echo "🎨 library versions checked with liquidz/antq" 45 | - run: echo "🍏 Job status is ${{ job.status }}." 46 | -------------------------------------------------------------------------------- /dev/mulog_events.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; Mulog Global Context and Custom Publisher 3 | ;; 4 | ;; - tap publisher for use with Portal and other tap sources 5 | ;; - publish all mulog events to Portal tap source 6 | ;; 7 | ;; NOTE: Mulog global context set in system configuration 8 | ;; --------------------------------------------------------- 9 | 10 | (ns mulog-events 11 | (:require 12 | [com.brunobonacci.mulog :as mulog] 13 | [com.brunobonacci.mulog.buffer :as mulog-buffer])) 14 | 15 | ;; --------------------------------------------------------- 16 | ;; Mulog event publishing 17 | 18 | (deftype TapPublisher 19 | [buffer transform] 20 | com.brunobonacci.mulog.publisher.PPublisher 21 | (agent-buffer [_] buffer) 22 | (publish-delay [_] 200) 23 | (publish [_ buffer] 24 | (doseq [item (transform (map second (mulog-buffer/items buffer)))] 25 | (tap> item)) 26 | (mulog-buffer/clear buffer))) 27 | 28 | #_{:clj-kondo/ignore [:unused-private-var]} 29 | (defn ^:private tap-events 30 | [{:keys [transform] :as _config}] 31 | (TapPublisher. (mulog-buffer/agent-buffer 10000) (or transform identity))) 32 | 33 | (def tap-publisher 34 | "Start mulog custom tap publisher to send all events to Portal 35 | and other tap sources 36 | `mulog-tap-publisher` to stop publisher" 37 | (mulog/start-publisher! 38 | {:type :custom, :fqn-function "mulog-events/tap-events"})) 39 | 40 | #_{:clj-kondo/ignore [:unused-public-var]} 41 | (defn stop 42 | "Stop mulog tap publisher to ensure multiple publishers are not started 43 | Recommended before using `(restart)` or evaluating the `user` namespace" 44 | [] 45 | tap-publisher) 46 | 47 | ;; Example mulog event message 48 | ;; (mulog/log ::dev-user-ns :message "Example event message" :ns (ns-publics *ns*)) 49 | ;; --------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /.github/workflows/quality-checks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Quality Checks" 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | jobs: 9 | check-code-quality: 10 | name: "Check Code Quality" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 14 | - run: echo "🐧 Job running on ${{ runner.os }} server" 15 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 16 | 17 | - name: "Checkout code" 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 22 | 23 | - name: "Prepare Java runtime" 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: "temurin" 27 | java-version: "21" 28 | 29 | - name: "Cache Clojure Dependencies" 30 | uses: actions/cache@v3 31 | with: 32 | path: | 33 | ~/.m2/repository 34 | ~/.gitlibs 35 | key: clojure-deps-${{ hashFiles('**/deps.edn') }} 36 | restore-keys: clojure-deps- 37 | 38 | - name: "Install tools" 39 | uses: DeLaGuardo/setup-clojure@12.1 40 | with: 41 | cli: 1.11.1.1435 # Clojure CLI 42 | cljstyle: 0.15.0 43 | clj-kondo: 2023.03.17 44 | 45 | - name: "Kaocha test runner" 46 | run: clojure -X:test/env:test/run 47 | 48 | - name: "Lint Clojure" 49 | run: clj-kondo --lint deps.edn --config '{:output {:pattern "::{{level}} file={{filename}},line={{row}},col={{col}}::{{message}}"}}' 50 | 51 | - name: "Check Clojure Style" 52 | run: cljstyle check --report 53 | 54 | - run: echo "🎨 style and format of Clojure code checked" 55 | 56 | - run: echo "🍏 Job status is ${{ job.status }}." 57 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/api/scoreboard.clj.template: -------------------------------------------------------------------------------- 1 | ;; -------------------------------------------------- 2 | ;; practicalli.gameboard.api.scoreboard 3 | ;; 4 | ;; Example route and handler function 5 | ;; using clojure.spec response validation 6 | ;; -------------------------------------------------- 7 | 8 | 9 | (ns practicalli.gameboard.api.scoreboard 10 | "Gameboard API Scoreboard across all games" 11 | (:require 12 | [ring.util.response :refer [response]] 13 | [clojure.spec.alpha :as spec])) 14 | 15 | 16 | ;; -------------------------------------------------- 17 | ;; Value Specifications 18 | (spec/def ::game-id string?) 19 | (spec/def ::game-name string?) 20 | (spec/def ::high-score string?) 21 | ;; -------------------------------------------------- 22 | 23 | 24 | ;; -------------------------------------------------- 25 | ;; Mock scores for the Gameboard service 26 | 27 | (def scores 28 | "Simple status report for external monitoring services, e.g. Pingdom 29 | Return: 30 | - `constantly` returns an anonymous function that returns a ring response hash-map" 31 | (constantly (response {::game-id "347938472938439487492" 32 | ::game-name "Polymous" 33 | ::high-score "344398799666"}))) 34 | ;; -------------------------------------------------- 35 | 36 | 37 | ;; -------------------------------------------------- 38 | ;; Routes 39 | 40 | (defn routes 41 | "Reitit route configuration for scoreboard endpoints 42 | Responses validated with practicalli.gameboard.spec clojure.spec" 43 | [] 44 | ["/scoreboard" 45 | {:swagger {:tags ["Scoreboard Endpoints"]} 46 | :get {:summary "Scoreboard across all games" 47 | :description "Return all the high scores for every game registered" 48 | :handler scores 49 | :responses 50 | {200 51 | {:body (spec/keys :req [::game-id ::game-name ::high-score])}}}}]) 52 | ;; -------------------------------------------------- 53 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/api/scoreboard.clj: -------------------------------------------------------------------------------- 1 | ;; -------------------------------------------------- 2 | ;; practicalli.gameboard.api.scoreboard 3 | ;; 4 | ;; Example route and handler function 5 | ;; using clojure.spec response validation 6 | ;; -------------------------------------------------- 7 | 8 | 9 | (ns practicalli.gameboard.api.scoreboard 10 | "Gameboard API Scoreboard across all games" 11 | (:require 12 | [ring.util.response :refer [response]] 13 | [clojure.spec.alpha :as spec])) 14 | 15 | 16 | ;; -------------------------------------------------- 17 | ;; Value Specifications 18 | (spec/def ::game-id string?) 19 | (spec/def ::game-name string?) 20 | (spec/def ::high-score string?) 21 | ;; -------------------------------------------------- 22 | 23 | 24 | ;; -------------------------------------------------- 25 | ;; Mock scores for the Gameboard service 26 | 27 | (def scores 28 | "Simple status report for external monitoring services, e.g. Pingdom 29 | Return: 30 | - `constantly` returns an anonymous function that returns a ring response hash-map" 31 | (constantly (response {::game-id "347938472938439487492" 32 | ::game-name "Polymous" 33 | ::high-score "344398799666"}))) 34 | ;; -------------------------------------------------- 35 | 36 | 37 | ;; -------------------------------------------------- 38 | ;; Routes 39 | 40 | (defn routes 41 | "Reitit route configuration for scoreboard endpoints 42 | Responses validated with practicalli.gameboard.spec clojure.spec" 43 | [system-config] 44 | ["/scoreboard" 45 | {:swagger {:tags ["Scoreboard Endpoints"]} 46 | :get {:summary "Scoreboard across all games" 47 | :description "Return all the high scores for every game registered" 48 | :handler scores 49 | :responses 50 | {200 51 | {:body (spec/keys :req [::game-id ::game-name ::high-score])}}}}]) 52 | ;; -------------------------------------------------- 53 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.clojars.practicalli 5 | gameboard 6 | 0.1.0-SNAPSHOT 7 | practicalli/gameboard 8 | TODO: Provide a meaningful description of the project 9 | https://github.com/practicalli/gameboard 10 | 11 | 12 | Eclipse Public License 13 | http://www.eclipse.org/legal/epl-v10.html 14 | 15 | 16 | 17 | 18 | Practicalli 19 | 20 | 21 | 22 | https://github.com/practicalli/gameboard 23 | scm:git:git://github.com/practicalli/gameboard.git 24 | scm:git:ssh://git@github.com/practicalli/gameboard.git 25 | v0.1.0-SNAPSHOT 26 | 27 | 28 | 29 | org.clojure 30 | clojure 31 | 1.11.2 32 | 33 | 34 | 35 | src 36 | 37 | 38 | 39 | clojars 40 | https://repo.clojars.org/ 41 | 42 | 43 | sonatype 44 | https://oss.sonatype.org/content/repositories/snapshots/ 45 | 46 | 47 | 48 | 49 | clojars 50 | Clojars repository 51 | https://clojars.org/repo 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /dev/system_repl.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; Donut System REPL 3 | ;; 4 | ;; Tools for REPl workflow with Donut system components 5 | ;; --------------------------------------------------------- 6 | 7 | (ns system-repl 8 | "Tools for REPl workflow with Donut system components" 9 | (:require 10 | [donut.system :as donut] 11 | [donut.system.repl :as donut-repl] 12 | [donut.system.repl.state :as donut-repl-state] 13 | [practicalli.gameboard.system :as system])) 14 | 15 | 16 | ;; --------------------------------------------------------- 17 | ;; Donut named systems 18 | ;; `:donut.system/repl` is default named system, 19 | ;; bound to `practicalli.gameboard.system` configuration 20 | (defmethod donut/named-system :donut.system/repl 21 | [_] system/main) 22 | 23 | ;; `dev` system, partially overriding main system configuration 24 | ;; to support the development workflow 25 | (defmethod donut/named-system :dev 26 | [_] (donut/system :donut.system/repl 27 | {[:env :app-env] "dev" 28 | [:env :app-version] "0.0.0-SNAPSHOT" 29 | [:services :http-server ::donut/config :options :join?] false 30 | [:services :event-log-publisher ::donut/config] 31 | {:publisher {:type :console :pretty? true}}})) 32 | ;; --------------------------------------------------------- 33 | 34 | ;; --------------------------------------------------------- 35 | ;; Donut REPL workflow helper functions 36 | 37 | (defn start 38 | "Start services using a named-system configuration, 39 | use `:dev` named-system by default" 40 | ([] (start :dev)) 41 | ([named-system] (donut-repl/start named-system))) 42 | 43 | (defn stop 44 | "Stop the currently running system" 45 | [] (donut-repl/stop)) 46 | 47 | (defn restart 48 | "Restart the system with donut repl, 49 | Uses clojure.tools.namespace.repl to reload namespaces 50 | `(clojure.tools.namespace.repl/refresh :after 'donut.system.repl/start)`" 51 | [] (donut-repl/restart)) 52 | 53 | (defn system 54 | "Return: fully qualified hash-map of system state" 55 | [] donut-repl-state/system) 56 | ;; --------------------------------------------------------- 57 | -------------------------------------------------------------------------------- /.github/workflows/megalinter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # MegaLinter GitHub Action configuration file 3 | # More info at https://megalinter.github.io 4 | # All variables described in https://megalinter.github.io/configuration/ 5 | 6 | name: MegaLinter 7 | on: 8 | workflow_dispatch: 9 | pull_request: 10 | branches: [main] 11 | push: 12 | branches: [main] 13 | 14 | # Run Linters in parallel 15 | # Cancel running job if new job is triggered 16 | concurrency: 17 | group: "${{ github.ref }}-${{ github.workflow }}" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | megalinter: 22 | name: MegaLinter 23 | runs-on: ubuntu-latest 24 | steps: 25 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 26 | - run: echo "🐧 Job running on ${{ runner.os }} server" 27 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 28 | 29 | # Git Checkout 30 | - name: Checkout Code 31 | uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 35 | 36 | # MegaLinter Configuration 37 | - name: MegaLinter Run 38 | id: ml 39 | ## latest release of major version 40 | uses: oxsecurity/megalinter/flavors/java@v7 41 | env: 42 | # ADD CUSTOM ENV VARIABLES OR DEFINE IN MEGALINTER_CONFIG file 43 | MEGALINTER_CONFIG: .github/config/megalinter.yaml 44 | 45 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # report individual linter status 46 | # Validate all source when push on main, else just the git diff with live. 47 | VALIDATE_ALL_CODEBASE: >- 48 | ${{ github.event_name == 'push' && github.ref == 'refs/heads/main'}} 49 | 50 | # Upload MegaLinter artifacts 51 | - name: Archive production artifacts 52 | if: ${{ success() }} || ${{ failure() }} 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: MegaLinter reports 56 | path: | 57 | megalinter-reports 58 | mega-linter.log 59 | 60 | # Summary and status 61 | - run: echo "🎨 MegaLinter quality checks completed" 62 | - run: echo "🍏 Job status is ${{ job.status }}." 63 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/service.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.gameboard 3 | ;; 4 | ;; TODO: Provide a meaningful description of the project 5 | ;; 6 | ;; Start the service using donut system configuration 7 | ;; defined in `system.clj` 8 | ;; 9 | ;; The service consist of 10 | ;; - httpkit web application server 11 | ;; - metosin/reitit for routing and ring for request / response management 12 | ;; - mulog event logging service 13 | ;; 14 | ;; Related namespaces 15 | ;; `practicalli.gameboard/system` donut system configuration 16 | ;; --------------------------------------------------------- 17 | 18 | 19 | (ns practicalli.gameboard.service 20 | "Gameboard service component lifecycle management" 21 | (:gen-class) 22 | (:require 23 | ;; Component system 24 | [donut.system :as donut] 25 | [practicalli.gameboard.system :as system])) 26 | 27 | 28 | ;; -------------------------------------------------- 29 | ;; Service entry point 30 | 31 | (defn -main 32 | "practicalli gameboard service managed by donut system, 33 | Aero is used to configure the donut system configuration based on profile (dev, test, prod), 34 | allowing environment specific configuration, e.g. mulog publisher 35 | The shutdown hook gracefully stops the service on receipt of a SIGTERM from the infrastructure, 36 | giving the application 30 seconds before forced termination." 37 | [] 38 | (let [profile (or (keyword (System/getenv "SERVICE_PROFILE")) 39 | :dev) 40 | 41 | ;; Reference to running system for shutdown hook 42 | running-system (donut/start (or (profile :profile) :prod))] 43 | 44 | ;; Shutdown system components on SIGTERM 45 | (.addShutdownHook 46 | (Runtime/getRuntime) 47 | (Thread. ^Runnable #(donut/signal running-system ::donut/stop))))) 48 | ;; -------------------------------------------------- 49 | 50 | 51 | ;; -------------------------------------------------- 52 | ;; Example clojure.exec function 53 | 54 | (defn greet 55 | "Greeting message via Clojure CLI clojure.exec" 56 | ;; TODO: call greet with hash-map argument 57 | ([] (greet "secret engineering")) 58 | ([{:keys [team-name]}] 59 | (str "practicalli gameboard service developed by the " team-name " team"))) 60 | 61 | 62 | (comment 63 | ;; -------------------------------------------------- 64 | ;; REPL workflow commands 65 | 66 | (greet {:team-name "Practicalli"})) 67 | 68 | ; End of rich comment 69 | -------------------------------------------------------------------------------- /.cljstyle: -------------------------------------------------------------------------------- 1 | ;; cljstyle configuration 2 | {:files 3 | {:extensions #{"cljc" "cljs" "clj" "cljx" "edn"}, 4 | :ignore #{"checkouts" "dev" ".hg" "target" ".git" "mulog_publisher.clj"}}, 5 | :rules 6 | {:namespaces 7 | {:enabled? false, 8 | :indent-size 2, 9 | :break-libs? true, 10 | :import-break-width 60}, 11 | :whitespace 12 | {:enabled? true, 13 | :remove-surrounding? true, 14 | :remove-trailing? true, 15 | :insert-missing? true}, 16 | :comments 17 | {:enabled? true, 18 | :inline-prefix " ", :leading-prefix "; "}, 19 | :functions {:enabled? true}, 20 | :eof-newline {:enabled? true}, 21 | :types 22 | {:enabled? true, 23 | :types? true, 24 | :protocols? true, 25 | :reifies? true, 26 | :proxies? true}, 27 | :blank-lines 28 | {:enabled? true, 29 | :trim-consecutive? true, 30 | :max-consecutive 2, 31 | :insert-padding? false, 32 | :padding-lines 2}, 33 | :indentation 34 | {:enabled? true, 35 | :list-indent 1, 36 | :indents 37 | { 38 | #"^def" [[:inner 0]], 39 | #"^with-" [[:inner 0]], 40 | alt! [[:block 0]], 41 | alt!! [[:block 0]], 42 | are [[:block 2]], 43 | as-> [[:block 1]], 44 | binding [[:block 1]], 45 | bound-fn [[:inner 0]], 46 | case [[:block 1]], 47 | catch [[:block 2]], 48 | comment [[:block 0]], 49 | cond [[:block 0]], 50 | cond-> [[:block 1]], 51 | cond->> [[:block 1]], 52 | condp [[:block 2]], 53 | def [[:inner 0]]}, 54 | defmacro [[:inner 0]], 55 | defmethod [[:inner 0]], 56 | defmulti [[:inner 0]], 57 | defn [[:inner 0]], 58 | defn- [[:inner 0]], 59 | defonce [[:inner 0]], 60 | defprotocol [[:block 1] [:inner 1]], 61 | defrecord [[:block 1] [:inner 1]], 62 | defstruct [[:block 1]], 63 | deftest [[:inner 0]], 64 | deftype [[:block 1] [:inner 1]], 65 | do [[:block 0]], 66 | doseq [[:block 1]], 67 | dotimes [[:block 1]], 68 | doto [[:block 1]], 69 | extend [[:block 1]], 70 | extend-protocol [[:block 1] [:inner 1]], 71 | extend-type [[:block 1] [:inner 1]], 72 | finally [[:block 0]], 73 | fn [[:inner 0]], 74 | for [[:block 1]], 75 | future [[:block 0]], 76 | go [[:block 0]], 77 | go-loop [[:block 1]], 78 | if [[:block 1]], 79 | if-let [[:block 1]], 80 | if-not [[:block 1]], 81 | if-some [[:block 1]], 82 | let [[:block 1]], 83 | letfn [[:block 1] [:inner 2 0]], 84 | locking [[:block 1]], 85 | loop [[:block 1]], 86 | match [[:block 1]], 87 | ns [[:block 1]], 88 | proxy [[:block 2] [:inner 1]], 89 | reify [[:inner 0] [:inner 1]], 90 | struct-map [[:block 1]], 91 | testing [[:block 1]], 92 | thread [[:block 0]], 93 | thrown-with-msg? [[:block 2]], 94 | thrown? [[:block 1]], 95 | try [[:block 0]], 96 | use-fixtures [[:inner 0]], 97 | when [[:block 1]], 98 | when-first [[:block 1]], 99 | when-let [[:block 1]], 100 | when-not [[:block 1]], 101 | when-some [[:block 1]], 102 | while [[:block 1]], 103 | with-local-vars [[:block 1]], 104 | with-open [[:block 1]], 105 | with-out-str [[:block 0]], 106 | with-precision [[:block 1]], 107 | with-redefs [[:block 1]]}}, 108 | :vars 109 | {:enabled? false}}} 110 | -------------------------------------------------------------------------------- /.github/config/megalinter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configuration file for MegaLinter 3 | # 4 | # General configuration: 5 | # https://oxsecurity.github.io/megalinter/configuration/ 6 | # 7 | # Specific Linters: 8 | # https://oxsecurity.github.io/megalinter/latest/supported-linters/ 9 | 10 | # ------------------------ 11 | # Linters 12 | 13 | # Run linters in parallel 14 | PARALLEL: true 15 | 16 | # ENABLE specific linters, all other linters automatically disabled 17 | ENABLE: 18 | - CLOJURE 19 | - CREDENTIALS 20 | - DOCKERFILE 21 | - MAKEFILE 22 | - MARKDOWN 23 | - GIT 24 | - SPELL 25 | - YAML 26 | - REPOSITORY 27 | 28 | # Linter specific configuration 29 | 30 | CLOJURE_CLJ_KONDO_CONFIG_FILE: ".github/config/clj-kondo-ci-config.edn" 31 | # CLOJURE_CLJ_KONDO_ARGUMENTS: "--lint deps.edn" 32 | # CLOJURE_CLJ_KONDO_FILTER_REGEX_EXCLUDE: "dev|develop" 33 | CLOJURE_CLJ_KONDO_FILTER_REGEX_EXCLUDE: "resources" 34 | 35 | # CREDENTIALS_SECRETLINT_DISABLE_ERRORS: true 36 | CREDENTIALS_SECRETLINT_CONFIG_FILE: ".github/config/secretlintrc.json" 37 | 38 | MARKDOWN_MARKDOWNLINT_CONFIG_FILE: ".github/config/markdown-lint.jsonc" 39 | MARKDOWN_MARKDOWNLINT_FILTER_REGEX_EXCLUDE: ".github/pull_request_template.md|CHANGELOG.md" 40 | # MARKDOWN_MARKDOWNLINT_DISABLE_ERRORS: true 41 | MARKDOWN_MARKDOWN_LINK_CHECK_CONFIG_FILE: ".github/config/markdown-link-check.json" 42 | # MARKDOWN_MARKDOWN_LINK_CHECK_CLI_LINT_MODE: "project" 43 | # MARKDOWN_MARKDOWN_LINK_CHECK_DISABLE_ERRORS: true 44 | MARKDOWN_REMARK_LINT_DISABLE_ERRORS: true 45 | # MARKDOWN_MARKDOWN_TABLE_FORMATTER_DISABLE_ERRORS: true 46 | 47 | REPOSITORY_TRUFFLEHOG_DISABLE_ERRORS: true # Errors only as warnings 48 | # REPOSITORY_TRIVY_DISABLE_ERRORS: true # Errors only as warnings 49 | REPOSITORY_TRIVY_ARGUMENTS: --ignorefile ".github/config/trivyignore" 50 | 51 | # SPELL_CSPELL_DISABLE_ERRORS: true 52 | SPELL_MISSPELL_DISABLE_ERRORS: true 53 | SPELL_LYCHEE_DISABLE_ERRORS: true # Errors are only warnings 54 | 55 | # YAML_PRETTIER_FILTER_REGEX_EXCLUDE: (docs/) 56 | # YAML_YAMLLINT_FILTER_REGEX_EXCLUDE: (docs/) 57 | 58 | # Explicitly disable linters to ensure they are never run 59 | # DISABLE: 60 | # - COPYPASTE # checks for excessive copy-pastes 61 | # - SPELL # spell checking - often creates many false positives 62 | # - CSS # 63 | 64 | # Disable linter features 65 | DISABLE_LINTERS: 66 | - YAML_PRETTIER # draconian format rules 67 | - SPELL_CSPELL # many clojure references causing false positives 68 | - YAML_YAMLLINT # vague error mesages, investigation required 69 | - REPOSITORY_GIT_DIFF # warnings about LF to CRLF 70 | - REPOSITORY_SECRETLINT # reporting errors in its own config file 71 | # - REPOSITORY_DEVSKIM # unnecessary URL TLS checks 72 | - REPOSITORY_CHECKOV # fails on root user in Dockerfile 73 | - REPOSITORY_SECRETLINT 74 | 75 | # Ignore all errors and return without error status 76 | # DISABLE_ERRORS: true 77 | 78 | # ------------------------ 79 | 80 | # ------------------------ 81 | # Reporting 82 | 83 | # Activate sources reporter 84 | UPDATED_SOURCES_REPORTER: false 85 | 86 | # Show Linter timings in summary table at end of run 87 | SHOW_ELAPSED_TIME: true 88 | 89 | # Upload reports to file.io 90 | FILEIO_REPORTER: false 91 | # ------------------------ 92 | 93 | # ------------------------ 94 | # Over-ride errors 95 | 96 | # detect errors but do not block CI passing 97 | # DISABLE_ERRORS: true 98 | # ------------------------ 99 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {;; --------------------------------------------------------- 2 | :paths 3 | ["src" "resources"] 4 | ;; --------------------------------------------------------- 5 | 6 | ;; --------------------------------------------------------- 7 | :deps 8 | {;; Service 9 | http-kit/http-kit {:mvn/version "2.6.0"} ; latest "2.7.0-alpha1" 10 | metosin/reitit {:mvn/version "0.5.13"} 11 | metosin/reitit-dev {:mvn/version "0.5.18"} ; human readable exceptions 12 | 13 | ;; Logging 14 | ;; create events and send to publisher 15 | com.brunobonacci/mulog {:mvn/version "0.9.0"} 16 | ;; JSON Console out support 17 | com.brunobonacci/mulog-adv-console {:mvn/version "0.9.0"} 18 | ;; Optional: suppress slf4j warning 19 | ;; org.slf4j/slf4j-nop {:mvn/version "1.7.32"} 20 | 21 | ;; System 22 | aero/aero {:mvn/version "1.1.6"} 23 | party.donut/system {:mvn/version "0.0.241"} 24 | org.clojure/clojure {:mvn/version "1.11.2"}} 25 | ;; --------------------------------------------------------- 26 | 27 | ;; --------------------------------------------------------- 28 | :aliases 29 | {;; ------------ 30 | ;; Practicalli REPL Reloaded workflow 31 | ;; Rich Terminal REPL Prompt with nREPL and Portal connections 32 | ;; https://practical.li/clojure/clojure-cli/repl-reloaded/ 33 | ;; clojure -M:repl/reloaded 34 | :repl/reloaded 35 | {:extra-paths ["dev" "test"] 36 | :extra-deps {nrepl/nrepl {:mvn/version "1.1.0"} 37 | cider/cider-nrepl {:mvn/version "0.45.0"} 38 | com.bhauman/rebel-readline {:mvn/version "0.1.4"} 39 | djblue/portal {:mvn/version "0.51.1"} ; portal data inspector 40 | clj-commons/clj-yaml {:mvn/version "1.0.27"} ; portal yaml support (optional) 41 | org.clojure/tools.namespace {:mvn/version "1.4.5"} 42 | org.clojure/tools.trace {:mvn/version "0.7.11"} 43 | org.slf4j/slf4j-nop {:mvn/version "2.0.12"} 44 | com.brunobonacci/mulog {:mvn/version "0.9.0"} 45 | lambdaisland/kaocha {:mvn/version "1.87.1366"} 46 | org.clojure/test.check {:mvn/version "1.1.1"} 47 | ring/ring-mock {:mvn/version "0.4.0"} 48 | criterium/criterium {:mvn/version "0.4.6"}} 49 | :main-opts ["--eval" "(apply require clojure.main/repl-requires)" 50 | "--main" "nrepl.cmdline" 51 | "--middleware" "[cider.nrepl/cider-middleware,portal.nrepl/wrap-portal]" 52 | "--interactive" 53 | "-f" "rebel-readline.main/-main"]} 54 | 55 | ;; Practicalli REPL Reloaded workflow 56 | ;; https://practical.li/clojure/clojure-cli/repl-reloaded/ 57 | ;; Use with editor command to start a REPL (Jack-in) to include REPL Reloaded tools 58 | :dev/reloaded 59 | {:extra-paths ["dev" "test"] 60 | :extra-deps {djblue/portal {:mvn/version "0.51.1"} ; portal data inspector 61 | clj-commons/clj-yaml {:mvn/version "1.0.27"} ; portal yaml support (optional) 62 | org.clojure/tools.namespace {:mvn/version "1.4.5"} 63 | org.clojure/tools.trace {:mvn/version "0.7.11"} 64 | org.slf4j/slf4j-nop {:mvn/version "2.0.12"} 65 | com.brunobonacci/mulog {:mvn/version "0.9.0"} 66 | lambdaisland/kaocha {:mvn/version "1.87.1366"} 67 | org.clojure/test.check {:mvn/version "1.1.1"} 68 | criterium/criterium {:mvn/version "0.4.6"}}} 69 | ;; ------------ 70 | 71 | ;; ------------ 72 | ;; Clojure.main execution of application 73 | :run/service 74 | {:main-opts ["-m" "practicalli.gameboard.service"]} 75 | 76 | ;; Clojure.exec execution of specified function 77 | :run/greet 78 | {:exec-fn practicalli.gameboard.service/greet 79 | :exec-args {:name "Clojure"}} 80 | ;; ------------ 81 | 82 | ;; ------------ 83 | ;; Add libraries and paths to support additional test tools 84 | :test/env 85 | {} 86 | 87 | ;; Test runner - local and CI 88 | ;; call with :watch? true to start file watcher and re-run tests on saved changes 89 | :test/run 90 | {:extra-paths ["test"] 91 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.87.1366"}} 92 | :main-opts ["-m" "kaocha.runner"] 93 | :exec-fn kaocha.runner/exec-fn 94 | :exec-args {:randomize? false 95 | :fail-fast? true}} 96 | ;; ------------ 97 | 98 | ;; ------------ 99 | ;; tools.build `build.clj` built script 100 | :build/task 101 | {:replace-paths ["."] 102 | :replace-deps {io.github.clojure/tools.build 103 | {:git/tag "v0.10.0" :git/sha "3a2c484"}} 104 | :ns-default build}}} 105 | ;; ------------ 106 | ;; --------------------------------------------------------- 107 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; REPL workflow development tools 3 | ;; 4 | ;; Include development tool libraries vai aliases from practicalli/clojure-cli-config 5 | ;; Start Rich Terminal UI REPL prompt: 6 | ;; `clojure -M:repl/reloaded` 7 | ;; 8 | ;; Or call clojure jack-in from an editor to start a repl 9 | ;; including the `:dev/reloaded` alias 10 | ;; - alias included in the Emacs `.dir-locals.el` file 11 | ;; --------------------------------------------------------- 12 | 13 | #_{:clj-kondo/ignore [:unused-namespace :unused-referred-var]} 14 | (ns user 15 | "Tools for REPL Driven Development" 16 | (:require 17 | ;; REPL Workflow 18 | [system-repl :refer [start stop system restart]] 19 | [clojure.tools.namespace.repl :refer [set-refresh-dirs]] 20 | [portal] ; launch portal 21 | [portal.api :as inspect] 22 | 23 | ;; Logging 24 | [com.brunobonacci.mulog :as mulog] ; Event Logging 25 | [mulog-events])) ; Global context & Tap publisher 26 | 27 | ;; --------------------------------------------------------- 28 | ;; Help 29 | 30 | (println "---------------------------------------------------------") 31 | (println "Loading custom user namespace tools...") 32 | (println "---------------------------------------------------------") 33 | 34 | (defn help 35 | [] 36 | (println "---------------------------------------------------------") 37 | (println "System components:") 38 | (println "(start) ; starts all components in system config") 39 | (println "(restart) ; read system config, reloads changed namespaces & restarts system") 40 | (println "(stop) ; shutdown all components in the system") 41 | (println "(system) ; show configuration of the running system") 42 | (println) 43 | (println "Hotload libraries: ; Clojure 1.12.x") 44 | (println "(add-lib 'library-name)") 45 | (println "(add-libs '{domain/library-name {:mvn/version \"v1.2.3\"}})") 46 | (println "(sync-deps) ; load dependencies from deps.edn") 47 | (println "- deps-* lsp snippets for adding library") 48 | (println) 49 | (println "Portal Inspector:") 50 | (println "- portal started by default, listening to all evaluations") 51 | (println "(inspect/clear) ; clear all values in portal") 52 | (println "(remove-tap #'inspect/submit) ; stop sending to portal") 53 | (println "(inspect/close) ; close portal") 54 | (println) 55 | (println "Mulog Publisher:") 56 | (println "- mulog publisher started by default") 57 | (println "(mulog-events/stop) ; stop publishing log events") 58 | (println) 59 | (println "(help) ; print help text") 60 | (println "---------------------------------------------------------")) 61 | 62 | (help) 63 | 64 | ;; End of Help 65 | ;; --------------------------------------------------------- 66 | 67 | ;; --------------------------------------------------------- 68 | ;; Avoid reloading `dev` code 69 | ;; - code in `dev` directory should be evaluated if changed to reload into repl 70 | (println 71 | "Set REPL refresh directories to " 72 | (set-refresh-dirs "src" "resources")) 73 | ;; --------------------------------------------------------- 74 | 75 | ;; --------------------------------------------------------- 76 | ;; Mulog event logging 77 | ;; `mulog-publisher` namespace used to launch tap> events to tap-source (portal) 78 | ;; and set global context for all events 79 | 80 | ;; Example mulog event message 81 | #_(mulog/log ::dev-user-ns 82 | :message "Example event from user namespace" 83 | :ns (ns-publics *ns*)) 84 | ;; --------------------------------------------------------- 85 | 86 | ;; --------------------------------------------------------- 87 | ;; Hotload libraries into running REPL 88 | ;; `deps-*` LSP snippets to add dependency forms 89 | (comment 90 | ;; Require for Clojure 1.11.x and earlier 91 | (require '[clojure.tools.deps.alpha.repl :refer [add-libs]]) 92 | (add-libs '{domain/library-name {:mvn/version "1.0.0"}}) 93 | 94 | ;; Clojure 1.12.x onward 95 | #_(add-lib 'library-name) ; find and add library 96 | #_(sync-deps) ; load dependencies in deps.edn (if not yet loaded) 97 | #_()) ; End of rich comment 98 | ;; --------------------------------------------------------- 99 | 100 | ;; --------------------------------------------------------- 101 | ;; Portal Data Inspector 102 | (comment 103 | ;; Open a portal inspector in browser window - light theme 104 | ;; (inspect/open {:portal.colors/theme :portal.colors/solarized-light}) 105 | 106 | (inspect/clear) ; Clear all values in portal window (allows garbage collection) 107 | 108 | (remove-tap #'inspect/submit) ; Remove portal from `tap>` sources 109 | 110 | (mulog-events/stop) ; stop tap publisher 111 | 112 | (inspect/close) ; Close the portal window 113 | 114 | (inspect/docs) ; View docs locally via Portal 115 | 116 | #_()) ; End of rich comment 117 | 118 | ;; --------------------------------------------------------- 119 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # Build and run Practicalli Gameboard API Service 3 | # 4 | # Author: Practicalli 5 | # 6 | # Builder image: 7 | # Official Clojure Docker image with Java 17 (eclipse-temurin) and Clojure CLI 8 | # https://hub.docker.com/_/clojure/ 9 | # 10 | # Run-time image: 11 | # Official Java Docker image with Java 17 (eclipse-temurin) 12 | # https://hub.docker.com/_/eclipse-temurin 13 | # ------------------------------------------ 14 | 15 | 16 | # ------------------------ 17 | # Setup Builder container 18 | 19 | FROM clojure:temurin-17-alpine AS builder 20 | 21 | # Set Clojure CLI version (defaults to latest release) 22 | # ENV CLOJURE_VERSION=1.11.1.1413 23 | 24 | # Create directory for project code (working directory) 25 | RUN mkdir -p /build 26 | 27 | # Set Docker working directory 28 | WORKDIR /build 29 | 30 | # Cache and install Clojure dependencies 31 | # Add before copying code to cache the layer even if code changes 32 | COPY deps.edn Makefile /build/ 33 | RUN make deps 34 | 35 | # Copy project to working directory 36 | # .dockerignore file excludes all but essential files 37 | COPY ./ /build 38 | 39 | 40 | # ------------------------ 41 | # Test and Package application via Makefile 42 | # `make all` calls `deps`, `test-ci`, `dist` and `clean` tasks 43 | # using shared library cache mounted by pipeline process 44 | 45 | 46 | # `dist` task packages Clojure service as an uberjar 47 | # - creates: /build/practicalli-gameboard-api-service.jar 48 | # - uses command `clojure -T:build uberjar` 49 | RUN make dist 50 | 51 | # End of Docker builder image 52 | # ------------------------------------------ 53 | 54 | 55 | # ------------------------------------------ 56 | # Docker container to run Practicalli Gameboard API Service 57 | # run locally using: docker-compose up --build 58 | 59 | # ------------------------ 60 | # Setup Run-time Container 61 | 62 | # Official OpenJDK Image 63 | FROM eclipse-temurin:17-alpine 64 | 65 | # Example labels for runtime docker image 66 | # LABEL org.opencontainers.image.authors="nospam+dockerfile@practicalli.net.clojars.practicalli" 67 | # LABEL net.clojars.practicalli.practicalli.gameboard="practicalli gameboard service" 68 | # LABEL io.github.practicalli.team="Practicalli Engineering Team" 69 | # LABEL version="0.1.0-SNAPSHOT" 70 | # LABEL description="practicalli gameboard service" 71 | 72 | # Add operating system packages 73 | # - dumb-init to ensure SIGTERM sent to java process running Clojure service 74 | # - Curl and jq binaries for manual running of system integration scripts 75 | # check for newer package versions: https://pkgs.alpinelinux.org/ 76 | RUN apk add --no-cache \ 77 | dumb-init~=1.2.5 \ 78 | curl~=8.0.1 \ 79 | jq~=1.6 80 | 81 | # Create Non-root group and user to run service securely 82 | RUN addgroup -S clojure && adduser -S clojure -G clojure 83 | 84 | # Create directory to contain service archive, owned by non-root user 85 | RUN mkdir -p /service && chown -R clojure. /service 86 | 87 | # Tell docker that all future commands should run as the appuser user 88 | USER clojure 89 | 90 | # Copy service archive file from Builder image 91 | WORKDIR /service 92 | COPY --from=builder /build/target/practicalli-gameboard-standalone.jar /service/ 93 | 94 | # Optional: Add System Integration testing scripts 95 | # RUN mkdir -p /service/test-scripts 96 | # COPY --from=builder /build/test-scripts/curl--* /service/test-scripts/ 97 | 98 | 99 | # ------------------------ 100 | # Set Service Environment variables 101 | 102 | # optional over-rides for Integrant configuration 103 | # ENV HTTP_SERVER_PORT= 104 | # ENV MYSQL_DATABASE= 105 | ENV SERVICE_PROFILE=prod 106 | 107 | # Expose port of HTTP Server 108 | EXPOSE 8080 109 | 110 | # ------------------------ 111 | # Run service 112 | 113 | # Docker Service heathcheck 114 | # docker inspect --format='{{json .State.Health}}' container-name 115 | # - local heathcheck defined in `compose.yaml` service definition 116 | # Heathchck options: 117 | # --interval=30s --timeout=30s --start-period=10s --retries=3 118 | # Shell: 119 | # HEALTHCHECK \ 120 | # CMD curl --fail http://localhost:8080/system-admin/status || exit 1 121 | # Exec array: 122 | HEALTHCHECK \ 123 | CMD ["curl", "--fail", "http://localhost:8080/system-admin/status"] 124 | 125 | 126 | # JDK_JAVA_OPTIONS environment variable for setting JVM options 127 | # Use JVM options that optomise running in a container 128 | # For very low latency, use the Z Garbage collector "-XX:+UseZGC" 129 | ENV JDK_JAVA_OPTIONS "-XshowSettings:system -XX:+UseContainerSupport -XX:MaxRAMPercentage=90" 130 | 131 | # Start service using dumb-init and java run-time 132 | # (overrides `jshell` entrypoint - default in eclipse-temuring image) 133 | ENTRYPOINT ["/usr/bin/dumb-init", "--"] 134 | CMD ["java", "-jar", "/service/practicalli-gameboard-standalone.jar"] 135 | 136 | 137 | # Docker Entrypoint documentation 138 | # https://docs.docker.com/engine/reference/builder/#entrypoint 139 | 140 | # $kill PID For Graceful Shutdown(SIGTERM) - can be caught for graceful shutdown 141 | # $kill -9 PID For Forceful Shutdown(SIGKILL) - process ends immeciately 142 | # SIGSTOP cannot be intercepted, process ends immediately 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # practicalli/gameboard 2 | 3 | ```none 4 | ██████╗ ██████╗ █████╗ ██████╗████████╗██╗ ██████╗ █████╗ ██╗ ██╗ ██╗ 5 | ██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██╔══██╗██║ ██║ ██║ 6 | ██████╔╝██████╔╝███████║██║ ██║ ██║██║ ███████║██║ ██║ ██║ 7 | ██╔═══╝ ██╔══██╗██╔══██║██║ ██║ ██║██║ ██╔══██║██║ ██║ ██║ 8 | ██║ ██║ ██║██║ ██║╚██████╗ ██║ ██║╚██████╗██║ ██║███████╗███████╗██║ 9 | ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ 10 | ``` 11 | 12 | ## Project Status 13 | 14 | TODO: add status badges for }} workflows and issues 15 | 16 | TODO: Provide a meaningful description of the project 17 | 18 | Project created with [deps-new](https://github.com/seancorfield/deps-new) and the [practicalli/service template](https://github.com/practicalli/project-templates) 19 | 20 | ## Run the service 21 | 22 | Run the service (clojure.main) 23 | 24 | ```shell 25 | clojure -M:run/service 26 | ``` 27 | 28 | Run the greet function (clojure.exec), optionally passing a `:name` key and value as arguments 29 | 30 | ```shell 31 | clojure -X:run/greet :name '"team name"' 32 | ``` 33 | 34 | ## Development 35 | 36 | List all the available project tasks using the `make` help 37 | 38 | ```shell 39 | make 40 | ``` 41 | 42 | > This project uses `make` tasks to run the Clojure tests, kaocha test runner and package the service into an uberjar. The `Makefile` uses `clojure` commands and arguments that can be used directly if not using `make`. 43 | 44 | ### Run Clojure REPL 45 | 46 | Start the REPL with the [Practicalli REPL Reloaded](https://practical.li/clojure/clojure-cli/repl-reloaded/) aliases to include the custom `user` namespace (`dev/user.clj`) which provides additional tools for development (IntegrantREPL, add-libs hotload, find-deps, Portal data inspector) 47 | 48 | ```shell 49 | make repl 50 | ``` 51 | 52 | Evaluate `(start)` expression at the repl prompt to start the service. Several mulog events should be published to the terminal window. 53 | 54 | Connect a clojure aware editor and start developing the code, evaluating changes as they are made. 55 | 56 | `(refresh)` will reload any changed namespaces 57 | 58 | The local nREPL server port will be printed, along with a help menu showing the REPL Reloaded tools available. 59 | 60 | > `:dev/reloaded` alias should be included in the editor command (jack-in) to start a REPL 61 | 62 | 63 | ### Live Lint checks 64 | 65 | Using an editor with Clojure LSP (or clj-kondo) integration provides diagnostic feedback on syntax and idomatic code use, as that code is typed into the editor and throughout an entire project. 66 | 67 | `.lsp/config.edn` provides exclusions where diagnostic warnings add no value, e.g. ignore unused public function warnings where those functions are part of the external interface of the project. 68 | 69 | 70 | ### Unit tests 71 | 72 | Run unit tests of the service using the kaocha test runner 73 | 74 | ```shell 75 | make test 76 | ``` 77 | 78 | > If additional libraries are required to support tests, add them to the `:test/env` alias definition in `deps.edn` 79 | 80 | `make test-watch` will run tests on file save, although changes to `template.edn` may require cancelling the test watch (Control-c) and restarting. test-watch requires Practicalli Clojure CLI Config `:test/watch` alias. 81 | 82 | ## Format Code 83 | 84 | Check the code format before pushing commits to a shared repository, using cljstyle to check the Clojure format, MegaLinter to check format of all other files and kaocha test runner to test the Clojure code. 85 | 86 | Before running the `pre-commit-check` 87 | 88 | - [install cljstyle](https://github.com/greglook/cljstyle/releases){target=_blank} 89 | - MegaLinter runs in a Docker container, so ensure Docker is running 90 | 91 | ```shell 92 | make pre-commit-check 93 | ``` 94 | 95 | Run cljstyle only 96 | 97 | - `make format-check` runs cljstyle and and prints a report if there are errors 98 | - `make format-fix` updates all files if there are errors (check the changes made via `git diff`) 99 | 100 | Run MegaLinter only 101 | 102 | - `make lint` runs all configured linters in `.github/config/megalinter.yaml` 103 | - `make lint-fix` as above and applies fixes 104 | 105 | Run Kaocha test runner only 106 | 107 | - `make test` runs all unit tests in the project, stopping at first failing test 108 | - `make test-watch` detect file changes and run all unit tests in the project, stopping at first failing test 109 | 110 | 111 | ## Deployment 112 | 113 | Build an uberjar to deploy the service as a jar file 114 | 115 | ```shell 116 | make build-uberjar 117 | ``` 118 | 119 | - `make build-config` displays the tools.build configuration 120 | - `make build-clean` deletes the build assets (`target` directory) 121 | 122 | ```shell 123 | make docker-build 124 | ``` 125 | 126 | - `make docker-down` shuts down all services started with `docker-build` 127 | - `make docker-build-clean` 128 | 129 | Or build and run the service via the multi-stage `Dockerfile` configuration as part of a CI workflow. 130 | 131 | 132 | ## License 133 | 134 | Copyright © 2024 Practicalli 135 | 136 | [Creative Commons Attribution Share-Alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/") 137 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/system.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.gameboard 3 | ;; 4 | ;; TODO: Provide a meaningful description of the project 5 | ;; 6 | ;; Start the service using donut configuration and an environment profile. 7 | ;; --------------------------------------------------------- 8 | 9 | (ns practicalli.gameboard.system 10 | "Service component lifecycle management" 11 | (:gen-class) 12 | (:require 13 | ;; Application dependencies 14 | [practicalli.gameboard.router :as router] 15 | 16 | ;; Component system 17 | [donut.system :as donut] 18 | ;; [practicalli.gameboard.parse-system :as parse-system] 19 | 20 | ;; System dependencies 21 | [org.httpkit.server :as http-server] 22 | [com.brunobonacci.mulog :as mulog])) 23 | 24 | ;; --------------------------------------------------------- 25 | ;; Donut Party System configuration 26 | 27 | (def main 28 | "System Component management with Donut" 29 | {::donut/defs 30 | ;; Option: move :env data to resources/config.edn and parse with aero reader 31 | {:env 32 | {:app-version "0.1.0" 33 | :app-env "prod" 34 | :http-port (or (System/getenv "SERVICE_HTTP_PORT") 8080) 35 | :persistence 36 | {:database-host (or (System/getenv "POSTGRES_HOST") "http://localhost") 37 | :database-port (or (System/getenv "POSTGRES_PORT") "5432") 38 | :database-username (or (System/getenv "POSTGRES_USERNAME") "clojure") 39 | :database-password (or (System/getenv "POSTGRES_PASSWORD") "clojure") 40 | :database-schema (or (System/getenv "POSTGRES_SCHEMA") "clojure")}} 41 | 42 | ;; Configure data API connections 43 | ;; TODO: example system defined with aero 44 | ;; :data-api 45 | ;; {:game-service-base-url #or [#env GAME_SERVICE_BASE_URL "http://localhost"] 46 | ;; :llamasoft-api-uri #or [#env LAMASOFT_API_URI "http://localhost"] 47 | ;; :polybus-report-uri "/report/polybus" 48 | ;; :moose-life-report-uri "/api/v1/report/moose-life" 49 | ;; :minotaur-arcade-report-uri "/api/v2/minotar-arcade" 50 | ;; :gridrunner-revolution-report-uri "/api/v1.1/gridrunner" 51 | ;; :space-giraffe-report-uri "/api/v1/games/space-giraffe"} 52 | 53 | ;; mulog publisher for a given publisher type, i.e. console, cloud-watch 54 | :event-log 55 | {:publisher 56 | #::donut{:start (fn mulog-publisher-start 57 | [{{:keys [global-context publisher]} ::donut/config}] 58 | (mulog/set-global-context! global-context) 59 | (mulog/log ::log-publish-component 60 | :publisher-config publisher 61 | :local-time (java.time.LocalDateTime/now)) 62 | (mulog/start-publisher! publisher)) 63 | 64 | :stop (fn mulog-publisher-stop 65 | [{::donut/keys [instance]}] 66 | (mulog/log ::log-publish-component-shutdown :publisher instance :local-time (java.time.LocalDateTime/now)) 67 | ;; Pause so final messages have chance to be published 68 | (Thread/sleep 250) 69 | (instance)) 70 | 71 | :config {:global-context {:app-name "practicalli gameboard service" 72 | :version (donut/ref [:env :app-version]) 73 | :environment (donut/ref [:env :app-env])} 74 | ;; Publish events to console in json format 75 | ;; optionally add `:transform` function to filter events before publishing 76 | :publisher {:type :console-json 77 | :pretty? false 78 | #_#_:transform identity}}}} 79 | 80 | ;; HTTP server start - returns function to stop the server 81 | :http 82 | {:server 83 | #::donut{:start (fn http-kit-run-server 84 | [{{:keys [handler options]} ::donut/config}] 85 | (mulog/log ::http-server-component 86 | :handler handler 87 | :port (options :port) 88 | :local-time (java.time.LocalDateTime/now)) 89 | (http-server/run-server handler options)) 90 | 91 | :stop (fn http-kit-stop-server 92 | [{::donut/keys [instance]}] 93 | (mulog/log ::http-server-component-shutdown 94 | :http-server-instance instance 95 | :local-time (java.time.LocalDateTime/now)) 96 | (instance)) 97 | 98 | :config {:handler (donut/local-ref [:handler]) 99 | :options {:port (donut/ref [:env :http-port]) 100 | :join? true}}} 101 | 102 | ;; Function handling all requests, passing system environment 103 | ;; Configure environment for router application, e.g. database connection details, etc. 104 | :handler (router/app (donut/ref [:env :persistence]))}}}) 105 | 106 | ;; End of Donut Party System configuration 107 | ;; --------------------------------------------------------- 108 | -------------------------------------------------------------------------------- /src/practicalli/gameboard/router.clj: -------------------------------------------------------------------------------- 1 | ;; -------------------------------------------------- 2 | ;; practicalli.gameboard router 3 | ;; 4 | ;; Each section of the API is defined in its own routes namespace 5 | ;; with an accompanying handler namespace 6 | ;; 7 | ;; reitit - routing and middleware 8 | ;; ring - request / response management 9 | ;; muuntaja - coercion (data transformation) 10 | ;; -------------------------------------------------- 11 | 12 | 13 | (ns practicalli.gameboard.router 14 | "API global request routing" 15 | (:require 16 | ;; Core Web Application Libraries 17 | [reitit.ring :as ring] 18 | [muuntaja.core :as muuntaja] 19 | 20 | ;; Routing middleware 21 | [reitit.ring.middleware.muuntaja :as middleware-muuntaja] 22 | [reitit.ring.middleware.parameters :as parameters] 23 | ;; [reitit.ring.middleware.exception :as exception] 24 | 25 | ;; Service middleware 26 | [practicalli.gameboard.middleware :as middleware-service] 27 | 28 | ;; Service Routing 29 | [practicalli.gameboard.api.system-admin :as system-admin] 30 | [practicalli.gameboard.api.scoreboard :as scoreboard] 31 | 32 | ;; Self-documenting API 33 | [reitit.swagger :as api-docs] 34 | [reitit.swagger-ui :as api-docs-ui] 35 | 36 | ;; Provide details of parameters to API documentation UI (swagger) 37 | [reitit.coercion.spec] 38 | [reitit.ring.coercion :as coercion] 39 | 40 | ;; Error handling 41 | [reitit.dev.pretty :as pretty] 42 | [com.brunobonacci.mulog :as mulog] ; Event Logging 43 | )) 44 | 45 | ;; -------------------------------------------------- 46 | ;; Open API documentation endpoint 47 | 48 | (def open-api-docs 49 | "Open API docs general information about the service, 50 | https://practical.li/clojure-web-services/project/gameboard/ 51 | keys composed of multiple names should use camelCase" 52 | ["/swagger.json" 53 | {:get {:no-doc true 54 | :swagger {:info {:title "practicalli gameboard Service API" 55 | :description "TODO: Provide a meaningful description of the project" 56 | :version "0.1.0" 57 | :termsOfService "https://net.clojars.practicalli/" 58 | :contact {:name "Engineering Team" 59 | :url "https://net.clojars.practicalli/wiki/engineering-team"} 60 | :license {:name "© Practicalli, 2024" 61 | :url "http://creativecommons.org/licenses/by-sa/4.0/"} 62 | :x-logo {:url "./favicon.png"}}} 63 | :handler (api-docs/create-swagger-handler)}}]) 64 | ;; -------------------------------------------------- 65 | 66 | 67 | ;; -------------------------------------------------- 68 | ;; Global route Configuration 69 | ;; - coersion and middleware applied to all routes 70 | 71 | (def router-configuration 72 | "Reitit configuration of coercion, data format transformation and middleware for all routing" 73 | {:data {:coercion reitit.coercion.spec/coercion 74 | :muuntaja muuntaja/instance 75 | :middleware [;; swagger feature for OpenAPI documentation 76 | api-docs/swagger-feature 77 | ;; query-params & form-params 78 | parameters/parameters-middleware 79 | ;; content-negotiation 80 | middleware-muuntaja/format-middleware 81 | ;; coercing response bodys 82 | coercion/coerce-response-middleware 83 | ;; coercing request parameters 84 | coercion/coerce-request-middleware 85 | ;; Pretty print exceptions 86 | coercion/coerce-exceptions-middleware 87 | ;; logging with mulog 88 | [middleware-service/wrap-trace-events :trace-events]]} 89 | ;; pretty-print reitit exceptions for human consumptions 90 | :exception pretty/exception}) 91 | 92 | ;; -------------------------------------------------- 93 | ;; Routing 94 | 95 | (defn app 96 | "Router for all requests to the Gameboard and OpenAPI documentation, 97 | using `ring-handler` to manage HTTP request and responses. 98 | Arguments: `system-config containt Integrant configuration for the running system 99 | including persistence connection to store and retrieve data" 100 | [system-config] 101 | 102 | (mulog/log ::router-app :system-config system-config) 103 | 104 | (ring/ring-handler 105 | (ring/router 106 | [;; -------------------------------------------------- 107 | ;; All routing for Gameboard service 108 | 109 | ;; OpenAPI Documentation routes 110 | open-api-docs 111 | 112 | ;; -------------------------------------------------- 113 | ;; System routes & Status 114 | ;; - `/system-admin/status` for simple service healthcheck 115 | (system-admin/routes) 116 | 117 | ;; -------------------------------------------------- 118 | ;; practicalli gameboard API routes 119 | ["/api" 120 | ["/v1" 121 | (scoreboard/routes system-config)]]] 122 | 123 | ;; End of All routing for Gameboard service 124 | ;; -------------------------------------------------- 125 | 126 | ;; -------------------------------------------------- 127 | ;; Router configuration 128 | ;; - middleware, coersion & content negotiation 129 | router-configuration) 130 | 131 | ;; -------------------------------------------------- 132 | ;; Default routes 133 | (ring/routes 134 | ;; Open API documentation as default route 135 | (api-docs-ui/create-swagger-ui-handler {:path "/"}) 136 | 137 | ;; Respond to any other route - returns blank page 138 | ;; TODO: create page template for routes not recognised 139 | (ring/create-default-handler)))) 140 | -------------------------------------------------------------------------------- /.github/config/clj-kondo-ci-config.edn: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; Clojure Linter - clj-kondo configuration for Continuous Integration 3 | ;; 4 | ;; Essential linter checks during CI workflows 5 | ;; disabling non-essential checks to optimise workflow feedback 6 | ;; --------------------------------------------------------- 7 | 8 | 9 | {;; Ignore code in comment blocks 10 | :skip-comments true 11 | 12 | :linters {:invalid-arity {:level :error 13 | :skip-args [#_riemann.test/test-stream]} 14 | :not-a-function {:level :error 15 | :skip-args [#_user/foo]} 16 | :private-call {:level :error} 17 | :inline-def {:level :error} 18 | :redundant-do {:level :off} 19 | :redundant-let {:level :warning} 20 | :cond-else {:level :off} 21 | :syntax {:level :warning} 22 | :file {:level :error} 23 | :missing-test-assertion {:level :warning} 24 | :conflicting-alias {:level :error} 25 | :duplicate-map-key {:level :error} 26 | :duplicate-set-key {:level :error} 27 | :missing-map-value {:level :error} 28 | :redefined-var {:level :off} 29 | :unreachable-code {:level :warning} 30 | :datalog-syntax {:level :off} 31 | :unbound-destructuring-default {:level :warning} 32 | :unused-binding {:level :off 33 | ;; :exclude-destructured-keys-in-fn-args false 34 | ;; :exclude-destructured-as false 35 | ;; :exclude-unused-as true 36 | } 37 | 38 | :unsorted-required-namespaces {:level :off} 39 | :unused-namespace {:level :off 40 | ;; don't warn about these namespaces: 41 | :exclude [#_clj-kondo.impl.var-info-gen]} 42 | ;; :simple-libspec true 43 | 44 | :unresolved-symbol {:level :error 45 | :exclude [;; ignore globally: 46 | #_js* 47 | ;; ignore occurrences of service and event in call to riemann.streams/where: 48 | #_(riemann.streams/where [service event]) 49 | ;; ignore all unresolved symbols in one-of: 50 | #_(clj-kondo.impl.utils/one-of) 51 | #_(user/defproject) ; ignore project.clj's defproject 52 | #_(clojure.test/are [thrown? thrown-with-msg?]) 53 | #_(cljs.test/are [thrown? thrown-with-msg?]) 54 | #_(clojure.test/is [thrown? thrown-with-msg?]) 55 | #_(cljs.test/is [thrown? thrown-with-msg?])]} 56 | :unresolved-var {:level :warning} 57 | :unresolved-namespace {:level :warning 58 | :exclude [#_foo.bar]} 59 | ;; for example: foo.bar is always loaded in a user profile 60 | 61 | :misplaced-docstring {:level :warning} 62 | :not-empty? {:level :off} 63 | :deprecated-var {:level :off 64 | #_:exclude 65 | #_{foo.foo/deprecated-fn 66 | ;; suppress warnings in the following namespaces 67 | {:namespaces [foo.bar "bar\\.*"] 68 | ;; or in these definitions: 69 | :defs [foo.baz/allowed "foo.baz/ign\\.*"]}}} 70 | :unused-referred-var {:level :off 71 | :exclude {#_#_taoensso.timbre [debug]}} 72 | :unused-private-var {:level :off} 73 | :duplicate-require {:level :warning} 74 | :refer {:level :off} 75 | :refer-all {:level :warning 76 | :exclude #{}} 77 | :use {:level :error} 78 | :missing-else-branch {:level :warning} 79 | :type-mismatch {:level :error} 80 | :missing-docstring {:level :warning} 81 | :consistent-alias {:level :off 82 | ;; warn when alias for clojure.string is 83 | ;; different from str 84 | :aliases {#_clojure.string #_str}} 85 | :unused-import {:level :off} 86 | :single-operand-comparison {:level :off} 87 | :single-logical-operand {:level :off} 88 | :single-key-in {:level :off} 89 | :missing-clause-in-try {:level :off} 90 | :missing-body-in-when {:level :off} 91 | :hook {:level :error} 92 | :format {:level :error} 93 | :shadowed-var {:level :off 94 | #_#_:suggestions {clojure.core/type tajpu 95 | clojure.core/name nomspaco} 96 | #_#_:exclude [frequencies] 97 | #_#_:include [name]} 98 | :deps.edn {:level :warning}} 99 | 100 | ;; Format the output of clj-kondo for GitHub actions 101 | :output {:pattern "::{{level}} file={{filename}},line={{row}},col={{col}}::{{message}}"}} 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # Makefile 3 | # 4 | # Consistent set of targets to support local development of Clojure 5 | # and build the Clojure service during CI deployment 6 | # 7 | # Requirements 8 | # - cljstyle 9 | # - Clojure CLI aliases 10 | # - `:env/dev` to include `dev` directory on class path 11 | # - `:env/test` to include `test` directory and libraries to support testing 12 | # - `:test/run` to run kaocha kaocha test runner and supporting paths and dependencies 13 | # - `:repl/rebel` to start a Rebel terminal UI 14 | # - `:package/uberjar` to create an uberjar for the service 15 | # - docker 16 | # - mega-linter-runner 17 | # ------------------------------------------ 18 | 19 | # .PHONY: ensures target used rather than matching file name 20 | # https://makefiletutorial.com/#phony 21 | .PHONY: all lint deps dist pre-commit-check repl test test-ci test-watch clean 22 | 23 | # ------- Makefile Variables --------- # 24 | # run help if no target specified 25 | .DEFAULT_GOAL := help 26 | 27 | # Column the target description is printed from 28 | HELP-DESCRIPTION-SPACING := 24 29 | 30 | # Tool variables 31 | MEGALINTER_RUNNER = npx mega-linter-runner --flavor java --env "'MEGALINTER_CONFIG=.github/config/megalinter.yaml'" --remove-container 32 | CLOJURE_TEST_RUNNER = clojure -M:test/env:test/run 33 | CLOJURE_EXEC_TEST_RUNNER = clojure -X:test/env:test/run 34 | 35 | DOCKER-BUILD-LOGFILE := docker-build-log-$(shell date +%y-%m-%d-%T).org 36 | 37 | # Makefile file and directory name wildcard 38 | # EDN-FILES := $(wildcard *.edn) 39 | # ------------------------------------ # 40 | 41 | # ------- Help ----------------------- # 42 | # Source: https://nedbatchelder.com/blog/201804/makefile_help_target.html 43 | 44 | help: ## Describe available tasks in Makefile 45 | @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \ 46 | sort | \ 47 | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-$(HELP-DESCRIPTION-SPACING)s\033[0m %s\n", $$1, $$2}' 48 | # ------------------------------------ # 49 | 50 | # ------- Clojure Development -------- # 51 | repl: ## Run Clojure REPL with rich terminal UI (Rebel Readline) 52 | $(info --------- Run Rebel REPL ---------) 53 | clojure -M:test/env:repl/reloaded 54 | 55 | run: ## Run Service using clojure.main 56 | $(info --------- Download test and service libraries ---------) 57 | clojure -M:run/service 58 | 59 | deps: deps.edn ## Prepare dependencies for test and dist targets 60 | $(info --------- Download test and service libraries ---------) 61 | clojure -P -M:test/run && clojure -P -T:build/task 62 | 63 | dist: build-uberjar ## Build and package Clojure service 64 | $(info --------- Build and Package Clojure service ---------) 65 | 66 | # Remove files and directories after build tasks 67 | # `-` before the command ignores any errors returned 68 | clean: ## Clean build temporary files 69 | $(info --------- Clean Clojure classpath cache ---------) 70 | - rm -rf ./.cpcache ./.clj-kondo ./.lsp 71 | # ------------------------------------ # 72 | 73 | # ------- Testing -------------------- # 74 | test-config: ## Print Kaocha test runner configuration 75 | $(info --------- Runner Configuration ---------) 76 | $(CLOJURE_TEST_RUNNER) --print-config 77 | 78 | test-profile: ## Profile unit test speed, showing 3 slowest tests 79 | $(info --------- Runner Profile Tests ---------) 80 | $(CLOJURE_TEST_RUNNER) --plugin kaocha.plugin/profiling 81 | 82 | test: ## Run unit tests - stoping on first error 83 | $(info --------- Runner for unit tests ---------) 84 | $(CLOJURE_EXEC_TEST_RUNNER) 85 | 86 | test-all: ## Run all unit tests regardless of failing tests 87 | $(info --------- Runner for all unit tests ---------) 88 | $(CLOJURE_EXEC_TEST_RUNNER) :fail-fast? false 89 | 90 | test-watch: ## Run tests when changes saved, stopping test run on first error 91 | $(info --------- Watcher for unit tests ---------) 92 | $(CLOJURE_EXEC_TEST_RUNNER) :watch? true 93 | 94 | test-watch-all: ## Run all tests when changes saved, regardless of failing tests 95 | $(info --------- Watcher for unit tests ---------) 96 | $(CLOJURE_EXEC_TEST_RUNNER) :fail-fast? false :watch? true 97 | # ------------------------------------ # 98 | 99 | # -------- Build tasks --------------- # 100 | build-config: ## Pretty print build configuration 101 | $(info --------- View current build config ---------) 102 | clojure -T:build/task config 103 | 104 | build-jar: ## Build a jar archive of Clojure project 105 | $(info --------- Build library jar ---------) 106 | clojure -T:build/task jar 107 | 108 | build-uberjar: ## Build a uberjar archive of Clojure project & Clojure runtime 109 | $(info --------- Build service Uberjar ---------) 110 | clojure -T:build/task uberjar 111 | 112 | build-clean: ## Clean build assets or given directory 113 | $(info --------- Clean Build ---------) 114 | clojure -T:build/task clean 115 | # ------------------------------------ # 116 | 117 | # ------- Code Quality --------------- # 118 | pre-commit-check: format-check lint test ## Run format, lint and test targets 119 | 120 | format-check: ## Run cljstyle to check the formatting of Clojure code 121 | $(info --------- cljstyle Runner ---------) 122 | cljstyle check 123 | 124 | format-fix: ## Run cljstyle and fix the formatting of Clojure code 125 | $(info --------- cljstyle Runner ---------) 126 | cljstyle fix 127 | 128 | lint: ## Run MegaLinter with custom configuration (node.js required) 129 | $(info --------- MegaLinter Runner ---------) 130 | $(MEGALINTER_RUNNER) 131 | 132 | lint-fix: ## Run MegaLinter with applied fixes and custom configuration (node.js required) 133 | $(info --------- MegaLinter Runner ---------) 134 | $(MEGALINTER_RUNNER) --fix 135 | 136 | lint-clean: ## Clean MegaLinter report information 137 | $(info --------- MegaLinter Clean Reports ---------) 138 | - rm -rf ./megalinter-reports 139 | # ------------------------------------ # 140 | 141 | # ------- Docker Containers ---------- # 142 | docker-build: ## Build Clojure project and run with docker compose 143 | $(info --------- Docker Compose Build ---------) 144 | docker compose up --build --detach 145 | 146 | docker-build-log: ## Build Clojure project and run with docker compose - log to file 147 | $(info --------- Docker Compose Build ---------) 148 | docker compose up --build --detach &> $(DOCKER-BUILD-LOGFILE) | tee $(DOCKER-BUILD-LOGFILE) 149 | 150 | docker-build-clean: ## Build Clojure project and run with docker compose, removing orphans 151 | $(info --------- Docker Compose Build - remove orphans ---------) 152 | docker compose up --build --remove-orphans --detach 153 | 154 | docker-down: ## Shut down containers in docker compose 155 | $(info --------- Docker Compose Down ---------) 156 | docker compose down 157 | 158 | swagger-editor: ## Start Swagger Editor in Docker 159 | $(info --------- Run Swagger Editor at locahost:8282 ---------) 160 | docker compose -f swagger-editor.yml up -d swagger-editor 161 | 162 | swagger-editor-down: ## Stop Swagger Editor in Docker 163 | $(info --------- Run Swagger Editor at locahost:8282 ---------) 164 | docker compose -f swagger-editor.yml down 165 | # ------------------------------------ # 166 | 167 | # ------ Continuous Integration ------ # 168 | # .DELETE_ON_ERROR: halts if command returns non-zero exit status 169 | # https://makefiletutorial.com/#delete_on_error 170 | 171 | # TODO: focus runner on ^:integration` tests 172 | test-ci: deps ## Test runner for integration tests 173 | $(info --------- Runner for integration tests ---------) 174 | clojure -P -X:test/env:test/run 175 | 176 | # Run tests, build & package the Clojure code and clean up afterward 177 | # `make all` used in Docker builder stage 178 | .DELETE_ON_ERROR: 179 | all: test-ci dist clean ## Call test-ci dist and clean targets, used for CI 180 | # ------------------------------------ # 181 | -------------------------------------------------------------------------------- /.github/config/markdown-lint.jsonc: -------------------------------------------------------------------------------- 1 | // Example markdownlint configuration with all properties set to their default value 2 | { 3 | 4 | // Default state for all rules 5 | "default": true, 6 | 7 | // Path to configuration file to extend 8 | "extends": null, 9 | 10 | // MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time 11 | "MD001": true, 12 | 13 | // MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading 14 | "MD002": { 15 | // Heading level 16 | "level": 1 17 | }, 18 | 19 | // MD003/heading-style/header-style - Heading style 20 | "MD003": { 21 | // Heading style 22 | "style": "consistent" 23 | }, 24 | 25 | // MD004/ul-style - Unordered list style 26 | "MD004": { 27 | // List style 28 | "style": "consistent" 29 | }, 30 | 31 | // MD005/list-indent - Inconsistent indentation for list items at the same level 32 | "MD005": true, 33 | 34 | // MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line 35 | "MD006": true, 36 | 37 | // MD007/ul-indent - Unordered list indentation 38 | "MD007": { 39 | // Spaces for indent 40 | "indent": 2, 41 | // Whether to indent the first level of the list 42 | "start_indented": false, 43 | // Spaces for first level indent (when start_indented is set) 44 | "start_indent": 2 45 | }, 46 | 47 | // MD009/no-trailing-spaces - Trailing spaces 48 | "MD009": { 49 | // Spaces for line break 50 | "br_spaces": 2, 51 | // Allow spaces for empty lines in list items 52 | "list_item_empty_lines": false, 53 | // Include unnecessary breaks 54 | "strict": true 55 | }, 56 | 57 | // MD010/no-hard-tabs - Hard tabs 58 | "MD010": { 59 | // Include code blocks 60 | "code_blocks": true, 61 | // Fenced code languages to ignore 62 | "ignore_code_languages": [], 63 | // Number of spaces for each hard tab 64 | "spaces_per_tab": 1 65 | }, 66 | 67 | // MD011/no-reversed-links - Reversed link syntax 68 | "MD011": true, 69 | 70 | // MD012/no-multiple-blanks - Multiple consecutive blank lines 71 | "MD012": { 72 | // Consecutive blank lines 73 | "maximum": 2 74 | }, 75 | 76 | // MD013/line-length - Line length 77 | "MD013": { 78 | // Number of characters 79 | "line_length": 420, 80 | // Number of characters for headings 81 | "heading_line_length": 80, 82 | // Number of characters for code blocks 83 | "code_block_line_length": 80, 84 | // Include code blocks 85 | "code_blocks": true, 86 | // Include tables 87 | "tables": true, 88 | // Include headings 89 | "headings": true, 90 | // Include headings 91 | "headers": true, 92 | // Strict length checking 93 | "strict": false, 94 | // Stern length checking 95 | "stern": false 96 | }, 97 | 98 | // MD014/commands-show-output - Dollar signs used before commands without showing output 99 | "MD014": true, 100 | 101 | // MD018/no-missing-space-atx - No space after hash on atx style heading 102 | "MD018": true, 103 | 104 | // MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading 105 | "MD019": true, 106 | 107 | // MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading 108 | "MD020": true, 109 | 110 | // MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading 111 | "MD021": true, 112 | 113 | // MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines 114 | "MD022": { 115 | // Blank lines above heading 116 | "lines_above": 1, 117 | // Blank lines below heading 118 | "lines_below": 1 119 | }, 120 | 121 | // MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line 122 | "MD023": true, 123 | 124 | // MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content 125 | "MD024": { 126 | // Only check sibling headings 127 | "allow_different_nesting": false, 128 | // Only check sibling headings 129 | "siblings_only": false 130 | }, 131 | 132 | // MD025/single-title/single-h1 - Multiple top-level headings in the same document 133 | "MD025": { 134 | // Heading level 135 | "level": 1, 136 | // RegExp for matching title in front matter 137 | "front_matter_title": "^\\s*title\\s*[:=]" 138 | }, 139 | 140 | // MD026/no-trailing-punctuation - Trailing punctuation in heading 141 | "MD026": { 142 | // Punctuation characters not allowed at end of headings 143 | "punctuation": ".,;:!。,;:!" 144 | }, 145 | 146 | // MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol 147 | "MD027": true, 148 | 149 | // MD028/no-blanks-blockquote - Blank line inside blockquote 150 | "MD028": true, 151 | 152 | // MD029/ol-prefix - Ordered list item prefix 153 | "MD029": { 154 | // List style 155 | "style": "one_or_ordered" 156 | }, 157 | 158 | // MD030/list-marker-space - Spaces after list markers 159 | "MD030": { 160 | // Spaces for single-line unordered list items 161 | "ul_single": 1, 162 | // Spaces for single-line ordered list items 163 | "ol_single": 1, 164 | // Spaces for multi-line unordered list items 165 | "ul_multi": 1, 166 | // Spaces for multi-line ordered list items 167 | "ol_multi": 1 168 | }, 169 | 170 | // MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines 171 | "MD031": { 172 | // Include list items 173 | "list_items": true 174 | }, 175 | 176 | // MD032/blanks-around-lists - Lists should be surrounded by blank lines 177 | "MD032": true, 178 | 179 | // MD033/no-inline-html - Inline HTML 180 | "MD033": { 181 | // Allowed elements 182 | "allowed_elements": ["iframe", "p", "div"] 183 | }, 184 | 185 | // MD034/no-bare-urls - Bare URL used 186 | "MD034": true, 187 | 188 | // MD035/hr-style - Horizontal rule style 189 | "MD035": { 190 | // Horizontal rule style 191 | "style": "consistent" 192 | }, 193 | 194 | // MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading 195 | "MD036": { 196 | // Punctuation characters 197 | "punctuation": ".,;:!?。,;:!?" 198 | }, 199 | 200 | // MD037/no-space-in-emphasis - Spaces inside emphasis markers 201 | "MD037": true, 202 | 203 | // MD038/no-space-in-code - Spaces inside code span elements 204 | "MD038": true, 205 | 206 | // MD039/no-space-in-links - Spaces inside link text 207 | "MD039": true, 208 | 209 | // MD040/fenced-code-language - Fenced code blocks should have a language specified 210 | "MD040": { 211 | // List of languages 212 | "allowed_languages": [], 213 | // Require language only 214 | "language_only": false 215 | }, 216 | 217 | // MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading 218 | "MD041": { 219 | // Heading level 220 | "level": 1, 221 | // RegExp for matching title in front matter 222 | "front_matter_title": "^\\s*title\\s*[:=]" 223 | }, 224 | 225 | // MD042/no-empty-links - No empty links 226 | "MD042": true, 227 | 228 | // MD043/required-headings/required-headers - Required heading structure 229 | "MD043": { 230 | }, 231 | 232 | // MD044/proper-names - Proper names should have the correct capitalization 233 | "MD044": { 234 | // List of proper names 235 | "names": [], 236 | // Include code blocks 237 | "code_blocks": true, 238 | // Include HTML elements 239 | "html_elements": true 240 | }, 241 | 242 | // MD045/no-alt-text - Images should have alternate text (alt text) 243 | "MD045": true, 244 | 245 | // MD046/code-block-style - Code block style 246 | "MD046": { 247 | // Block style 248 | "style": "consistent" 249 | }, 250 | 251 | // MD047/single-trailing-newline - Files should end with a single newline character 252 | "MD047": true, 253 | 254 | // MD048/code-fence-style - Code fence style 255 | "MD048": { 256 | // Code fence style 257 | "style": "consistent" 258 | }, 259 | 260 | // MD049/emphasis-style - Emphasis style should be consistent 261 | "MD049": { 262 | // Emphasis style should be consistent 263 | "style": "consistent" 264 | }, 265 | 266 | // MD050/strong-style - Strong style should be consistent 267 | "MD050": { 268 | // Strong style should be consistent 269 | "style": "consistent" 270 | }, 271 | 272 | // MD051/link-fragments - Link fragments should be valid 273 | "MD051": true, 274 | 275 | // MD052/reference-links-images - Reference links and images should use a label that is defined 276 | "MD052": true, 277 | 278 | // MD053/link-image-reference-definitions - Link and image reference definitions should be needed 279 | "MD053": { 280 | // Ignored definitions 281 | "ignored_definitions": [ 282 | "//" 283 | ] 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | --------------------------------------------------------------------------------