├── doc └── intro.md ├── .dir-locals.el ├── .github ├── CODEOWNERS ├── config │ ├── markdown-lint-check.json │ ├── secretlintrc.json │ ├── markdown-link-check.json │ ├── megalinter.yaml │ ├── clj-kondo-ci-config.edn │ └── markdown-lint.jsonc ├── FUNDING.yaml ├── pull_request_template.md └── workflows │ ├── lint-review.yaml │ ├── changelog-check.yaml │ ├── scheduled-version-check.yaml │ ├── quality-checks.yaml │ └── megalinter.yaml ├── .gitattributes ├── tests.edn ├── .dockerignore ├── CHANGELOG.md ├── .gitignore ├── swagger-compose.yaml ├── dev ├── portal.clj ├── http-requests.clj ├── mulog_events.clj └── user.clj ├── test └── practicalli │ └── random_function_slack_app_test.clj ├── deps.edn ├── compose.yaml ├── src └── practicalli │ └── random_function_slack_app.clj ├── .cljstyle ├── Dockerfile ├── Makefile ├── README.md └── LICENSE /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to practicalli/random-function-slack-app 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # practicalli/random-function-slack-app 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 - 2023-08-22 17 | 18 | ### Added 19 | 20 | * [#1](https://github.com/practicalli/clojure/issues/1) Created practicalli/random-function-slack-app project with deps-new using practicalli.template/service 21 | 22 | [Unreleased]: https://github.com/practicalli/random-function-slack-app/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 | Resolve # 6 | Refer # 7 | 8 | :octocat Type of change 9 | 10 | _Please tick `x` relevant options, delete those not relevant_ 11 | 12 | - [ ] New feature 13 | - [ ] Deprecate feature 14 | - [ ] Development workflow 15 | - [ ] Documentation 16 | - [ ] Continuous integration workflow 17 | 18 | :beetle How Has This Been Tested? 19 | 20 | - [ ] unit test 21 | - [ ] linter check 22 | - [x] GitHub Action checkers 23 | 24 | :eyes Checklist 25 | 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 | -------------------------------------------------------------------------------- /.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.org 15 | !README.md 16 | 17 | # ------------------------ 18 | # Include Clojure project & config 19 | !build.clj 20 | !deps.edn 21 | !pom.xml 22 | !dev/ 23 | !docs/ 24 | !resources/ 25 | !src/ 26 | !test/ 27 | 28 | # ------------------------ 29 | # Include Clojure tools 30 | !.cljstyle 31 | !.dir-locals.el 32 | !compose.yaml 33 | !Dockerfile 34 | !.dockerignore 35 | !Makefile 36 | !tests.edn 37 | 38 | # ------------------------ 39 | # Include Git & CI workflow 40 | !.gitattributes 41 | !.gitignore 42 | !.github/ 43 | -------------------------------------------------------------------------------- /swagger-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # --- OpenAPI Swagger Compose Configuration --- # 3 | # - Docker Compose V2 4 | # - https://docs.docker.com/compose/compose-file/ 5 | # - https://hub.docker.com/r/swaggerapi/swagger-editor 6 | # 7 | # Run the Swagger editor in docker to test API specifications 8 | # in JSON or YAML formats 9 | # 10 | # Alternatively use: https://editor-next.swagger.io/ 11 | # --------------------------------------------- # 12 | 13 | 14 | # --------------------------------------------- # 15 | # Using Swagger Editor 16 | # 17 | # make swagger-editor 18 | # make swagger-editor-down 19 | # 20 | # Open: http://localhost:8282 and paste OpenAPI specification 21 | # --------------------------------------------- # 22 | 23 | # Local OpenAPI (Swagger) Editor to debug swagger open api definition 24 | services: 25 | swagger-editor: 26 | image: "swaggerapi/swagger-editor:latest" 27 | ports: 28 | - "8282:8080" 29 | -------------------------------------------------------------------------------- /dev/portal.clj: -------------------------------------------------------------------------------- 1 | (ns portal 2 | (:require 3 | ;; Data inspector 4 | [portal.api :as inspect])) 5 | 6 | ;; --------------------------------------------------------- 7 | ;; Start Portal and capture all evaluation results 8 | 9 | ;; Open Portal window in browser with dark theme 10 | ;; https://cljdoc.org/d/djblue/portal/0.37.1/doc/ui-concepts/themes 11 | ;; Portal options: 12 | ;; - light theme {:portal.colors/theme :portal.colors/solarized-light} 13 | ;; - dark theme {:portal.colors/theme :portal.colors/gruvbox} 14 | 15 | (def instance 16 | "Open portal window if no portal sessions have been created. 17 | A portal session is created when opening a portal window" 18 | (or (seq (inspect/sessions)) 19 | (inspect/open {:portal.colors/theme :portal.colors/gruvbox}))) 20 | 21 | ;; Add portal as tapsource (add to clojure.core/tapset) 22 | (add-tap #'portal.api/submit) 23 | ;; --------------------------------------------------------- 24 | -------------------------------------------------------------------------------- /test/practicalli/random_function_slack_app_test.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.random-function-slack-app.-test 3 | ;; 4 | ;; Example unit tests for practicalli.random-function-slack-app 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 | 12 | (ns practicalli.random-function-slack-app-test 13 | (:require 14 | [clojure.test :refer [deftest is testing]] 15 | [practicalli.random-function-slack-app :as random-function-slack-app])) 16 | 17 | 18 | (deftest application-test 19 | (testing "TODO: Start with a failing test, make it pass, then refactor" 20 | 21 | ;; TODO: fix greet function to pass test 22 | (is (= "practicalli application developed by the secret engineering team" 23 | (random-function-slack-app/greet))) 24 | 25 | ;; TODO: fix test by calling greet with {:team-name "Practicalli Engineering"} 26 | (is (= (random-function-slack-app/greet "Practicalli Engineering") 27 | "practicalli service developed by the Practicalli Engineering team")))) 28 | -------------------------------------------------------------------------------- /.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@v3 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 | -------------------------------------------------------------------------------- /.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 | - "CHANGELOG.md" 10 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 11 | 12 | jobs: 13 | changelog: 14 | name: Changelog Update Check 15 | runs-on: ubuntu-latest 16 | steps: 17 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 18 | - run: echo "🐧 Job running on ${{ runner.os }} server" 19 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 20 | 21 | # Git Checkout 22 | - name: Checkout Code 23 | uses: actions/checkout@v3 24 | with: 25 | token: "${{ secrets.PAT || secrets.GITHUB_TOKEN }}" 26 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 27 | 28 | # Changelog Enforcer 29 | - name: Changelog Enforcer 30 | uses: dangoslen/changelog-enforcer@v3 31 | with: 32 | changeLogPath: "CHANGELOG.md" 33 | skipLabels: "skip-changelog-check" 34 | 35 | # Summary and status 36 | - run: echo "🎨 Changelog Enforcer quality checks completed" 37 | - run: echo "🍏 Job status is ${{ job.status }}." 38 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths 2 | ["src" "resources"] 3 | 4 | :deps 5 | {;; Application 6 | org.clojure/clojure {:mvn/version "1.11.1"} 7 | http-kit/http-kit {:mvn/version "2.7.0"} 8 | 9 | ;; Logging 10 | ;; create events and send to publisher 11 | com.brunobonacci/mulog {:mvn/version "0.9.0"} 12 | ;; JSON Console out support 13 | com.brunobonacci/mulog-adv-console {:mvn/version "0.9.0"}} 14 | 15 | :aliases 16 | {;; Clojure.main execution of application 17 | :run/app 18 | {:main-opts ["-m" "practicalli.random-function-slack-app"]} 19 | 20 | ;; Clojure.exec execution of specified function 21 | :run/greet 22 | {:exec-fn practicalli.random-function-slack-app/greet 23 | :exec-args {:name "Clojure"}} 24 | 25 | ;; Add libraries and paths to support additional test tools 26 | :test/env 27 | {} 28 | 29 | ;; Test runner - local and CI 30 | ;; call with :watch? true to start file watcher and re-run tests on saved changes 31 | :test/run 32 | {:extra-paths ["test"] 33 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.85.1342"}} 34 | :main-opts ["-m" "kaocha.runner"] 35 | :exec-fn kaocha.runner/exec-fn 36 | :exec-args {:randomize? false 37 | :fail-fast? true}} 38 | 39 | ;; tools.build `build.clj` built script 40 | :build 41 | {:replace-paths ["."] 42 | :replace-deps {io.github.clojure/tools.build 43 | {:git/tag "v0.9.4" :git/sha "76b78fe"}} 44 | :ns-default build}}} 45 | -------------------------------------------------------------------------------- /dev/http-requests.clj: -------------------------------------------------------------------------------- 1 | (ns http-requests) 2 | 3 | ;; --------------------------------------------------------- 4 | #_{:clj-kondo/ignore [:redefined-var]} 5 | (comment 6 | (let [slack-authentication-token (System/getenv "SLACK_AUTHENTICATION_TOKEN")]) 7 | 8 | ;; Exceptions 9 | (client/get "http://example.com/broken" {:throw-entire-message? true}) 10 | 11 | ;;fire and forget, returns immediately[1], returned promise is ignored 12 | ;; (http/post "http://host.com/path") 13 | 14 | #_(let [response1 (http/get "http://http-kit.org/") 15 | response2 (http/get "http://clojure.org/")]) 16 | ;; Handle responses one-by-one, blocking as necessary 17 | ;; Other keys :headers :body :error :opts 18 | ;; (println "response1's status: " (:status @response1)) 19 | ;; (println "response2's status: " (:status @response2) 20 | 21 | #_(def options {:timeout 200 ; ms 22 | :basic-auth ["user" "pass"] 23 | :query-params {:param "value" :param2 ["value1" "value2"]} 24 | :user-agent "User-Agent-string" 25 | :headers {"X-Header" "Value"}}) 26 | #_(http/get "http://host.com/path" options 27 | (fn [{:keys [status headers body error]}] ;; asynchronous response handling 28 | (if error 29 | (println "Failed, exception is " error) 30 | (println "Async HTTP GET: " status)))) 31 | ; [1] may not always true, since DNS lookup maybe slow 32 | 33 | #_()) ; End of rich comment 34 | -------------------------------------------------------------------------------- /.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:00 ever day 21 | # - cron: "0 4 * * 5" # at 04:00 ever Friday 22 | - cron: "0 4 1 * *" # at 04:00 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@v3 36 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 37 | 38 | - name: "Antq Version Check" 39 | uses: liquidz/antq-action@main 40 | with: 41 | excludes: "org.clojure/tools.deps.alpha" 42 | - run: echo "🎨 library versions checked with liquidz/antq" 43 | - run: echo "🍏 Job status is ${{ job.status }}." 44 | -------------------------------------------------------------------------------- /.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@v3 19 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 20 | 21 | - name: "Prepare Java runtime" 22 | uses: actions/setup-java@v3 23 | with: 24 | distribution: "temurin" 25 | java-version: "17" 26 | 27 | - name: "Cache Clojure Dependencies" 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.m2/repository 32 | ~/.gitlibs 33 | key: clojure-deps-${{ hashFiles('**/deps.edn') }} 34 | restore-keys: clojure-deps- 35 | 36 | - name: "Install tools" 37 | uses: DeLaGuardo/setup-clojure@11.0 38 | with: 39 | cli: 1.11.1.1356 # Clojure CLI 40 | cljstyle: 0.15.0 41 | clj-kondo: 2023.03.17 42 | 43 | - name: "Kaocha test runner" 44 | run: clojure -X:test/env:test/run 45 | 46 | - name: "Lint Clojure" 47 | run: clj-kondo --lint deps.edn --config '{:output {:pattern "::{{level}} file={{filename}},line={{row}},col={{col}}::{{message}}"}}' 48 | 49 | - name: "Check Clojure Style" 50 | run: cljstyle check --report 51 | 52 | - run: echo "🎨 style and format of Clojure code checked" 53 | 54 | - run: echo "🍏 Job status is ${{ job.status }}." 55 | -------------------------------------------------------------------------------- /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 | random-function-slack-app: 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 | # depends_on: 26 | # postgres-database: 27 | # condition: service_healthy 28 | 29 | 30 | # --- Persistence Services --- # 31 | 32 | # --- Postgres Relational Database --- # 33 | # https://github.com/docker-library/docs/blob/master/postgres/README.md 34 | # postgres-database: 35 | # image: postgres:15.2-alpine 36 | # environment: 37 | # # superuser password - must not be empty 38 | # POSTGRES_PASSWORD: "$DOCKER_POSTGRES_ROOT_PASSWORD" 39 | # # Set User Credentials - optional 40 | # POSTGRES_USER: "$DOCKER_POSTGRES_USER" 41 | # POSTGRES_DB: "$DOCKER_POSTGRES_SCHEMA" 42 | # healthcheck: 43 | # test: [ "CMD", "pg_isready" ] 44 | # timeout: 45s 45 | # interval: 10s 46 | # retries: 10 47 | # ports: 48 | # - 5432:5432 49 | # Persist Postgres database schema in a docker volume 50 | # volumes: 51 | # - postgres-data:/var/lib/postgres/data 52 | # Mount project root 53 | # volumes: 54 | # postgres-data: 55 | 56 | # postgres web-based administration tool 57 | # postgres-adminer: 58 | # image: adminer 59 | # restart: always 60 | # ports: 61 | # - 8080:8080 62 | -------------------------------------------------------------------------------- /src/practicalli/random_function_slack_app.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; practicalli.random-function-slack-app 3 | ;; 4 | ;; TODO: Provide a meaningful description of the project 5 | ;; --------------------------------------------------------- 6 | 7 | 8 | (ns practicalli.random-function-slack-app 9 | (:gen-class) 10 | (:require 11 | [org.httpkit.client :as http] 12 | [com.brunobonacci.mulog :as mulog])) 13 | 14 | ;; --------------------------------------------------------- 15 | ;; Start Mulog publisher - only once 16 | (defonce mulog-publisher 17 | (mulog/start-publisher! {:type :console :pretty? true})) 18 | ;; --------------------------------------------------------- 19 | 20 | ;; --------------------------------------------------------- 21 | ;; Application 22 | 23 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} 24 | (defn greet 25 | "Greeting message via Clojure CLI clojure.exec" 26 | ([] (greet {:team-name "secret engineering"})) 27 | ([{:keys [team-name]}] 28 | (str "practicalli random-function-slack-app service developed by the " team-name " team"))) 29 | 30 | (defn -main 31 | "Entry point into the application via clojure.main -M" 32 | [& args] 33 | (let [team (first args)] 34 | (mulog/set-global-context! 35 | {:app-name "practicalli random-function-slack-app" :version "0.1.0-SNAPSHOT"}) 36 | (mulog/log ::application-starup :arguments args) 37 | (if team 38 | (greet team) 39 | (greet)))) 40 | 41 | ;; --------------------------------------------------------- 42 | 43 | ;; --------------------------------------------------------- 44 | ;; Rick Comment 45 | #_{:clj-kondo/ignore [:redefined-var]} 46 | (comment 47 | 48 | (-main) 49 | (-main {:team-name "Clojure Engineering"}) 50 | 51 | ;; Stop mulog publisher 52 | (mulog-publisher) 53 | 54 | (let [slack-authentication-token (System/getenv "SLACK_AUTHENTICATION_TOKEN")] 55 | slack-authentication-token) 56 | 57 | #_()) ; End of rich comment block 58 | ;; --------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /dev/mulog_events.clj: -------------------------------------------------------------------------------- 1 | ;; --------------------------------------------------------- 2 | ;; Mulog Global Context and Custom Publisher 3 | ;; 4 | ;; - set event log global context 5 | ;; - tap publisher for use with Portal and other tap sources 6 | ;; - publish all mulog events to Portal tap source 7 | ;; --------------------------------------------------------- 8 | 9 | (ns mulog-events 10 | (:require 11 | [com.brunobonacci.mulog :as mulog] 12 | [com.brunobonacci.mulog.buffer :as mulog-buffer])) 13 | 14 | ;; --------------------------------------------------------- 15 | ;; Set event global context 16 | ;; - information added to every event for REPL workflow 17 | (mulog/set-global-context! {:app-name "random-function-slack-app Service", 18 | :version "0.1.0", :env "dev"}) 19 | ;; --------------------------------------------------------- 20 | 21 | ;; --------------------------------------------------------- 22 | ;; Mulog event publishing 23 | 24 | (deftype TapPublisher 25 | [buffer transform] 26 | com.brunobonacci.mulog.publisher.PPublisher 27 | (agent-buffer [_] buffer) 28 | (publish-delay [_] 200) 29 | (publish [_ buffer] 30 | (doseq [item (transform (map second (mulog-buffer/items buffer)))] 31 | (tap> item)) 32 | (mulog-buffer/clear buffer))) 33 | 34 | #_{:clj-kondo/ignore [:unused-private-var]} 35 | (defn ^:private tap-events 36 | [{:keys [transform] :as _config}] 37 | (TapPublisher. (mulog-buffer/agent-buffer 10000) (or transform identity))) 38 | 39 | (def tap-publisher 40 | "Start mulog custom tap publisher to send all events to Portal 41 | and other tap sources 42 | `mulog-tap-publisher` to stop publisher" 43 | (mulog/start-publisher! 44 | {:type :custom, :fqn-function "mulog-events/tap-events"})) 45 | 46 | #_{:clj-kondo/ignore [:unused-public-var]} 47 | (defn stop 48 | "Stop mulog tap publisher to ensure multiple publishers are not started 49 | Recommended before using `(restart)` or evaluating the `user` namespace" 50 | [] 51 | tap-publisher) 52 | 53 | ;; Example mulog event message 54 | ;; (mulog/log ::dev-user-ns :message "Example event message" :ns (ns-publics *ns*)) 55 | ;; --------------------------------------------------------- 56 | -------------------------------------------------------------------------------- /.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@v3 32 | with: 33 | token: "${{ secrets.PAT || secrets.GITHUB_TOKEN }}" 34 | fetch-depth: 0 35 | - run: echo "🐙 ${{ github.repository }} repository was cloned to the runner." 36 | 37 | # MegaLinter Configuration 38 | - name: MegaLinter Run 39 | id: ml 40 | ## latest release of major version 41 | uses: oxsecurity/megalinter/flavors/java@v7 42 | env: 43 | # ADD CUSTOM ENV VARIABLES OR DEFINE IN MEGALINTER_CONFIG file 44 | MEGALINTER_CONFIG: .github/config/megalinter.yaml 45 | 46 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # report individual linter status 47 | # Validate all source when push on main, else just the git diff with live. 48 | VALIDATE_ALL_CODEBASE: >- 49 | ${{ github.event_name == 'push' && github.ref == 'refs/heads/main'}} 50 | 51 | # Upload MegaLinter artifacts 52 | - name: Archive production artifacts 53 | if: ${{ success() }} || ${{ failure() }} 54 | uses: actions/upload-artifact@v3 55 | with: 56 | name: MegaLinter reports 57 | path: | 58 | megalinter-reports 59 | mega-linter.log 60 | 61 | # Summary and status 62 | - run: echo "🎨 MegaLinter quality checks completed" 63 | - run: echo "🍏 Job status is ${{ job.status }}." 64 | -------------------------------------------------------------------------------- /.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 | 34 | # CREDENTIALS_SECRETLINT_DISABLE_ERRORS: true 35 | CREDENTIALS_SECRETLINT_CONFIG_FILE: ".github/config/secretlintrc.json" 36 | 37 | MARKDOWN_MARKDOWNLINT_CONFIG_FILE: ".github/config/markdown-lint.jsonc" 38 | MARKDOWN_MARKDOWNLINT_FILTER_REGEX_EXCLUDE: ".github/pull_request_template.md" 39 | # MARKDOWN_MARKDOWNLINT_DISABLE_ERRORS: false 40 | MARKDOWN_MARKDOWN_LINK_CHECK_CONFIG_FILE: ".github/config/markdown-link-check.json" 41 | # MARKDOWN_MARKDOWN_LINK_CHECK_CLI_LINT_MODE: "project" 42 | # MARKDOWN_MARKDOWN_LINK_CHECK_DISABLE_ERRORS: false 43 | MARKDOWN_REMARK_LINT_DISABLE_ERRORS: true 44 | # MARKDOWN_MARKDOWN_TABLE_FORMATTER_DISABLE_ERRORS: false 45 | 46 | REPOSITORY_TRUFFLEHOG_DISABLE_ERRORS: true # Errors only as warnings 47 | 48 | # SPELL_CSPELL_DISABLE_ERRORS: true 49 | SPELL_MISSPELL_DISABLE_ERRORS: true 50 | SPELL_LYCHEE_DISABLE_ERRORS: true # Errors are only warnings 51 | 52 | # YAML_PRETTIER_FILTER_REGEX_EXCLUDE: (docs/) 53 | # YAML_YAMLLINT_FILTER_REGEX_EXCLUDE: (docs/) 54 | 55 | # Explicitly disable linters to ensure they are never run 56 | # DISABLE: 57 | # - COPYPASTE # checks for excessive copy-pastes 58 | # - SPELL # spell checking - often creates many false positives 59 | # - CSS # 60 | 61 | # Disable linter features 62 | DISABLE_LINTERS: 63 | - SPELL_CSPELL # many clojure references causing false positives 64 | - YAML_YAMLLINT # vague error mesages, investigation required 65 | - REPOSITORY_GIT_DIFF # warnings about LF to CRLF 66 | - REPOSITORY_SECRETLINT # reporting errors in its own config file 67 | # - REPOSITORY_DEVSKIM # unnecessary URL TLS checks 68 | - REPOSITORY_CHECKOV # fails on root user in Dockerfile 69 | # - REPOSITORY_SECRETLINT 70 | 71 | # Ignore all errors and return without error status 72 | # DISABLE_ERRORS: true 73 | 74 | # ------------------------ 75 | 76 | # ------------------------ 77 | # Reporting 78 | 79 | # Activate sources reporter 80 | UPDATED_SOURCES_REPORTER: false 81 | 82 | # Show Linter timings in summary table at end of run 83 | SHOW_ELAPSED_TIME: true 84 | 85 | # Upload reports to file.io 86 | FILEIO_REPORTER: false 87 | 88 | # ------------------------ 89 | 90 | # ------------------------ 91 | # Over-ride errors 92 | 93 | # detect errors but do not block CI passing 94 | # DISABLE_ERRORS: true 95 | # ------------------------ 96 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | 14 | (ns user 15 | "Tools for REPL Driven Development" 16 | (:require 17 | ;; REPL Workflow 18 | [portal] ; launch portal 19 | [portal.api :as inspect] ; Data inspector 20 | [clojure.tools.namespace.repl :as namespace] 21 | 22 | ;; Logging 23 | [com.brunobonacci.mulog :as mulog] ; Event Logging 24 | [mulog-events])) ; Tap mulog events 25 | 26 | ;; --------------------------------------------------------- 27 | ;; Help 28 | 29 | (println "---------------------------------------------------------") 30 | (println "Loading custom user namespace tools...") 31 | (println "---------------------------------------------------------") 32 | 33 | (defn help 34 | [] 35 | (println "---------------------------------------------------------") 36 | (println "Namesapece Management:") 37 | (println "(namespace/refresh) ; refresh all changed namespaces") 38 | (println "(namespace/refresh-all) ; refresh all namespaces") 39 | (println) 40 | (println "Hotload libraries: ; Clojure 1.12.x") 41 | (println "(add-lib 'library-name)") 42 | (println "(add-libs '{domain/library-name {:mvn/version \"v1.2.3\"}})") 43 | (println "(sync-deps) ; load dependencies from deps.edn") 44 | (println "- deps-* lsp snippets for adding library") 45 | (println) 46 | (println "Portal Inspector:") 47 | (println "- portal started by default, listening to all evaluations") 48 | (println "(inspect/clear) ; clear all values in portal") 49 | (println "(remove-tap #'inspect/submit) ; stop sending to portal") 50 | (println "(inspect/close) ; close portal") 51 | (println) 52 | (println "(help) ; print help text") 53 | (println "---------------------------------------------------------")) 54 | 55 | (help) 56 | 57 | ;; End of Help 58 | ;; --------------------------------------------------------- 59 | 60 | ;; --------------------------------------------------------- 61 | ;; Avoid reloading `dev` code 62 | ;; - code in `dev` directory should be evaluated if changed to reload into repl 63 | (println 64 | "Set REPL refresh directories to " 65 | (namespace/set-refresh-dirs "src" "resources")) 66 | ;; --------------------------------------------------------- 67 | 68 | ;; --------------------------------------------------------- 69 | ;; Mulog event logging 70 | ;; `mulog-publisher` namespace used to launch tap> events to tap-source (portal) 71 | ;; and set global context for all events 72 | 73 | ;; Example mulog event message 74 | (mulog/log ::dev-user-ns 75 | :message "Example event from user namespace" 76 | :ns (ns-publics *ns*)) 77 | ;; --------------------------------------------------------- 78 | 79 | ;; --------------------------------------------------------- 80 | ;; Hotload libraries into running REPL 81 | ;; `deps-*` LSP snippets to add dependency forms 82 | (comment 83 | ;; Require for Clojure 1.11.x and earlier 84 | (require '[clojure.tools.deps.alpha.repl :refer [add-libs]]) 85 | (add-libs '{domain/library-name {:mvn/version "1.0.0"}}) 86 | 87 | ;; Clojure 1.12.x only 88 | #_(add-lib 'library-name) ; find and add library 89 | #_(sync-deps) ; load dependencies in deps.edn (if not yet loaded) 90 | #_()) ; End of rich comment 91 | ;; --------------------------------------------------------- 92 | 93 | ;; --------------------------------------------------------- 94 | ;; Portal Data Inspector 95 | (comment 96 | ;; Open a portal inspector in browser window - light theme 97 | ;; (inspect/open {:portal.colors/theme :portal.colors/solarized-light}) 98 | 99 | (inspect/clear) ; Clear all values in portal window (allows garbage collection) 100 | 101 | (remove-tap #'inspect/submit) ; Remove portal from `tap>` sources 102 | 103 | (mulog-tap-stop) ; stop tap publisher 104 | 105 | (inspect/close) ; Close the portal window 106 | 107 | (inspect/docs) ; View docs locally via Portal 108 | 109 | #_()) ; End of rich comment 110 | 111 | ;; --------------------------------------------------------- 112 | -------------------------------------------------------------------------------- /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.1155 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.random-function-slack-app="practicalli random-function-slack-app" 68 | # LABEL io.github.practicalli.team="Practicalli Engineering Team" 69 | # LABEL version="0.1.0-SNAPSHOT" 70 | # LABEL description="practicalli random-function-slack-app 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-random-function-slack-app-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-random-function-slack-app-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 | -------------------------------------------------------------------------------- /.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 | # Makefile file and directory name wildcard 36 | # EDN-FILES := $(wildcard *.edn) 37 | # ------------------------------------ # 38 | 39 | # ------- Help ----------------------- # 40 | # Source: https://nedbatchelder.com/blog/201804/makefile_help_target.html 41 | 42 | help: ## Describe available tasks in Makefile 43 | @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \ 44 | sort | \ 45 | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-$(HELP-DESCRIPTION-SPACING)s\033[0m %s\n", $$1, $$2}' 46 | # ------------------------------------ # 47 | 48 | # ------- Clojure Development -------- # 49 | repl: ## Run Clojure REPL with rich terminal UI (Rebel Readline) 50 | $(info --------- Run Rebel REPL ---------) 51 | clojure -M:test/env:repl/reloaded 52 | 53 | deps: deps.edn ## Prepare dependencies for test and dist targets 54 | $(info --------- Download test and service libraries ---------) 55 | clojure -P -X:build 56 | 57 | dist: build-uberjar ## Build and package Clojure service 58 | $(info --------- Build and Package Clojure service ---------) 59 | 60 | # Remove files and directories after build tasks 61 | # `-` before the command ignores any errors returned 62 | clean: ## Clean build temporary files 63 | $(info --------- Clean Clojure classpath cache ---------) 64 | - rm -rf ./.cpcache ./.clj-kondo ./.lsp 65 | # ------------------------------------ # 66 | 67 | # ------- Testing -------------------- # 68 | test-config: ## Print Kaocha test runner configuration 69 | $(info --------- Runner Configuration ---------) 70 | $(CLOJURE_TEST_RUNNER) --print-config 71 | 72 | test-profile: ## Profile unit test speed, showing 3 slowest tests 73 | $(info --------- Runner Profile Tests ---------) 74 | $(CLOJURE_TEST_RUNNER) --plugin kaocha.plugin/profiling 75 | 76 | test: ## Run unit tests - stoping on first error 77 | $(info --------- Runner for unit tests ---------) 78 | $(CLOJURE_EXEC_TEST_RUNNER) 79 | 80 | test-all: ## Run all unit tests regardless of failing tests 81 | $(info --------- Runner for all unit tests ---------) 82 | $(CLOJURE_EXEC_TEST_RUNNER) :fail-fast? false 83 | 84 | test-watch: ## Run tests when changes saved, stopping test run on first error 85 | $(info --------- Watcher for unit tests ---------) 86 | $(CLOJURE_EXEC_TEST_RUNNER) :watch? true 87 | 88 | test-watch-all: ## Run all tests when changes saved, regardless of failing tests 89 | $(info --------- Watcher for unit tests ---------) 90 | $(CLOJURE_EXEC_TEST_RUNNER) :fail-fast? false :watch? true 91 | # ------------------------------------ # 92 | 93 | # -------- Build tasks --------------- # 94 | build-config: ## Pretty print build configuration 95 | $(info --------- View current build config ---------) 96 | clojure -T:build config 97 | 98 | build-jar: ## Build a jar archive of Clojure project 99 | $(info --------- Build library jar ---------) 100 | clojure -T:build jar 101 | 102 | build-uberjar: ## Build a uberjar archive of Clojure project & Clojure runtime 103 | $(info --------- Build service Uberjar ---------) 104 | clojure -T:build uberjar 105 | 106 | build-clean: ## Clean build assets or given directory 107 | $(info --------- Clean Build ---------) 108 | clojure -T:build clean 109 | # ------------------------------------ # 110 | 111 | # ------- Code Quality --------------- # 112 | pre-commit-check: format-check lint test ## Run format, lint and test targets 113 | 114 | format-check: ## Run cljstyle to check the formatting of Clojure code 115 | $(info --------- cljstyle Runner ---------) 116 | cljstyle check 117 | 118 | format-fix: ## Run cljstyle and fix the formatting of Clojure code 119 | $(info --------- cljstyle Runner ---------) 120 | cljstyle fix 121 | 122 | lint: ## Run MegaLinter with custom configuration (node.js required) 123 | $(info --------- MegaLinter Runner ---------) 124 | $(MEGALINTER_RUNNER) 125 | 126 | lint-fix: ## Run MegaLinter with applied fixes and custom configuration (node.js required) 127 | $(info --------- MegaLinter Runner ---------) 128 | $(MEGALINTER_RUNNER) --fix 129 | 130 | lint-clean: ## Clean MegaLinter report information 131 | $(info --------- MegaLinter Clean Reports ---------) 132 | - rm -rf ./megalinter-reports 133 | # ------------------------------------ # 134 | 135 | # ------- Docker Containers ---------- # 136 | docker-build: ## Build Clojure project and run with docker compose 137 | $(info --------- Docker Compose Build ---------) 138 | docker compose up --build --detach 139 | 140 | docker-build-clean: ## Build Clojure project and run with docker compose, removing orphans 141 | $(info --------- Docker Compose Build - remove orphans ---------) 142 | docker compose up --build --remove-orphans --detach 143 | 144 | docker-down: ## Shut down containers in docker compose 145 | $(info --------- Docker Compose Down ---------) 146 | docker compose down 147 | 148 | swagger-editor: ## Start Swagger Editor in Docker 149 | $(info --------- Run Swagger Editor at locahost:8282 ---------) 150 | docker compose -f swagger-editor.yml up -d swagger-editor 151 | 152 | swagger-editor-down: ## Stop Swagger Editor in Docker 153 | $(info --------- Run Swagger Editor at locahost:8282 ---------) 154 | docker compose -f swagger-editor.yml down 155 | # ------------------------------------ # 156 | 157 | # ------ Continuous Integration ------ # 158 | # .DELETE_ON_ERROR: halts if command returns non-zero exit status 159 | # https://makefiletutorial.com/#delete_on_error 160 | 161 | # TODO: focus runner on ^:integration` tests 162 | test-ci: deps ## Test runner for integration tests 163 | $(info --------- Runner for integration tests ---------) 164 | clojure -P -X:test/env:test/run 165 | 166 | # Run tests, build & package the Clojure code and clean up afterward 167 | # `make all` used in Docker builder stage 168 | .DELETE_ON_ERROR: 169 | all: test-ci dist clean ## Call test-ci dist and clean targets, used for CI 170 | # ------------------------------------ # 171 | -------------------------------------------------------------------------------- /.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"] 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # practicalli/random-function-slack-app 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 | A Slack App to post a random function from the Clojure standard library. 17 | 18 | - `/clojure-function` posts a message to slack with a random function 19 | 20 | Project created with [deps-new](https://github.com/seancorfield/deps-new) and the [practicalli/application template](https://github.com/practicalli/project-templates) 21 | 22 | 23 | ## Slack setup 24 | 25 | Create a Slack account and follow the prompts to create a new workspace, which will be used to develop the Slack App. 26 | 27 | [Slack Quickstart](https://api.slack.com/start/quickstart) describes how to create a Slack app. 28 | 29 | [Create a new Slack app with the Slack UI](https://api.slack.com/apps) 30 | 31 | Store Slack API keys and access info in password manager. 32 | 33 | Select **From Scratch** 34 | 35 | Enter App Name and select the Development Workspace to experiment and build the app. 36 | 37 | > Regardless of development workspace, the app can be distributed to any other workspaces. 38 | 39 | ### Configure scopes 40 | 41 | Add a scope to post messages to a channel 42 | 43 | **Sidebar** > **OAuth & Permissions** > **Scopes** > **Add an OAuth Scope** 44 | 45 | - `chat:write` scope to the Bot Token to allow the app to post messages 46 | - `channels:read` scope too so your app can gain knowledge about public Slack channels 47 | 48 | > Reisntall the app if changing scopes and other features 49 | 50 | 51 | ### Update Display information 52 | 53 | > This feels like it should have been done before installing the app 54 | 55 | Provide a short & long description of the app and set background colour. Optionally add an app icon (between 512 and 2000 px in size). 56 | 57 | Short description 58 | 59 | A random function from the Clojure Standard Library 60 | 61 | Long description 62 | 63 | A random function is selected from the Clojure Standard Library and displayed along with the documentation (doc string) to explain what the function does. 64 | 65 | A bonus feature will be to provide examples of function use. 66 | 67 | 68 | ### Install app into development workspace 69 | Install your app to your Slack workspace to test it and generate the tokens you need to interact with the Slack API. You will be asked to authorize this app after clicking an install option. 70 | 71 | Sidebar > Settings > Basic Information > Install your app 72 | 73 | 74 | ### Authorization token 75 | 76 | The Authorization token for the workspace is in the [app management web page](https://api.slack.com/apps/) for the specific app 77 | 78 | **Sidebar** > **OAuth & Permissions** > **OAuth Tokens for Your Workspace** 79 | 80 | Create an environment variable to hold the authorization token with the value of the **Bot User OAuth Token** 81 | 82 | ```shell 83 | export SLACK_AUTHENTICATION_TOKEN=xxxx-123412341234-12345123451245-... 84 | ``` 85 | 86 | > Environment variables used with Clojure must be set before running the REPL, so the variables are available to the Java Virtual Machine process. 87 | > 88 | > Add the environment variables to `.bashrc` for Bash or `.zshenv` for Zsh 89 | 90 | 91 | Access tokens represent the permissions delegated to the app by the installing user. 92 | 93 | > Avoid checking access tokens into version control 94 | 95 | 96 | 97 | ## Test app - save for later... 98 | 99 | Add the app to a public channel and test its (as yet unconfigured slash command) 100 | 101 | 102 | 103 | 104 | ## Slack background info 105 | 106 | #### Scopes overview 107 | 108 | Scopes give the app permission to carry out actions, e.g. post messages, in the development workspace. 109 | 110 | Open the development workspace, either in a web page or in the Slack desktop app. 111 | 112 | **Sidebar** > **OAuth & Permissions** > **Scopes** > **Add an OAuth Scope** 113 | 114 | - `chat:write` scope to the Bot Token to allow the app to post messages 115 | - `channels:read` scope too so your app can gain knowledge about public Slack channels 116 | - `commands` scope to build a Slash command. 117 | - `incoming-webhook` scope to use Incoming Webhooks. 118 | - `chat:write.public` scope to gain the ability to post in all public channels, without joining. Otherwise, you'll need to use conversations.join, or have your app invited by a user into a channel, before you can post. 119 | - `chat:write.customize` scope to adjust the app's message authorship to make use of the username, icon_url, and icon_emoji parameters in `chat.postMessage`. 120 | 121 | 122 | > Add scopes to the Bot Token. 123 | > 124 | > Only add scopes to the User Token when the app needs to act as a specific user (e.g. post message as user, set user status, etc.) 125 | 126 | 127 | ### Slack API Methods 128 | 129 | Your access token allows you to call the methods described by the scopes you requested during installation. 130 | 131 | For example, your chat:write scope now allows your app to post messages. Your app probably isn't a member of any channels yet, so pick a channel you don't mind adding some test messages to and /invite your app. 132 | 133 | You can find the corresponding id for the channel that your app just joined by looking through the results of the conversations.list method: 134 | 135 | ```shell 136 | curl https://slack.com/api/conversations.list -H "Authorization: Bearer xoxb-1234..." 137 | ``` 138 | 139 | You'll receive a list of conversation objects. 140 | 141 | Now, post a message to the same channel your app just joined with the chat.postMessage method: 142 | 143 | ```shell 144 | curl -X POST -F channel=C1234 -F text="Reminder: we've got a softball game tonight!" https://slack.com/api/chat.postMessage -H "Authorization: Bearer xoxb-1234..." 145 | ``` 146 | 147 | Voila! We're already well on our way to putting a full-fledged Slack app on the table. 148 | 149 | [Web API Guide](https://api.slack.com/web) 150 | 151 | [API Methods list](https://api.slack.com/methods) 152 | 153 | [Interactive Workflows](https://api.slack.com/interactivity) 154 | 155 | 156 | ## Local Development 157 | 158 | Use Socket Mode to route the app interactions and events over a WebSockets connection instead sending payloads to Request URLs, the public HTTP endpoints. 159 | 160 | Socket mode is intended for internal apps that are in development or need to be deployed behind a firewall. It is not intended for widely distributed apps. 161 | 162 | Alternatively, use ngrock to redirect requests to the local app. 163 | 164 | 165 | ### Install app into workspace 166 | 167 | Install Slack app into a workspace (not the development workspace) 168 | 169 | **Sidebar** > **Install App** > **Install App To Workspace** > **Slack OAuth UI** 170 | 171 | 172 | ## Run the application 173 | 174 | Run the application (clojure.main) 175 | 176 | ```shell 177 | clojure -M:run/app 178 | ``` 179 | 180 | Run the greet function (clojure.exec), optionally passing a `:name` key and value as arguments 181 | 182 | ```shell 183 | clojure -X:run/greet :team-name '"team name"' 184 | ``` 185 | 186 | ## Development 187 | 188 | Practicalli workflow overview: 189 | 190 | - start a REPL process in a Terminal 191 | - open the project in a Clojure Editor and connected to the REPL 192 | - write code and evaluate expressions in the editor using the source code files 193 | 194 | [Practicalli Clojure CLI Config](https://practical.li/clojure/clojure-cli/practicalli-config/) should be used with this project to support all aliases used. 195 | 196 | 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 which can be used directly if not using `make`. 197 | 198 | `make` command in a terminal will list all the tasks available 199 | 200 | ```shell 201 | make 202 | ``` 203 | 204 | 205 | ### Run Clojure REPL 206 | 207 | 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 (Portal data inspector, hotload libraries, namespace reload) 208 | 209 | ```shell 210 | make repl 211 | ``` 212 | 213 | The local nREPL server port will be printed, along with a help menu showing the REPL Reloaded tools available. 214 | 215 | Evaluate the practicalli.random-function-slack-app namespace and a mulog publisher will start, sending pretty printed events to the console. Evaluate `(mulog-publisher)` to stop the mulog publisher. 216 | 217 | Call the `-main` function with or without an argument, or call the `greet` function directly passing an optional key and value pair. 218 | 219 | `(namespace/refresh)` will reload any changed namespaces in the Clojure project. 220 | 221 | 222 | ### Clojure Editor 223 | 224 | If a REPL has been run from a terminal, use the editor **connect*- feature. 225 | 226 | Otherwise, use the `:dev/reloaded` alias from Practicalli Clojure CLI Config to starting a REPL process from within a Clojure editor. 227 | 228 | 229 | ### Unit tests 230 | 231 | Run unit tests of the service using the kaocha test runner 232 | 233 | ```shell 234 | make test 235 | ``` 236 | 237 | > If additional libraries are required to support tests, add them to the `:test/env` alias definition in `deps.edn` 238 | 239 | `make test-watch` will run tests on file save, stopping the current test run on the first failing test. Tests will continue to be watched until `Ctrl-c` is pressed. 240 | 241 | ## Format Code 242 | 243 | 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. 244 | 245 | Before running the `pre-commit-check` 246 | 247 | - [install cljstyle](https://github.com/greglook/cljstyle/releases){target=_blank} 248 | - MegaLinter runs in a Docker container, so ensure Docker is running 249 | 250 | ```shell 251 | make pre-commit-check 252 | ``` 253 | 254 | Run cljstyle only 255 | 256 | - `make format-check` runs cljstyle and and prints a report if there are errors 257 | - `make format-fix` updates all files if there are errors (stage, stash or commit work forman and check format changes via `git diff`) 258 | 259 | Run MegaLinter only 260 | 261 | - `make lint` runs all configured linters in `.github/config/megalinter.yaml` 262 | - `make lint-fix` as above and applies fixes 263 | 264 | Run Kaocha test runner only 265 | 266 | - `make test` runs all unit tests in the project, stopping at first failing test 267 | - `make test-watch` detect file changes and run all unit tests in the project, stopping at first failing test 268 | 269 | 270 | ## Deployment 271 | 272 | Build an uberjar to deploy the service as a jar file 273 | 274 | ```shell 275 | make build-uberjar 276 | ``` 277 | 278 | - `make build-config` displays the tools.build configuration 279 | - `make build-clean` deletes the build assets (`target` directory) 280 | 281 | ```shell 282 | make docker-build 283 | ``` 284 | 285 | - `make docker-down` shuts down all services started with `docker-build` 286 | - `make docker-build-clean` 287 | 288 | Or build and run the service via the multi-stage `Dockerfile` configuration as part of a CI workflow. 289 | 290 | 291 | ## License 292 | 293 | Copyright © 2023 Practicalli 294 | 295 | [Creative Commons Attribution Share-Alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/") 296 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------