├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ ├── push-latest.yml │ └── push-tag.yml ├── .gitignore ├── .node-version ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CREDITS ├── Dockerfile ├── LICENSE ├── README.md ├── apidoc.json ├── bin └── www ├── config ├── default.yml ├── development.yml └── prod.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── docs ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── main.bundle.js │ ├── main.css │ ├── prism-diff-highlight.css │ ├── prism-toolbar.css │ └── prism.css └── index.html ├── img ├── initials-0.png ├── random-0.png ├── random-1.png └── random-2.png ├── package-lock.json ├── package.json ├── scripts └── publish └── src ├── app.js ├── logger.js ├── routes ├── avatar.js ├── healthz.js └── status.js ├── shapes ├── circle.js ├── rect.js └── triangle.js └── utils ├── avatar.js ├── config.js ├── initials.js ├── random.js └── respond.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | docs/ 3 | img/ 4 | log/ 5 | .editorconfig 6 | .eslintignore 7 | .eslintrc.js 8 | .gitignore 9 | .node-version 10 | .nvmrc 11 | .prettierignore 12 | .prettierrc.js 13 | apidoc.json 14 | CONTRIBUTING.md 15 | docker-compose.yml 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | node: true, 10 | }, 11 | // extends: [ 12 | // // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | // 'standard' 14 | // ], 15 | extends: [ 16 | 'eslint:recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | // required to lint *.vue files 20 | plugins: [], 21 | // add your custom rules here 22 | rules: { 23 | // allow async-await 24 | 'generator-star-spacing': 'off', 25 | // allow debugger during development 26 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Build the container image 15 | run: docker build . --file Dockerfile 16 | # --tag avatar:$(date +%s) 17 | -------------------------------------------------------------------------------- /.github/workflows/push-latest.yml: -------------------------------------------------------------------------------- 1 | name: push-latest 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | push: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: mr-smithers-excellent/docker-build-push@v6 13 | with: 14 | image: frncsdrk/avatar 15 | registry: docker.io 16 | tags: latest 17 | dockerfile: ./Dockerfile 18 | directory: ./ 19 | username: ${{ secrets.DOCKER_USERNAME }} 20 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/push-tag.yml: -------------------------------------------------------------------------------- 1 | name: push-tag 2 | 3 | on: 4 | create: 5 | tags: 6 | - 1.* 7 | # NOTE: Update on major version change 8 | 9 | jobs: 10 | push: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set env 15 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 16 | - name: Test 17 | run: | 18 | echo $RELEASE_VERSION 19 | echo ${{ env.RELEASE_VERSION }} 20 | - uses: mr-smithers-excellent/docker-build-push@v6 21 | with: 22 | image: frncsdrk/avatar 23 | registry: docker.io 24 | tags: ${{ env.RELEASE_VERSION }}, stable 25 | dockerfile: ./Dockerfile 26 | directory: ./ 27 | username: ${{ secrets.DOCKER_USERNAME }} 28 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | dist 5 | log 6 | out 7 | vol 8 | 9 | config/local* 10 | *audit.json 11 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.14.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.14.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.5.3 (2022-03-29) 4 | 5 | ### chore 6 | 7 | * Update thirdparty dependencies ([cd1f4c](https://github.com/frncsdrk/tiny-changelog/commit/cd1f4c44805ef63c4728ac2dd7f0310cf2e35e74)) 8 | 9 | ## 1.5.2 (2022-03-24) 10 | 11 | ### uncategorized 12 | 13 | * Update thirdparty dependencies ([655f29](https://github.com/frncsdrk/tiny-changelog/commit/655f29b156ee1cb1d908e5c7d8c598f0e1d63c17)) 14 | * Fix typo ([c2b49f](https://github.com/frncsdrk/tiny-changelog/commit/c2b49f5cd08685201f4332dc4318d38ece873c36)) 15 | 16 | ## 1.5.1 (2022-03-13) 17 | 18 | ### uncategorized 19 | 20 | * Update readme ([3fd4e9](https://github.com/frncsdrk/tiny-changelog/commit/3fd4e9eb82f3282a088af082267ff7f94883369a)) 21 | * Update dockerignore file ([7f47f9](https://github.com/frncsdrk/tiny-changelog/commit/7f47f983550c821e93667905dadd9b811403a0db)) 22 | * Add examples of avatars ([bcfaeb](https://github.com/frncsdrk/tiny-changelog/commit/bcfaeb56f23d4d48c2e9716b2826505ebe024eb0)) 23 | * Update service description ([b53891](https://github.com/frncsdrk/tiny-changelog/commit/b538919f0926bad781237c56d7fd9383fd98e8c5)) 24 | * Fix formatting ([70c11c](https://github.com/frncsdrk/tiny-changelog/commit/70c11cb7af6ffe53819ad92a503cb3332f71a9f6)) 25 | * Update readme ([503c58](https://github.com/frncsdrk/tiny-changelog/commit/503c580628195418d59e9b24cccfa9ce14be47fe)) 26 | 27 | ## 1.5.0 (2022-03-12) 28 | 29 | ### uncategorized 30 | 31 | * feat/api-docs (#11) ([5da7fd](https://github.com/frncsdrk/tiny-changelog/commit/5da7fd3609a3e53f15a2f8d79cc9960f435b122d)) 32 | * Update readme ([2dd330](https://github.com/frncsdrk/tiny-changelog/commit/2dd330680b4f1965dabab9e6f51bd54255a3c6f8)) 33 | * Add node version management ([4cc8d1](https://github.com/frncsdrk/tiny-changelog/commit/4cc8d1a6c8547612a8d346fc005e5a47901b122e)) 34 | * Update license ([c3814a](https://github.com/frncsdrk/tiny-changelog/commit/c3814a79e81a02530990686f8df37c058ee0ce42)) 35 | * feat/initials ([332937](https://github.com/frncsdrk/tiny-changelog/commit/3329375f5fc44c0d5f5a35f45ca4a10b717268a4)) 36 | * Update readme ([824d01](https://github.com/frncsdrk/tiny-changelog/commit/824d0142f3bb2701085aaf3f4f0f597a65ed87b5)) 37 | * Add custom positioning of initials ([a0f217](https://github.com/frncsdrk/tiny-changelog/commit/a0f2173ba94843c58e888f7310617369236a855b)) 38 | * Format code ([abd40f](https://github.com/frncsdrk/tiny-changelog/commit/abd40fe1c9d4702708aa4836fe5d22857ec1c909)) 39 | * Make initials configurable ([c10ab4](https://github.com/frncsdrk/tiny-changelog/commit/c10ab4f61d24a299c0b54439ed58c4317c1e8e99)) 40 | * Add basic calculation for centering letters ([9ff3ad](https://github.com/frncsdrk/tiny-changelog/commit/9ff3adfaa9d9463fcb85c9fb104c1c0da2ded739)) 41 | * WIP: Add initials ([1d1708](https://github.com/frncsdrk/tiny-changelog/commit/1d1708e2a73c34869592a7e174420e45a79c2c08)) 42 | * Reformat code ([42dea1](https://github.com/frncsdrk/tiny-changelog/commit/42dea150636755c61b5bcb43154f2c878cfea62e)) 43 | * Update eslint config ([c770f6](https://github.com/frncsdrk/tiny-changelog/commit/c770f632e595e86c5773fbe600e0531d27d90794)) 44 | * Fix peer dependencies ([f26fd5](https://github.com/frncsdrk/tiny-changelog/commit/f26fd5de971586322f21ba7163523f8127591dfe)) 45 | * Update thirdparty dependencies ([4aa04c](https://github.com/frncsdrk/tiny-changelog/commit/4aa04cb7fd939eb73e4deb83987e43d9625969da)) 46 | * Fix github actions ([ea9902](https://github.com/frncsdrk/tiny-changelog/commit/ea990256a201e68cb9aec116842822c453261efe)) 47 | * Switch default branch to main ([866777](https://github.com/frncsdrk/tiny-changelog/commit/866777dd7df1ee96fd5a58a698dc63db20e7261a)) 48 | * WIP: Fix github actions ([dee5fd](https://github.com/frncsdrk/tiny-changelog/commit/dee5fd1fb104c329dddfe83502b3387331e947b3)) 49 | * Fix docker push action config ([fa4c6f](https://github.com/frncsdrk/tiny-changelog/commit/fa4c6faeabe6f5178d86b8d7acd16727bd3e1ded)) 50 | * Update variables for docker push actions ([583432](https://github.com/frncsdrk/tiny-changelog/commit/5834327dfef321ac1b6d373aea63b8c80d8c9858)) 51 | * Fix typo in actions config ([de5d98](https://github.com/frncsdrk/tiny-changelog/commit/de5d98f2e13204240ff9a92613999b98c6c54d5d)) 52 | * Update docker push action release ([f4bea9](https://github.com/frncsdrk/tiny-changelog/commit/f4bea9d34f80c6113b2b9d1702c0bc9efd579206)) 53 | * Update thirdparty dependencies ([ba6726](https://github.com/frncsdrk/tiny-changelog/commit/ba672684afbc01c83e9fad9ddfec2a0baf2248e8)) 54 | * Update readme ([879bbc](https://github.com/frncsdrk/tiny-changelog/commit/879bbcb228e11c96f067b32c3f5aa6346aa6a5c3)) 55 | * Update thirdparty dependencies ([fb67b7](https://github.com/frncsdrk/tiny-changelog/commit/fb67b7b6c70753e1ab8f09f8bb76e2cc4ba65bc8)) 56 | * Update thirdparty dependencies ([3e52dc](https://github.com/frncsdrk/tiny-changelog/commit/3e52dc773e98bddd6c923b61bbf54d4799ea7bfe)) 57 | * Update thirdparty dependencies ([37f234](https://github.com/frncsdrk/tiny-changelog/commit/37f234756813800b3f1ad5f4dd622a747007b424)) 58 | * Update dockerignore ([e264b4](https://github.com/frncsdrk/tiny-changelog/commit/e264b4ccab34c2c8aa238a8fd1de85a0355bae76)) 59 | 60 | ## 1.4.7 (2021-08-31) 61 | 62 | ### uncategorized 63 | 64 | * Update thirdparty dependencies ([3c3607](https://github.com/frncsdrk/tiny-changelog/commit/3c36075aafb607e526976ef2f4c7da3c09803253)) 65 | 66 | ## 1.4.6 (2021-08-12) 67 | 68 | ### uncategorized 69 | 70 | * Update thirdparty dependencies ([d555f2](https://github.com/frncsdrk/tiny-changelog/commit/d555f2e484f25f9d338875f3dc9c33cd52cd0911)) 71 | 72 | ## 1.4.5 (2021-08-04) 73 | 74 | ### uncategorized 75 | 76 | * Update thirdparty dependencies ([f90bd0](https://github.com/frncsdrk/tiny-changelog/commit/f90bd0f5254d0c429218c7c30c6ab0772ee8fc0b)) 77 | 78 | ## 1.4.4 (2021-06-10) 79 | 80 | ### uncategorized 81 | 82 | * Fix security vulnerabilities ([589b1d](https://github.com/frncsdrk/tiny-changelog/commit/589b1d87fe1f9fe27b45b01c7ddb6acbb0b45ab1)) 83 | * Add actions to push container image to docker hub ([6293ed](https://github.com/frncsdrk/tiny-changelog/commit/6293edb56bb6a9856b2ff510ac57aa10c1cf00ec)) 84 | * Remove body-parser dependency ([fd83c9](https://github.com/frncsdrk/tiny-changelog/commit/fd83c95bc7d727d56b90d938d5c86bcb808fc399)) 85 | * Update thirdparty dependencies ([0352b2](https://github.com/frncsdrk/tiny-changelog/commit/0352b218768a2693c814fb7d6a670accfe50e2dd)) 86 | 87 | ## 1.4.3 (2021-05-08) 88 | 89 | ### uncategorized 90 | 91 | * Update thirdparty dependencies ([b88ac0](https://github.com/frncsdrk/tiny-changelog/commit/b88ac0199ba8e0001c3128096d61614a4fa42891)) 92 | 93 | ## 1.4.2 (2021-04-15) 94 | 95 | ### uncategorized 96 | 97 | * Update thirdparty dependencies ([ed47cf](https://github.com/frncsdrk/tiny-changelog/commit/ed47cfb3c119487793a72161a25417f7d671f546)) 98 | 99 | ## 1.4.1 (2021-04-15) 100 | 101 | ## 1.4.0 (2021-03-01) 102 | 103 | ### uncategorized 104 | 105 | * Update thirdparty dependencies ([7f8ed2](https://github.com/frncsdrk/tiny-changelog/commit/7f8ed211a9dab8312f3bf1531eaa6afbae467d7e)) 106 | * Add drawing of triangles in directions ([faf1f1](https://github.com/frncsdrk/tiny-changelog/commit/faf1f10904496f68ef17b4208247b4d0ffd6d891)) 107 | 108 | ## 1.3.8 (2021-02-10) 109 | 110 | ### uncategorized 111 | 112 | * Update copyright ([9a6cf6](https://github.com/frncsdrk/tiny-changelog/commit/9a6cf6bc3b9243ef9c4964ef84bb9546090d56a2)) 113 | * Update thirdparty dependencies ([4137c4](https://github.com/frncsdrk/tiny-changelog/commit/4137c451a43061f53c65bbee087b92daade4a762)) 114 | 115 | ## 1.3.7 (2020-12-12) 116 | 117 | ### uncategorized 118 | 119 | * Update thirdparty dependencies ([a3d7f6](https://github.com/frncsdrk/tiny-changelog/commit/a3d7f6e72e1681514bf65a139fbdf3d7a1315489)) 120 | 121 | ## 1.3.6 (2020-12-12) 122 | 123 | ### uncategorized 124 | 125 | * Update thirdparty dependencies ([dce5fe](https://github.com/frncsdrk/tiny-changelog/commit/dce5fe8795ff853af289f5f48825570f42f3eee6)) 126 | 127 | ## 1.3.5 (2020-11-04) 128 | 129 | ### uncategorized 130 | 131 | * Update thirdparty dependencies ([6c8589](https://github.com/frncsdrk/tiny-changelog/commit/6c8589b356580777f676d95ad181b9b177ed21ad)) 132 | 133 | ## 1.3.4 (2020-07-18) 134 | 135 | ### uncategorized 136 | 137 | * Update thirdparty dependencies ([2ec5a7](https://github.com/frncsdrk/tiny-changelog/commit/2ec5a7f66f8ab4929e269926ee65e3e894d8675e)) 138 | * Fix build action ([6f71d9](https://github.com/frncsdrk/tiny-changelog/commit/6f71d909584c3ed0ae057aa682fc90475a34a894)) 139 | * Fix build status badge ([5f954b](https://github.com/frncsdrk/tiny-changelog/commit/5f954b92c16323ceec7ed84a68da8106a740f72f)) 140 | * Update readme with new build status badge ([c286b9](https://github.com/frncsdrk/tiny-changelog/commit/c286b9dd64a1f3f1e14e0e72befb6ddfda6b98f8)) 141 | * Remove cirrus ci config ([b3f0ad](https://github.com/frncsdrk/tiny-changelog/commit/b3f0adb1c5c2015a512f166cbef5b8533443cb03)) 142 | * Add github workflow to build container image ([980ede](https://github.com/frncsdrk/tiny-changelog/commit/980edea7600fe3b742f64ca78807d389ba215ba7)) 143 | * Change base image ([79b6bd](https://github.com/frncsdrk/tiny-changelog/commit/79b6bd605eb8675c7d55935c18e5a3d5b09fb82b)) 144 | * Change build images ([8f4cea](https://github.com/frncsdrk/tiny-changelog/commit/8f4cea7f009067e017d78791db01de0a676b56b7)) 145 | 146 | ## 1.3.3 (2020-07-05) 147 | 148 | ### uncategorized 149 | 150 | * Update thirdparty dependencies ([43176c](https://github.com/frncsdrk/tiny-changelog/commit/43176c7f08ab6ce9f7278d36916255e48346548d)) 151 | 152 | ## 1.3.2 (2020-04-16) 153 | 154 | ### uncategorized 155 | 156 | * Update thirdparty dependencies ([3fe8e5](https://github.com/frncsdrk/tiny-changelog/commit/3fe8e5f372de7d8066b3c4d1e278043806612640)) 157 | 158 | ## 1.3.1 (2020-03-14) 159 | 160 | ### uncategorized 161 | 162 | * Update thirdparty dependencies ([cc20c6](https://github.com/frncsdrk/tiny-changelog/commit/cc20c6e4bbf8de16b05dd086148d21859cd8e2c1)) 163 | 164 | ## 1.3.0 (2020-02-26) 165 | 166 | ### uncategorized 167 | 168 | * docs(readme): Update readme ([83d6a2](https://github.com/frncsdrk/tiny-changelog/commit/83d6a28919926bac6fb6f6b3900965ba0bd1ee60)) 169 | * docs(readme): Add create avatar route ([e1340d](https://github.com/frncsdrk/tiny-changelog/commit/e1340d0d6ab02bb97a2bccda478f63ffe4e36b9f)) 170 | * docs(code): Add jsdoc notations ([7b1331](https://github.com/frncsdrk/tiny-changelog/commit/7b1331e93dd2f1759efab97e96daa2cc3c79c9b3)) 171 | * refactor(utils/avatar): Refactor createAvatar method ([758bb9](https://github.com/frncsdrk/tiny-changelog/commit/758bb9a7d5a31a34ec86d99a504f4160bb8db818)) 172 | * feat(routes/avatar): Add basic configurable drawing of avatar ([660bfa](https://github.com/frncsdrk/tiny-changelog/commit/660bfae26c34bac20342d1c23697e5dffecd595d)) 173 | * chore(deps): Update thirdparty dependencies ([5acaa2](https://github.com/frncsdrk/tiny-changelog/commit/5acaa267ce343405908a73639a692b65ad2a1f80)) 174 | * docs(readme): Fix typo ([eee575](https://github.com/frncsdrk/tiny-changelog/commit/eee57559ae4f2fa9de436aebf7f6a9585b8f51f3)) 175 | * feat(routes/avatar-random): Make default parameters overwritable ([8c6a0b](https://github.com/frncsdrk/tiny-changelog/commit/8c6a0bf6b67d09ad3fd6e1fc0e56f0c1db366861)) 176 | * feat(routes/avatar): Add mimeType parameter ([55e448](https://github.com/frncsdrk/tiny-changelog/commit/55e448c2ce785dec58072ec7771594aa2348406c)) 177 | 178 | ## 1.2.1 (2020-02-20) 179 | 180 | ### uncategorized 181 | 182 | * refactor(triangle): Improve naming ([cb196d](https://github.com/frncsdrk/tiny-changelog/commit/cb196d08607f5c5a867a990e3d65a4827e0b86ac)) 183 | * feat(routes/status): Change response to better reflect deprecation ([730f11](https://github.com/frncsdrk/tiny-changelog/commit/730f118203c205e414fded35bc8738cde9c57079)) 184 | * refactor(drawing): Refactor drawing logic ([b40503](https://github.com/frncsdrk/tiny-changelog/commit/b40503766542197b9e472f8162f877d7747940da)) 185 | * refactor(utils/respond): Use next in respond helper ([82b3e8](https://github.com/frncsdrk/tiny-changelog/commit/82b3e87d175239fc189d96fbd9b1615a31f804a4)) 186 | * refactor: Clean up src tree ([8a44ae](https://github.com/frncsdrk/tiny-changelog/commit/8a44aea9c44061397be271655b34397ec201728d)) 187 | 188 | ## 1.2.0 (2020-02-13) 189 | 190 | ### uncategorized 191 | 192 | * feat(routes): Add healthz route ([09d3af](https://github.com/frncsdrk/tiny-changelog/commit/09d3af66e95e067d7c8db397e586c0bd4bd855db)) 193 | * feat(container): Add docker-compose prod file ([7c4931](https://github.com/frncsdrk/tiny-changelog/commit/7c49317d9eee98f0d7eaa206a76786cff10d3a7f)) 194 | * fix(container): Update dockerignore ([0d88b3](https://github.com/frncsdrk/tiny-changelog/commit/0d88b35977946cd5d37c689a3ff7b9aff8faf644)) 195 | * docs(contributing): Update contributing ([8a2cea](https://github.com/frncsdrk/tiny-changelog/commit/8a2cea3ea43b50785ebce0c32e2c752a9d64589d)) 196 | * docs(readme): Update readme ([3efe0a](https://github.com/frncsdrk/tiny-changelog/commit/3efe0aa1da82ebf8597acaa658cf34d07d4a3b6b)) 197 | * docs(readme): Update readme ([0c15ec](https://github.com/frncsdrk/tiny-changelog/commit/0c15ec4a6e6bd8d1311a6ad97bb1174e1daef0eb)) 198 | 199 | ## 1.1.1 (2020-02-07) 200 | 201 | ### uncategorized 202 | 203 | * chore(dependencies): Update dependencies ([212ef0](https://github.com/frncsdrk/tiny-changelog/commit/212ef0d97ce77b80c686b64a78c809b6e00161ee)) 204 | * ci: Remove travis config ([d3e9ac](https://github.com/frncsdrk/tiny-changelog/commit/d3e9ac19c219845279f5f3d3386333db44ed70d7)) 205 | * ci: Fix cirrus config ([6e7e09](https://github.com/frncsdrk/tiny-changelog/commit/6e7e09682ce3c368c1b3b8a71c00f53aafcd9369)) 206 | * docs(readme): Replace travis with cirrus badge ([f126e4](https://github.com/frncsdrk/tiny-changelog/commit/f126e4ef824a5f75f0dad8b73560cbf0feeb21c8)) 207 | * ci: Add Cirrus CI config ([140839](https://github.com/frncsdrk/tiny-changelog/commit/1408391c395e005e84462ff4456f55a553c6a7f4)) 208 | * docs(readme): Fix API description ([a7047d](https://github.com/frncsdrk/tiny-changelog/commit/a7047da627b0ba671f66fdc0a597a708007e0984)) 209 | * fix(helpers/avatar): Fix drawing of foreground if drawing background ([d0b792](https://github.com/frncsdrk/tiny-changelog/commit/d0b7921772ee30566d37f518ed7325980e91bd69)) 210 | * refactor(helpers/avatar): Refactor createAvatar method ([e43eaa](https://github.com/frncsdrk/tiny-changelog/commit/e43eaaeb9acd7153d385adb3f9bbc31723e3f92d)) 211 | * refactor(helpers/avatar): Encapsulate drawing of arc ([913d9b](https://github.com/frncsdrk/tiny-changelog/commit/913d9b4ca18ff0cdee877d91e7348328ae8eff40)) 212 | 213 | ## 1.1.0 (2020-01-04) 214 | 215 | ### uncategorized 216 | 217 | * chore(license): Update copyright ([d9c089](https://github.com/frncsdrk/tiny-changelog/commit/d9c089e44dc503ea3fff1fc207aea6f9102e226e)) 218 | * fix(dependencies): Update thirparty dependencies ([7ddf45](https://github.com/frncsdrk/tiny-changelog/commit/7ddf458f54c46ade28a16b9513fccc9b780af57c)) 219 | * feat(shapes): Add drawing of top-facing triangle ([60cb7a](https://github.com/frncsdrk/tiny-changelog/commit/60cb7a6dd53db13b73db3b6fb3fe2aa56e10d5f0)) 220 | 221 | ## 1.0.3 (2019-12-08) 222 | 223 | ### uncategorized 224 | 225 | * fix(dependencies): Update thirdparty dependencies ([bebeee](https://github.com/frncsdrk/tiny-changelog/commit/bebeee0bbbbf55e77944f21fe3085c58c2151a0e)) 226 | * Add dockerignore to optimize image size ([adc18f](https://github.com/frncsdrk/tiny-changelog/commit/adc18ffda8e36d47f8a8b2b6c7cb17dcf9ac6166)) 227 | * Fix formatting ([e33071](https://github.com/frncsdrk/tiny-changelog/commit/e33071f066eaa188fd210d3324be0b9ffcd92cdb)) 228 | * Add contribution guide and credits ([25d726](https://github.com/frncsdrk/tiny-changelog/commit/25d7264a25aa5d738d65b4ec923973fc8016ae29)) 229 | * Update copyright ([9c0842](https://github.com/frncsdrk/tiny-changelog/commit/9c084295f2545bba093784757ffe2fe6c252af87)) 230 | * Add docker-compose file ([481eca](https://github.com/frncsdrk/tiny-changelog/commit/481eca88f8d550288f7884427d218c2e9993fbd1)) 231 | * Fix executable script ([1a8841](https://github.com/frncsdrk/tiny-changelog/commit/1a8841205e00393846ecd0384a62acb54d916a1c)) 232 | * Add shebang to executable script ([fbf718](https://github.com/frncsdrk/tiny-changelog/commit/fbf71888f2df08fc6cb263ec3d4591b52f9f7bcc)) 233 | 234 | ## 1.0.2 (2019-11-16) 235 | 236 | ### uncategorized 237 | 238 | * Update thirdparty dependencies ([c489c7](https://github.com/frncsdrk/tiny-changelog/commit/c489c74c4f4acc031199bb3d097aed4f2f29de6d)) 239 | * Update thirdparty dependencies ([f0e5d9](https://github.com/frncsdrk/tiny-changelog/commit/f0e5d9faa6a9283f42d4197cd02b6a2d0fca3016)) 240 | 241 | ## 1.0.1 (2019-09-29) 242 | 243 | ### uncategorized 244 | 245 | * Update thirdparty dependencies ([d3cce6](https://github.com/frncsdrk/tiny-changelog/commit/d3cce6cc7c0b16eeda62fc9d879c5e7c06d54de9)) 246 | * Fix license ([057916](https://github.com/frncsdrk/tiny-changelog/commit/057916ade6400dc53bdde93d8a5569a4e682738a)) 247 | 248 | ## 1.0.0 (2019-09-01) 249 | 250 | ## 1.0.0-rc2,0.1.5 (2019-09-01) 251 | 252 | ### uncategorized 253 | 254 | * Update readme ([927a9f](https://github.com/frncsdrk/tiny-changelog/commit/927a9fa4eab880a098e657efff15efc94a5b0f39)) 255 | * Change default port to 9000 ([22c06c](https://github.com/frncsdrk/tiny-changelog/commit/22c06c485b7be079115e6c6bfc7b4f92164445bd)) 256 | 257 | ## 0.1.4 (2019-09-01) 258 | 259 | ### uncategorized 260 | 261 | * Update thirdparty dependecies ([ff012a](https://github.com/frncsdrk/tiny-changelog/commit/ff012a58b7bd8ae0746f95f99862cf4ed3846d46)) 262 | 263 | ## 1.0.0-rc1,0.1.3 (2019-07-20) 264 | 265 | ### uncategorized 266 | 267 | * Update readme ([14b74f](https://github.com/frncsdrk/tiny-changelog/commit/14b74fcf98c1aaf827ce003b7279c6bbaacf7414)) 268 | * Fix wording in readme ([cc19d1](https://github.com/frncsdrk/tiny-changelog/commit/cc19d162bcc1830278a4056cfb73ac5fdecc1db9)) 269 | * Update readme ([af79b3](https://github.com/frncsdrk/tiny-changelog/commit/af79b316f87ec8f8e878fad1ea57f11257103bdb)) 270 | * Update license ([50f95d](https://github.com/frncsdrk/tiny-changelog/commit/50f95d7a018def8139188cbda997f53aec58dba5)) 271 | 272 | ## 0.1.2 (2019-07-18) 273 | 274 | ### uncategorized 275 | 276 | * Add service config ([a84620](https://github.com/frncsdrk/tiny-changelog/commit/a84620d8b4c1d0db50f9c8241479b21fe4e8214f)) 277 | * Fix formatting according to eslint rules ([7a294a](https://github.com/frncsdrk/tiny-changelog/commit/7a294aa58640b3ee2b939750f0cace2ee5897e0a)) 278 | * Fix docker file ([ff13bd](https://github.com/frncsdrk/tiny-changelog/commit/ff13bdaace89c89aa6de70c7de65f11d0c0e449e)) 279 | * Fix docker file ([28bbcf](https://github.com/frncsdrk/tiny-changelog/commit/28bbcf81d0937e279197de06afa8c560eb86370a)) 280 | * Add docker file ([c8aad6](https://github.com/frncsdrk/tiny-changelog/commit/c8aad6ef8e8e2c3fb3a1b6623d39011ef476e25e)) 281 | * Add eslint config ([3dbff3](https://github.com/frncsdrk/tiny-changelog/commit/3dbff38e45716d2c20043a8052369b6c4eb5139a)) 282 | * Update travis config ([c87c91](https://github.com/frncsdrk/tiny-changelog/commit/c87c918029a8f90c5f3fc22f6d1b90e9aa8a1829)) 283 | 284 | ## 0.1.1 (2019-07-18) 285 | 286 | ### uncategorized 287 | 288 | * Update readme ([c1713c](https://github.com/frncsdrk/tiny-changelog/commit/c1713c23d018312a1d073fdc62b1812d6b7d2a26)) 289 | * Replace index with bin/www ([2d4baf](https://github.com/frncsdrk/tiny-changelog/commit/2d4baffc56cd65974cffee4d2acd60148c4aac02)) 290 | * Update readme ([a6f84f](https://github.com/frncsdrk/tiny-changelog/commit/a6f84f679e83c163923ebe7dd8f3b5e13b5c08c1)) 291 | * Update dependencies ([ffe51b](https://github.com/frncsdrk/tiny-changelog/commit/ffe51bbe5d601586c692a0c6802173935eaf0a11)) 292 | 293 | ## 0.1.0 (2018-05-04) 294 | 295 | ### uncategorized 296 | 297 | * Add build badge to readme ([a31149](https://github.com/frncsdrk/tiny-changelog/commit/a31149f4bc8dfb219f9e4676aa1a4c94d8c3ae8d)) 298 | * Add only deploying to BRU ([cec08f](https://github.com/frncsdrk/tiny-changelog/commit/cec08f90bab466b1ec2e28997b708143741d583e)) 299 | * Add packages required for building canvas package ([26d96f](https://github.com/frncsdrk/tiny-changelog/commit/26d96f94eae85bfc341256e5d1ce32b4a3f1ef4f)) 300 | * Downgrade canvas package ([c54cd5](https://github.com/frncsdrk/tiny-changelog/commit/c54cd5e37a4338adf12356e431a7a6c25c5662b6)) 301 | * Fix travis config ([2b3bc4](https://github.com/frncsdrk/tiny-changelog/commit/2b3bc43235cb5e0e90bdc6994aee0e951b3b3d84)) 302 | * Add zeit now as dev dependency ([55f05a](https://github.com/frncsdrk/tiny-changelog/commit/55f05a03848a53372d37ccffbdc380764763c549)) 303 | * Fix travis build ([3363dd](https://github.com/frncsdrk/tiny-changelog/commit/3363dd52d03e5e22ce291c2136cacf90761b0da9)) 304 | * Fix yarn lock ([0d633e](https://github.com/frncsdrk/tiny-changelog/commit/0d633eee53f1514ae1cfb0258228744bbd3fa7d0)) 305 | * Add zeit now config ([0b9ec3](https://github.com/frncsdrk/tiny-changelog/commit/0b9ec30f6be51e3e3e8edfcebe87bc9d4a9e4508)) 306 | * Add travis config ([fccb8d](https://github.com/frncsdrk/tiny-changelog/commit/fccb8d2400cbcc399d38295caa3efae79bada260)) 307 | * Update readme with license ([c22dbd](https://github.com/frncsdrk/tiny-changelog/commit/c22dbdb12bf134a6c016a6aef1ff92faad8e3c86)) 308 | * Update readme ([97b21d](https://github.com/frncsdrk/tiny-changelog/commit/97b21da3c96398510e4effc8c4c6091f91b66705)) 309 | * Add random route ([b862c7](https://github.com/frncsdrk/tiny-changelog/commit/b862c7f0840434c045a67f55a3c5e12b419bc7fc)) 310 | * Add basic status route simply returning data if service is alive ([5b8df8](https://github.com/frncsdrk/tiny-changelog/commit/5b8df8c12449bce291fb07805b158f8e38b4e6d0)) 311 | * Clean up ([012c83](https://github.com/frncsdrk/tiny-changelog/commit/012c83995cb7d555a85ee95a8356e6af338a7eb7)) 312 | * Add background color handling ([b7f7ab](https://github.com/frncsdrk/tiny-changelog/commit/b7f7abebddde2f4166dee92223cc6088011535fb)) 313 | * Fix configurations ([385568](https://github.com/frncsdrk/tiny-changelog/commit/38556811457f16e2c16bb3eb612323b7820cb39e)) 314 | * Wrap logic in express microservice ([c18035](https://github.com/frncsdrk/tiny-changelog/commit/c18035f0e9803e14af46269d0be0094a0d78cdf2)) 315 | * Add example of creating random avatars with squares, vertically symmetric ([05114d](https://github.com/frncsdrk/tiny-changelog/commit/05114dee8a1f210611a3c27806823ae41b3146da)) 316 | * Add another example ([a91fef](https://github.com/frncsdrk/tiny-changelog/commit/a91fef963a788b942b502a570ebb88ee497b1a88)) 317 | * Add example ([58412c](https://github.com/frncsdrk/tiny-changelog/commit/58412cc75a6d966801673a962fa50ce79a177b58)) 318 | * Initial commit ([325cbc](https://github.com/frncsdrk/tiny-changelog/commit/325cbcb838c27fdf6d55293607eeb1bd0342b9d9)) 319 | 320 | 321 | Generated by [tiny-changelog](https://github.com/frncsdrk/tiny-changelog) 322 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes all contributions! 4 | 5 | ## Code 6 | 7 | Please follow the style used by already existing code. 8 | 9 | ## What if you are not proficient with Git? 10 | 11 | Open an issue and we will figure out how to get your contribution in. 12 | 13 | ## Credits 14 | 15 | If you want to get mentioned in the [CREDITS](https://github.com/frncsdrk/avatar/blob/main/CREDITS) open a pull request 16 | with the change or open an issue specifying the details. 17 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | In a similar fashion to the Linux source tree 2 | this file contains a list of contributors 3 | with a description of their contributions. 4 | See `CONTRIBUTING` for a description of how to 5 | get added. 6 | The list is sorted alphabetically by username and 7 | formatted to allow easy grepping and beautification 8 | by scripts. The fields are: username (U), name (N), 9 | email (E), web-address (W), and description (D). 10 | Thanks, 11 | frncsdrk 12 | 13 | ---------- 14 | 15 | U: frncsdrk 16 | D: Code, Docs 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/randomgoods/node-image-libs:legacy-slim 2 | 3 | ADD . /app 4 | 5 | # Create application user 6 | RUN groupadd -g 1001 avatar && \ 7 | useradd -m -u 1001 -g avatar avatar && \ 8 | chown -R avatar:avatar /app 9 | 10 | # Create log directory 11 | RUN mkdir -p /var/log/avatar && \ 12 | chown -R avatar:avatar /var/log/avatar 13 | 14 | USER avatar 15 | 16 | WORKDIR /app 17 | RUN npm i --omit=dev 18 | 19 | EXPOSE 9000 20 | 21 | CMD ["npm", "start"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - 2025 frncsdrk and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # avatar 2 | 3 | [![build](https://github.com/frncsdrk/avatar/actions/workflows/build.yml/badge.svg)](https://github.com/frncsdrk/avatar/actions/workflows/build.yml) 4 | 5 | Self-hosted service for creating random avatars 6 | 7 | Inspired by github default avatars 8 | 9 | ## Examples 10 | 11 | ### Random 12 | 13 | ![random squares](./img/random-0.png "squares") 14 | ![random circles](./img/random-1.png "circles") 15 | ![random triangles](./img/random-2.png "triangles") 16 | 17 | ### Initials 18 | 19 | ![initials AV](./img/initials-0.png "initials") 20 | 21 | ## Usage 22 | 23 | Default **Port**: `9000` 24 | 25 | ### Download 26 | 27 | Either download a release from the [Releases page](https://github.com/frncsdrk/avatar/tags) or clone the repo. 28 | Then follow the steps under [Development Setup](https://github.com/frncsdrk/avatar#setup). 29 | 30 | ### Docker 31 | 32 | There is a container image available on [Docker Hub](https://hub.docker.com/r/frncsdrk/avatar) 33 | and there is a Docker-Compose file in the repo. 34 | Both are provided for convenience and as a starting point for a production setup. 35 | 36 | ``` 37 | docker|podman run -p 9000:9000/tcp docker.io/frncsdrk/avatar 38 | ``` 39 | 40 | ### Configuration 41 | 42 | You can put config files into `./config/`. See [file load order](https://github.com/node-config/node-config/wiki/Configuration-Files#file-load-order) for configuration names and there priority. 43 | 44 | **Mount configuration into container** 45 | 46 | *Generally mounting a custom configuration shouldn't be necessary, except if you want to set a custom port* 47 | 48 | Using another name than `prod.yml` will lead to a warning when using the container image from this repository. 49 | The warning is irrelevant as long as you follow the configuration naming conventions. 50 | 51 | ``` 52 | docker|podman run -p :/tcp -v ./vol:/app/config docker.io/frncsdrk/avatar 53 | ``` 54 | 55 | ## API 56 | 57 | See [API Docs](https://frncsdrk.github.io/avatar) 58 | 59 | ## Logs 60 | 61 | The service uses a preconfigured log directory and logs into a combined log with all messages and an error log. 62 | For the development environment the default directory is `./log` and for production it is `/var/log/avatar`. 63 | Additionally, if the service is not running in production mode all messages get logged to the console. 64 | 65 | ## Development 66 | 67 | ### Setup 68 | 69 | #### Prerequisites 70 | 71 | - Node.js installation 72 | - Libraries for image editing 73 | - apiDoc installation, if editing docs 74 | 75 | #### Steps 76 | 77 | - Run `brew install node` or similar to install Node.js 78 | - Run `brew install pkg-config cairo pango libpng jpeg giflib librsvg` or similar to install libraries for image editing 79 | - Run `npm install -g apidoc` to install apiDoc globally 80 | 81 | ### Usage 82 | 83 | - Run `npm start` to start service 84 | - Run `npm run dev` to start service with nodemon 85 | - Run `npm run generate:docs` to generate API docs from source code 86 | 87 | ## Contributions 88 | 89 | See [CONTRIBUTING](https://github.com/frncsdrk/avatar/blob/main/CONTRIBUTING.md) 90 | 91 | ## Credits 92 | 93 | See [CREDITS](https://github.com/frncsdrk/avatar/blob/main/CREDITS) 94 | 95 | ## License 96 | 97 | [MIT](https://github.com/frncsdrk/avatar/blob/main/LICENSE) (c) 2018 - 2025 frncsdrk and contributors 98 | -------------------------------------------------------------------------------- /apidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avatar", 3 | "version": "1.6.0", 4 | "description": "Self-hosted service for creating random avatars" 5 | } 6 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const app = require('../src/app') 4 | 5 | app.start() 6 | -------------------------------------------------------------------------------- /config/default.yml: -------------------------------------------------------------------------------- 1 | service: 2 | server: 3 | port: 9000 4 | log: 5 | path: ./log 6 | -------------------------------------------------------------------------------- /config/development.yml: -------------------------------------------------------------------------------- 1 | service: 2 | server: 3 | port: 9000 4 | log: 5 | path: ./log 6 | -------------------------------------------------------------------------------- /config/prod.yml: -------------------------------------------------------------------------------- 1 | service: 2 | server: 3 | port: 9000 4 | log: 5 | path: /var/log/avatar 6 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | avatar_nodejs: 5 | build: . 6 | image: frncsdrk/avatar:latest 7 | container_name: avatar_nodejs 8 | restart: unless-stopped 9 | ports: 10 | - "9000:9000" 11 | networks: 12 | - avatar 13 | 14 | networks: 15 | avatar: 16 | driver: bridge 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | avatar_nodejs: 5 | build: . 6 | image: avatar_nodejs:dev 7 | container_name: avatar_nodejs_dev 8 | restart: unless-stopped 9 | ports: 10 | - "127.0.0.1:9000:9000" 11 | networks: 12 | - avatar 13 | 14 | networks: 15 | avatar: 16 | driver: bridge 17 | -------------------------------------------------------------------------------- /docs/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/assets/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/assets/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/assets/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/docs/assets/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/assets/main.css: -------------------------------------------------------------------------------- 1 | /** 2 | * apidoc main css file 3 | */ 4 | 5 | /** 6 | * Define colors 7 | */ 8 | :root { 9 | --primary: #0088cc; 10 | --white: #fff; 11 | --light-gray: #ccc; 12 | --main-gray: #777; 13 | --dark-gray: #2d2d2d; 14 | --hover-gray: #666; 15 | --meth-get: green; 16 | --meth-put: #e5c500; 17 | --meth-post: #4070ec; 18 | --meth-delete: #ed0039; 19 | --red: #dc3545; 20 | } 21 | 22 | .color-primary { 23 | color: var(--primary); 24 | } 25 | 26 | .bg-primary { 27 | background-color: var(--primary); 28 | } 29 | 30 | .bg-red { 31 | color: var(--white); 32 | background-color: var(--red); 33 | } 34 | 35 | .border-danger { 36 | border: 1px solid var(--red); 37 | } 38 | 39 | /** for some reason the iOS safari style is applied on date inputs */ 40 | input[type="date"] { 41 | line-height: 1.4 !important; 42 | } 43 | 44 | /* ------------------------------------------------------------------------------------------ 45 | * Content 46 | * ------------------------------------------------------------------------------------------ */ 47 | @font-face { 48 | font-family: 'Glyphicons Halflings'; 49 | src: url('./glyphicons-halflings-regular.eot'); 50 | src: url('./glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 51 | url('./glyphicons-halflings-regular.woff') format('woff'), 52 | url('./glyphicons-halflings-regular.woff2') format('woff2'), 53 | url('./glyphicons-halflings-regular.ttf') format('truetype'), 54 | url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); 55 | } 56 | 57 | /* Hide vertical scrollbar on off canvas animation ("left" positioning) */ 58 | html { 59 | overflow-x: hidden; 60 | } 61 | 62 | body { 63 | font-family: "Source Sans Pro", sans-serif; 64 | } 65 | 66 | a:focus { 67 | background-color: var(--primary); 68 | } 69 | 70 | #content { 71 | margin-top: 10px; 72 | padding-left: 10px; 73 | } 74 | 75 | p { 76 | font-size: 130%; 77 | color: var(--main-gray); 78 | } 79 | 80 | section { 81 | padding: 30px 0; 82 | } 83 | 84 | article { 85 | border-top: 1px solid var(--light-gray); 86 | padding: 14px 0 30px 0; 87 | } 88 | 89 | table { 90 | border-collapse: collapse; 91 | width: 100%; 92 | margin: 0 0 20px 0; 93 | } 94 | 95 | th { 96 | background-color: var(--main-gray); 97 | color: var(--white); 98 | text-align: left; 99 | padding: 5px 8px; 100 | border: 1px solid var(--main-gray); 101 | } 102 | 103 | td { 104 | padding: 5px; 105 | border: 1px solid var(--main-gray); 106 | } 107 | 108 | td.code { 109 | font-family: "Source Code Pro", monospace; 110 | font-weight: 600; 111 | } 112 | 113 | .label { 114 | float: right; 115 | margin-top: 4px; 116 | user-select: none; 117 | } 118 | 119 | .label.optional { 120 | background-color: grey; 121 | } 122 | 123 | .label.required { 124 | background-color: var(--red); 125 | } 126 | 127 | .default-value, 128 | .type-size { 129 | font-style: italic; 130 | font-size: 95%; 131 | } 132 | 133 | .open-left { 134 | right: 0; 135 | left: auto; 136 | } 137 | 138 | .invisible { 139 | visibility: hidden; 140 | } 141 | 142 | .input-group-addon.sample-request-select { 143 | padding: 0 6px; 144 | } 145 | 146 | .input-group-addon.sample-request-select select { 147 | width: auto; 148 | height: 32px; 149 | } 150 | 151 | .sample-request-input-Boolean-container { 152 | width: 40px; 153 | height: 34px; 154 | background: var(--white); 155 | border: 1px solid var(--light-gray); 156 | } 157 | 158 | .sample-request-input-Boolean-container > div { 159 | margin-top: 7px; 160 | text-align: center; 161 | } 162 | 163 | .sample-request-input-Boolean-container > div input { 164 | margin: 0; 165 | } 166 | 167 | /* ------------------------------------------------------------------------------------------ 168 | * Request method (HTTP verb) 169 | * ------------------------------------------------------------------------------------------ */ 170 | .method { 171 | font-weight: 600; 172 | font-size: 15px; 173 | display: inline-block; 174 | margin: 0 0 5px 0; 175 | padding: 4px 5px; 176 | border-radius: 6px; 177 | text-transform: uppercase; 178 | background-color: var(--main-gray); 179 | color: var(--white); 180 | } 181 | 182 | .meth-get { 183 | background-color: var(--meth-get); 184 | } 185 | 186 | .meth-put { 187 | background-color: var(--meth-put); 188 | } 189 | 190 | .meth-post { 191 | background-color: var(--meth-post); 192 | } 193 | 194 | .meth-delete { 195 | background-color: var(--meth-delete); 196 | } 197 | 198 | /* ------------------------------------------------------------------------------------------ 199 | * Sidenav 200 | * ------------------------------------------------------------------------------------------ */ 201 | #scrollingNav { 202 | position: fixed; 203 | top: 0; 204 | left: 0; 205 | bottom: 0; 206 | z-index: 10; 207 | background-color: var(--dark-gray); 208 | box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%); 209 | } 210 | 211 | .sidenav { 212 | color: var(--white); 213 | position: absolute; 214 | top: 50px; 215 | left: 0; 216 | right: 0; 217 | bottom: 0; 218 | overflow-x: hidden; 219 | overflow-y: hidden; 220 | } 221 | 222 | .sidenav:hover { 223 | overflow-x: auto; 224 | overflow-y: auto; 225 | } 226 | 227 | .sidenav > li > a { 228 | color: var(--white); 229 | display: block; 230 | padding: 8px 13px; 231 | } 232 | 233 | /* non active sidenav link are highlighted on hover */ 234 | .sidenav > li:not(.active) > a:hover { 235 | background-color: var(--hover-gray); 236 | } 237 | 238 | .sidenav > li.nav-header { 239 | margin-top: 8px; 240 | margin-bottom: 8px; 241 | } 242 | 243 | .sidenav > li.nav-header > a { 244 | padding: 5px 15px; 245 | font-weight: 700; 246 | font-size: 16px; 247 | background-color: var(--main-gray); 248 | } 249 | 250 | 251 | .sidenav > li.active > a { 252 | position: relative; 253 | background-color: var(--primary); 254 | color: var(--white); 255 | } 256 | 257 | /** 258 | * TODO: commented out for the moment 259 | .sidenav > li.has-modifications a { 260 | border-right: 4px solid var(--main-gray); 261 | } 262 | 263 | .nav-list-item :not(.is-new) { 264 | border-left: 4px solid var(--main-gray); 265 | } 266 | 267 | .sidenav > li.is-new a { 268 | border-left: 4px solid var(--primary); 269 | } 270 | */ 271 | 272 | /* 273 | * Off Canvas 274 | * -------------------------------------------------- 275 | */ 276 | @media screen and (max-width: 767px) { 277 | #content { 278 | margin-top: 58px; 279 | } 280 | 281 | .row-offcanvas { 282 | position: relative; 283 | -webkit-transition: all .25s ease-out; 284 | -o-transition: all .25s ease-out; 285 | transition: all .25s ease-out; 286 | left: 0; 287 | } 288 | 289 | .row-offcanvas, 290 | .row-offcanvas * { 291 | transition: all 0.5s ease-out; 292 | } 293 | 294 | .row-offcanvas .sidebar-offcanvas { 295 | position: absolute; 296 | top: 0; 297 | left: -200px !important; /* 6 columns */ 298 | width: 100%; /* 6 columns */ 299 | max-width: 200px; 300 | } 301 | 302 | .nav-toggle { 303 | position: fixed; 304 | left: 0; 305 | background: var(--dark-gray); 306 | width: 100%; 307 | } 308 | 309 | .nav-toggle .btn { 310 | margin: 10px 14px; 311 | } 312 | .nav-toggle .icon-bar { 313 | display: block; 314 | width: 22px; 315 | height: 2px; 316 | border-radius: 1px; 317 | background-color: var(--white); 318 | } 319 | .nav-toggle .icon-bar + .icon-bar { 320 | margin-top: 4px; 321 | } 322 | 323 | .row-offcanvas.active .sidebar-offcanvas { 324 | left: 0 !important; /* 6 columns */ 325 | } 326 | .row-offcanvas.active, .row-offcanvas.active .nav-toggle { 327 | left: 200px; 328 | } 329 | /* Styling the three lines to make it an X */ 330 | .row-offcanvas.active .nav-toggle .btn > .icon-bar { 331 | transform: rotate(45deg) translate(-4px, -4px); 332 | } 333 | .row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(2) { 334 | display: none; 335 | } 336 | .row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(3) { 337 | transform: rotate(-45deg); 338 | } 339 | } 340 | 341 | /* ------------------------------------------------------------------------------------------ 342 | * Side nav search 343 | * ------------------------------------------------------------------------------------------ */ 344 | .sidenav-search { 345 | padding: 16px 10px 10px; 346 | background-color: var(--dark-gray); 347 | } 348 | 349 | .sidenav-search .search { 350 | height: 26px; 351 | } 352 | 353 | .search-reset { 354 | position: absolute; 355 | display: block; 356 | cursor: pointer; 357 | width: 20px; 358 | height: 20px; 359 | text-align: center; 360 | right: 28px; 361 | top: 18px; 362 | background-color: #fff; 363 | } 364 | 365 | /* ------------------------------------------------------------------------------------------ 366 | * Prism - Toolbar 367 | * ------------------------------------------------------------------------------------------ */ 368 | div.code-toolbar.code-toolbar > .toolbar { 369 | top: .4rem; 370 | right: .4rem; 371 | } 372 | div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:hover, 373 | div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:focus { 374 | color: var(--white); 375 | } 376 | div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button { 377 | color: var(--light-gray); 378 | padding: .5em; 379 | background: var(--hover-gray); 380 | box-shadow: 0 2px 1px 1px rgba(0,0,0,.5); 381 | } 382 | 383 | /* ------------------------------------------------------------------------------------------ 384 | * Compare 385 | * ------------------------------------------------------------------------------------------ */ 386 | 387 | ins { 388 | background: #60d060; 389 | text-decoration: none; 390 | color: #000000; 391 | } 392 | 393 | del { 394 | background: #f05050; 395 | color: #000000; 396 | } 397 | 398 | .label-ins { 399 | background-color: #60d060; 400 | } 401 | 402 | .label-del { 403 | background-color: #f05050; 404 | text-decoration: line-through; 405 | } 406 | 407 | pre.ins { 408 | background-color: #60d060; 409 | } 410 | 411 | pre.del { 412 | background-color: #f05050; 413 | text-decoration: line-through; 414 | } 415 | 416 | table.ins th, 417 | table.ins td { 418 | background-color: #60d060; 419 | } 420 | 421 | table.del th, 422 | table.del td { 423 | background-color: #f05050; 424 | text-decoration: line-through; 425 | } 426 | 427 | tr.ins td { 428 | background-color: #60d060; 429 | } 430 | 431 | tr.del td { 432 | background-color: #f05050; 433 | text-decoration: line-through; 434 | } 435 | 436 | /* ------------------------------------------------------------------------------------------ 437 | * Spinner 438 | * ------------------------------------------------------------------------------------------ */ 439 | 440 | #loader { 441 | position: absolute; 442 | width: 100%; 443 | } 444 | 445 | #loader p { 446 | padding-top: 80px; 447 | margin-left: -4px; 448 | } 449 | 450 | .spinner { 451 | margin: 200px auto; 452 | width: 60px; 453 | height: 60px; 454 | position: relative; 455 | } 456 | 457 | .container1 > div, .container2 > div, .container3 > div { 458 | width: 14px; 459 | height: 14px; 460 | background-color: #0088cc; 461 | 462 | border-radius: 100%; 463 | position: absolute; 464 | -webkit-animation: bouncedelay 1.2s infinite ease-in-out; 465 | animation: bouncedelay 1.2s infinite ease-in-out; 466 | /* Prevent first frame from flickering when animation starts */ 467 | -webkit-animation-fill-mode: both; 468 | animation-fill-mode: both; 469 | } 470 | 471 | .spinner .spinner-container { 472 | position: absolute; 473 | width: 100%; 474 | height: 100%; 475 | } 476 | 477 | .container2 { 478 | -webkit-transform: rotateZ(45deg); 479 | transform: rotateZ(45deg); 480 | } 481 | 482 | .container3 { 483 | -webkit-transform: rotateZ(90deg); 484 | transform: rotateZ(90deg); 485 | } 486 | 487 | .circle1 { top: 0; left: 0; } 488 | .circle2 { top: 0; right: 0; } 489 | .circle3 { right: 0; bottom: 0; } 490 | .circle4 { left: 0; bottom: 0; } 491 | 492 | .container2 .circle1 { 493 | -webkit-animation-delay: -1.1s; 494 | animation-delay: -1.1s; 495 | } 496 | 497 | .container3 .circle1 { 498 | -webkit-animation-delay: -1.0s; 499 | animation-delay: -1.0s; 500 | } 501 | 502 | .container1 .circle2 { 503 | -webkit-animation-delay: -0.9s; 504 | animation-delay: -0.9s; 505 | } 506 | 507 | .container2 .circle2 { 508 | -webkit-animation-delay: -0.8s; 509 | animation-delay: -0.8s; 510 | } 511 | 512 | .container3 .circle2 { 513 | -webkit-animation-delay: -0.7s; 514 | animation-delay: -0.7s; 515 | } 516 | 517 | .container1 .circle3 { 518 | -webkit-animation-delay: -0.6s; 519 | animation-delay: -0.6s; 520 | } 521 | 522 | .container2 .circle3 { 523 | -webkit-animation-delay: -0.5s; 524 | animation-delay: -0.5s; 525 | } 526 | 527 | .container3 .circle3 { 528 | -webkit-animation-delay: -0.4s; 529 | animation-delay: -0.4s; 530 | } 531 | 532 | .container1 .circle4 { 533 | -webkit-animation-delay: -0.3s; 534 | animation-delay: -0.3s; 535 | } 536 | 537 | .container2 .circle4 { 538 | -webkit-animation-delay: -0.2s; 539 | animation-delay: -0.2s; 540 | } 541 | 542 | .container3 .circle4 { 543 | -webkit-animation-delay: -0.1s; 544 | animation-delay: -0.1s; 545 | } 546 | 547 | @-webkit-keyframes bouncedelay { 548 | 0%, 80%, 100% { -webkit-transform: scale(0.0) } 549 | 40% { -webkit-transform: scale(1.0) } 550 | } 551 | 552 | @keyframes bouncedelay { 553 | 0%, 80%, 100% { 554 | transform: scale(0.0); 555 | -webkit-transform: scale(0.0); 556 | } 40% { 557 | transform: scale(1.0); 558 | -webkit-transform: scale(1.0); 559 | } 560 | } 561 | 562 | /* ------------------------------------------------------------------------------------------ 563 | * Tabs 564 | * ------------------------------------------------------------------------------------------ */ 565 | ul.nav-tabs { 566 | margin: 0; 567 | } 568 | 569 | p.deprecated span{ 570 | color: var(--red); 571 | font-weight: bold; 572 | text-decoration: underline; 573 | } 574 | 575 | /** 576 | * Footer 577 | */ 578 | #generator { 579 | padding: 10px 0; 580 | } 581 | 582 | /* ------------------------------------------------------------------------------------------ 583 | * Print 584 | * ------------------------------------------------------------------------------------------ */ 585 | 586 | @media print { 587 | 588 | #sidenav, 589 | #version, 590 | #versions, 591 | section .version, 592 | section .versions { 593 | display: none; 594 | } 595 | 596 | #content { 597 | margin-left: 0; 598 | } 599 | 600 | a { 601 | text-decoration: none; 602 | color: inherit; 603 | } 604 | 605 | a:after { 606 | content: " [" attr(href) "] "; 607 | } 608 | 609 | p { 610 | color: #000000 611 | } 612 | 613 | pre { 614 | background-color: #ffffff; 615 | color: #000000; 616 | padding: 10px; 617 | border: #808080 1px solid; 618 | border-radius: 6px; 619 | position: relative; 620 | margin: 10px 0 20px 0; 621 | } 622 | 623 | } /* /@media print */ 624 | -------------------------------------------------------------------------------- /docs/assets/prism-diff-highlight.css: -------------------------------------------------------------------------------- 1 | pre.diff-highlight > code .token.deleted:not(.prefix), 2 | pre > code.diff-highlight .token.deleted:not(.prefix) { 3 | background-color: rgba(255, 0, 0, .1); 4 | color: inherit; 5 | display: block; 6 | } 7 | 8 | pre.diff-highlight > code .token.inserted:not(.prefix), 9 | pre > code.diff-highlight .token.inserted:not(.prefix) { 10 | background-color: rgba(0, 255, 128, .1); 11 | color: inherit; 12 | display: block; 13 | } 14 | -------------------------------------------------------------------------------- /docs/assets/prism-toolbar.css: -------------------------------------------------------------------------------- 1 | div.code-toolbar { 2 | position: relative; 3 | } 4 | 5 | div.code-toolbar > .toolbar { 6 | position: absolute; 7 | z-index: 10; 8 | top: .3em; 9 | right: .2em; 10 | transition: opacity 0.3s ease-in-out; 11 | opacity: 0; 12 | } 13 | 14 | div.code-toolbar:hover > .toolbar { 15 | opacity: 1; 16 | } 17 | 18 | /* Separate line b/c rules are thrown out if selector is invalid. 19 | IE11 and old Edge versions don't support :focus-within. */ 20 | div.code-toolbar:focus-within > .toolbar { 21 | opacity: 1; 22 | } 23 | 24 | div.code-toolbar > .toolbar > .toolbar-item { 25 | display: inline-block; 26 | } 27 | 28 | div.code-toolbar > .toolbar > .toolbar-item > a { 29 | cursor: pointer; 30 | } 31 | 32 | div.code-toolbar > .toolbar > .toolbar-item > button { 33 | background: none; 34 | border: 0; 35 | color: inherit; 36 | font: inherit; 37 | line-height: normal; 38 | overflow: visible; 39 | padding: 0; 40 | -webkit-user-select: none; /* for button */ 41 | -moz-user-select: none; 42 | -ms-user-select: none; 43 | } 44 | 45 | div.code-toolbar > .toolbar > .toolbar-item > a, 46 | div.code-toolbar > .toolbar > .toolbar-item > button, 47 | div.code-toolbar > .toolbar > .toolbar-item > span { 48 | color: #bbb; 49 | font-size: .8em; 50 | padding: 0 .5em; 51 | background: #f5f2f0; 52 | background: rgba(224, 224, 224, 0.2); 53 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 54 | border-radius: .5em; 55 | } 56 | 57 | div.code-toolbar > .toolbar > .toolbar-item > a:hover, 58 | div.code-toolbar > .toolbar > .toolbar-item > a:focus, 59 | div.code-toolbar > .toolbar > .toolbar-item > button:hover, 60 | div.code-toolbar > .toolbar > .toolbar-item > button:focus, 61 | div.code-toolbar > .toolbar > .toolbar-item > span:hover, 62 | div.code-toolbar > .toolbar > .toolbar-item > span:focus { 63 | color: inherit; 64 | text-decoration: none; 65 | } 66 | -------------------------------------------------------------------------------- /docs/assets/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 3 | * Based on https://github.com/chriskempson/tomorrow-theme 4 | * @author Rose Pritchard 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #ccc; 10 | background: none; 11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 12 | font-size: 1em; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | } 37 | 38 | :not(pre) > code[class*="language-"], 39 | pre[class*="language-"] { 40 | background: #2d2d2d; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .1em; 46 | border-radius: .3em; 47 | white-space: normal; 48 | } 49 | 50 | .token.comment, 51 | .token.block-comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #999; 56 | } 57 | 58 | .token.punctuation { 59 | color: #ccc; 60 | } 61 | 62 | .token.tag, 63 | .token.attr-name, 64 | .token.namespace, 65 | .token.deleted { 66 | color: #e2777a; 67 | } 68 | 69 | .token.function-name { 70 | color: #6196cc; 71 | } 72 | 73 | .token.boolean, 74 | .token.number, 75 | .token.function { 76 | color: #f08d49; 77 | } 78 | 79 | .token.property, 80 | .token.class-name, 81 | .token.constant, 82 | .token.symbol { 83 | color: #f8c555; 84 | } 85 | 86 | .token.selector, 87 | .token.important, 88 | .token.atrule, 89 | .token.keyword, 90 | .token.builtin { 91 | color: #cc99cd; 92 | } 93 | 94 | .token.string, 95 | .token.char, 96 | .token.attr-value, 97 | .token.regex, 98 | .token.variable { 99 | color: #7ec699; 100 | } 101 | 102 | .token.operator, 103 | .token.entity, 104 | .token.url { 105 | color: #67cdcc; 106 | } 107 | 108 | .token.important, 109 | .token.bold { 110 | font-weight: bold; 111 | } 112 | .token.italic { 113 | font-style: italic; 114 | } 115 | 116 | .token.entity { 117 | cursor: help; 118 | } 119 | 120 | .token.inserted { 121 | color: green; 122 | } 123 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | avatar 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 55 | 56 | 57 | 85 | 86 | 91 | 92 | 97 | 98 | 109 | 110 | 123 | 124 | 213 | 214 | 259 | 260 | 311 | 312 | 381 | 382 | 604 | 605 | 721 | 722 | 790 | 791 | 900 | 901 | 918 | 919 | 936 | 937 | 1016 | 1017 |
1018 |
1019 |
1020 |
1021 |
1022 | 1023 |
1024 | 1025 |
1026 |
1027 |
1028 |
1029 | 1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |

Loading...

1042 |
1043 |
1044 | 1045 | 1046 | 1047 | 1048 | -------------------------------------------------------------------------------- /img/initials-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/img/initials-0.png -------------------------------------------------------------------------------- /img/random-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/img/random-0.png -------------------------------------------------------------------------------- /img/random-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/img/random-1.png -------------------------------------------------------------------------------- /img/random-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frncsdrk/avatar/eaf33918f759f9c1c3b8e621a53a1017a3a8d3cf/img/random-2.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avatar", 3 | "version": "1.6.1", 4 | "description": "Self-hosted service for creating random avatars", 5 | "main": "src/app.js", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nodemon bin/www", 9 | "generate:docs": "apidoc -i src -o docs", 10 | "start": "NODE_ENV=prod node bin/www" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/frncsdrk/avatar.git" 15 | }, 16 | "keywords": [ 17 | "avatar" 18 | ], 19 | "author": "frncsdrk", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/frncsdrk/avatar/issues" 23 | }, 24 | "homepage": "https://github.com/frncsdrk/avatar#readme", 25 | "dependencies": { 26 | "canvas": "^3.1.0", 27 | "config": "^3.3.12", 28 | "cors": "^2.8.5", 29 | "express": "^4.21.2", 30 | "js-yaml": "^4.1.0", 31 | "response-time": "^2.3.3", 32 | "winston": "^3.17.0", 33 | "winston-daily-rotate-file": "^5.0.0" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^9.22.0", 37 | "eslint-config-prettier": "^10.1.1", 38 | "eslint-plugin-import": "^2.31.0", 39 | "eslint-plugin-node": "^11.1.0", 40 | "eslint-plugin-prettier": "^5.2.3", 41 | "eslint-plugin-promise": "^7.2.1", 42 | "nodemon": "^3.1.9", 43 | "prettier": "^3.5.3" 44 | }, 45 | "engines": { 46 | "node": ">= 18.0.0 <23.0.0" 47 | }, 48 | "volta": { 49 | "node": "20.14.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Publish docs 4 | 5 | git subtree push --prefix docs gh gh-pages 6 | printf '%s\n' "PUBLISHED" 7 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const responseTime = require('response-time'); 4 | const config = require('config'); 5 | 6 | const logger = require('./logger'); 7 | 8 | const avatar = require('./routes/avatar'); 9 | const healthz = require('./routes/healthz'); 10 | const status = require('./routes/status'); 11 | 12 | /** 13 | * Get port from config or use fallback 14 | */ 15 | const getPort = () => { 16 | return config.has('service.server.port') ? config.get('service.server.port') : 9000; 17 | }; 18 | 19 | /** 20 | * Start the app 21 | */ 22 | const start = () => { 23 | const app = express(); 24 | 25 | app.use(express.json()); 26 | app.use(express.text({ limit: '50mb', type: 'text/*' })); 27 | app.use(express.urlencoded({ extended: false })); 28 | app.use(cors()); 29 | 30 | // Request logging including response time 31 | app.use(responseTime((req, res, time) => { 32 | logger.info({ 33 | method: req.method, 34 | userAgent: req.get('User-Agent'), 35 | url: req.url, 36 | statusCode: res.statusCode, 37 | responseTime: time, 38 | }); 39 | })); 40 | 41 | app.use('/avatar', avatar); 42 | app.use('/healthz', healthz); 43 | app.use('/status', status); 44 | 45 | const server = app.listen(getPort(), () => { 46 | logger.info('app is running on %s', getPort()); 47 | }); 48 | }; 49 | 50 | module.exports = { 51 | start, 52 | }; 53 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | const config = require('config'); 2 | const winston = require('winston'); 3 | const { createLogger, format, transports } = require('winston'); 4 | require('winston-daily-rotate-file'); 5 | 6 | const getLogDirectory = () => { 7 | return config.has('service.log.path') ? config.get('service.log.path') : './log'; 8 | }; 9 | 10 | const fileRotateCombinedTransport = new winston.transports.DailyRotateFile({ 11 | filename: `${ getLogDirectory() }/combined-%DATE%.log`, 12 | datePattern: 'YYYY-MM-DD', 13 | maxFiles: '14d', 14 | zippedArchive: true, 15 | }); 16 | const fileRotateErrorTransport = new winston.transports.DailyRotateFile({ 17 | level: 'error', 18 | filename: `${ getLogDirectory() }/error-%DATE%.log`, 19 | datePattern: 'YYYY-MM-DD', 20 | maxFiles: '14d', 21 | zippedArchive: true, 22 | }); 23 | 24 | const logger = createLogger({ 25 | level: 'info', 26 | format: format.combine( 27 | format.timestamp({ 28 | format: 'YYYY-MM-DDTHH:mm:ss' 29 | }), 30 | format.errors({ stack: true }), 31 | format.splat(), 32 | format.json() 33 | ), 34 | defaultMeta: { service: 'avatar' }, 35 | transports: [ 36 | // 37 | // - Write to all logs with level `info` and below to `combined.log`. 38 | // - Write all logs error (and below) to `error.log`. 39 | // 40 | fileRotateCombinedTransport, 41 | fileRotateErrorTransport 42 | ], 43 | exitOnError: false, 44 | }); 45 | 46 | // 47 | // If we're not in production then **ALSO** log to the `console` 48 | // with the colorized simple format. 49 | // 50 | if (process.env.NODE_ENV !== 'prod') { 51 | logger.add(new transports.Console({ 52 | format: format.cli(), 53 | })); 54 | } 55 | 56 | module.exports = logger; 57 | -------------------------------------------------------------------------------- /src/routes/avatar.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const respond = require('../utils/respond'); 4 | const { createAvatar } = require('./../utils/avatar'); 5 | const { createInitialsAvatar } = require('./../utils/initials'); 6 | const random = require('./../utils/random'); 7 | 8 | const router = express.Router(); 9 | 10 | /** 11 | * @api {get} /avatar Request avatar image 12 | * @apiName GetAvatar 13 | * @apiGroup Avatar 14 | * 15 | * @apiVersion 1.4.0 16 | * 17 | * @apiQuery {Number} [width=256] Width in px 18 | * @apiQuery {Number} [height=256] Height in px 19 | * @apiQuery {Boolean} [verticallySymmetric=true] Render avatar vertically symmetric 20 | * @apiQuery {Boolean} [horizontallySymmetric=false] Render avatar horizontally symmetric (will only be used if verticallySymmetric is explicitly turned off) 21 | * @apiQuery {String} [direction='top'] Direction in which triangles should point 22 | * @apiQuery {String='square','circle','triangle'} [type='square'] Type of shape to use for avatar 23 | * @apiQuery {Number} [elementWidth=16] Width of singular shapes in px 24 | * @apiQuery {String} [color='blue'] Color of elements 25 | * @apiQuery {String} [bgColor=none] Background color 26 | * @apiQuery {Boolean} [stroke=false] Add stroke to outline elements 27 | * @apiQuery {String} [strokeColor] Color of stroke 28 | * @apiQuery {String} [mimeType='image/png'] Specify MIME type of image 29 | * 30 | * @apiSuccessExample Success-Response: 31 | * HTTP/1.1 200 OK 32 | * Image 33 | */ 34 | router.get('/', (req, res, next) => { 35 | createAvatar(req.query, (err, data) => { 36 | respond({ 37 | contentType: data.contentType, 38 | data: data.buffer, 39 | err, 40 | next, 41 | res, 42 | }); 43 | }); 44 | }); 45 | 46 | /** 47 | * @api {post} /avatar Create specified avatar 48 | * @apiName CreateAvatar 49 | * @apiGroup Avatar 50 | * 51 | * @apiVersion 1.4.0 52 | * 53 | * @apiBody {text/plain} body Avatar pattern, where every '#' is a shape 54 | * 55 | * @apiQuery {Number} [width=256] Width in px 56 | * @apiQuery {Number} [height=256] Height in px 57 | * @apiQuery {Boolean} [verticallySymmetric=true] Render avatar vertically symmetric 58 | * @apiQuery {Boolean} [horizontallySymmetric=false] Render avatar horizontally symmetric (will only be used if verticallySymmetric is explicitly turned off) 59 | * @apiQuery {String} [direction='top'] Direction in which triangles should point 60 | * @apiQuery {String='square','circle','triangle'} [type='square'] Type of shape to use for avatar 61 | * @apiQuery {Number} [elementWidth=16] Width of singular shapes in px 62 | * @apiQuery {String} [color='blue'] Color of elements 63 | * @apiQuery {String} [bgColor=none] Background color 64 | * @apiQuery {Boolean} [stroke=false] Add stroke to outline elements 65 | * @apiQuery {String} [strokeColor] Color of stroke 66 | * @apiQuery {String} [mimeType='image/png'] Specify MIME type of image 67 | * 68 | * @apiSuccessExample Success-Response: 69 | * HTTP/1.1 200 OK 70 | * Image 71 | */ 72 | router.post('/', (req, res, next) => { 73 | createAvatar( 74 | { 75 | body: req.body, 76 | verticallySymmetric: false, 77 | ...req.query, 78 | }, 79 | (err, data) => { 80 | respond({ 81 | contentType: data.contentType, 82 | data: data.buffer, 83 | err, 84 | next, 85 | res, 86 | }); 87 | } 88 | ); 89 | }); 90 | 91 | /** 92 | * @api {get} /avatar/random Request random avatar image 93 | * @apiName GetRandomAvatar 94 | * @apiGroup Avatar 95 | * 96 | * @apiVersion 1.4.0 97 | * 98 | * @apiQuery {Number} width=256 Width in px 99 | * @apiQuery {Number} height=256 Height in px 100 | * @apiQuery {Boolean} verticallySymmetric=true Render avatar vertically symmetric 101 | * @apiQuery {Boolean} horizontallySymmetric=false Render avatar horizontally symmetric (will only be used if verticallySymmetric is explicitly turned off) 102 | * @apiQuery {String} direction='top' Direction in which triangles should point 103 | * @apiQuery {String='square','circle','triangle'} type='square' Type of shape to use for avatar 104 | * @apiQuery {Number} elementWidth=16 Width of singular shapes in px 105 | * @apiQuery {String} color='blue' Color of elements 106 | * @apiQuery {String} bgColor=none Background color 107 | * @apiQuery {Boolean} stroke=false Add stroke to outline elements 108 | * @apiQuery {String} strokeColor Color of stroke 109 | * @apiQuery {String} mimeType='image/png' Specify MIME type of image 110 | * 111 | * @apiSuccessExample Success-Response: 112 | * HTTP/1.1 200 OK 113 | * Image 114 | */ 115 | router.get('/random', (req, res, next) => { 116 | createAvatar( 117 | { 118 | width: 256, 119 | height: 256, 120 | elementWidth: 16, 121 | color: '#' + ((Math.random() * 0xffffff) << 0).toString(16), 122 | type: random.getRandomShapeType(), 123 | ...req.query, 124 | }, 125 | (err, data) => { 126 | respond({ 127 | contentType: data.contentType, 128 | data: data.buffer, 129 | err, 130 | next, 131 | res, 132 | }); 133 | } 134 | ); 135 | }); 136 | 137 | /** 138 | * @api {get} /avatar/initials Request initials avatar image 139 | * @apiName GetInitialsAvatar 140 | * @apiGroup Avatar 141 | * 142 | * @apiVersion 1.5.0 143 | * 144 | * @apiQuery {String} letters The initials to be used for the avatar 145 | * @apiQuery {String} [type='center'] Type of position calculation (default: 'center') 146 | * @apiQuery {Number} [widthFactor=1.3] For correction of position on the X-axis (default: 1.3) (only relevant for type 'center') 147 | * @apiQuery {Number} [heightFactor=0.8] For correction of position on the Y-axis (default: 0.8) (only relevant for type 'center') 148 | * @apiQuery {Number} [positionX] Position on the X-axis (only relevant for type 'custom') 149 | * @apiQuery {Number} [positionY] Position on the Y-axis (only relevant for type 'custom') 150 | * 151 | * @apiSuccessExample Success-Response: 152 | * HTTP/1.1 200 OK 153 | * Image 154 | */ 155 | router.get('/initials', (req, res, next) => { 156 | createInitialsAvatar( 157 | { 158 | color: '#' + ((Math.random() * 0xffffff) << 0).toString(16), 159 | type: 'center', 160 | ...req.query, 161 | }, 162 | (err, data) => { 163 | respond({ 164 | contentType: data.contentType, 165 | data: data.buffer, 166 | err, 167 | next, 168 | res, 169 | }); 170 | } 171 | ); 172 | }); 173 | 174 | module.exports = router; 175 | -------------------------------------------------------------------------------- /src/routes/healthz.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const respond = require('../utils/respond'); 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * @api {get} /healthz Request service health 9 | * @apiName Healthz 10 | * @apiGroup Health 11 | * 12 | * @apiVersion 1.2.0 13 | * 14 | * @apiSuccess {String} message Message text 15 | * 16 | * @apiSuccessExample Success-Response: 17 | * HTTP/1.1 200 OK 18 | * { 19 | * "message": "OK" 20 | * } 21 | */ 22 | router.get('/', (req, res, next) => { 23 | respond({ 24 | data: { 25 | msg: 'OK', 26 | }, 27 | next, 28 | res, 29 | }); 30 | }); 31 | 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /src/routes/status.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const respond = require('../utils/respond'); 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * @api {get} /status Request service status 9 | * @apiName Status 10 | * @apiGroup Health 11 | * 12 | * @apiVersion 0.1.0 13 | * @apiDeprecated use (Health:Healthz) 14 | * 15 | * @apiSuccess {Boolean} deprecated Deprecated flag 16 | * @apiSuccess {String} message Message text 17 | * @apiSuccess {Integer} status Status code 18 | * 19 | * @apiSuccessExample Success-Response: 20 | * HTTP/1.1 200 OK 21 | * { 22 | * "deprecated": true, 23 | * "message": "OK", 24 | * "status": 200 25 | * } 26 | */ 27 | router.get('/', (req, res, next) => { 28 | respond({ 29 | data: { 30 | deprecated: true, 31 | message: 'OK', 32 | status: 200, 33 | }, 34 | next, 35 | res, 36 | }); 37 | }); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /src/shapes/circle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw arc shape 3 | * @param {object} ctx - canvas context 4 | * @param {number} x 5 | * @param {number} y 6 | * @param {number} radius 7 | * @param {number} startingAngle 8 | * @param {number} endingAngle 9 | * @param {boolean} stroke - flag 10 | */ 11 | const drawRaw = (ctx, x, y, radius, startingAngle, endingAngle, stroke) => { 12 | ctx.beginPath(); 13 | ctx.arc(x, y, radius, startingAngle, endingAngle); 14 | ctx.fill(); 15 | stroke && ctx.stroke(); 16 | }; 17 | 18 | /** 19 | * Draw circle 20 | * @param {object} context 21 | */ 22 | const draw = (context) => { 23 | const elementWidth = context.elementWidth; 24 | drawRaw( 25 | context.ctx, 26 | context.positionX * elementWidth + context.radius, 27 | context.positionY * elementWidth + context.radius, 28 | context.radius, 29 | 0, 30 | 2 * Math.PI, 31 | context.stroke 32 | ); 33 | }; 34 | 35 | /** 36 | * Mirror drawn circle shape vertically 37 | * @param {object} context 38 | */ 39 | const drawVerticallySymmetric = (context) => { 40 | const elementWidth = context.elementWidth; 41 | const radius = context.radius; 42 | drawRaw( 43 | context.ctx, 44 | (context.elementsPerRow * 2 - context.positionX - 1) * elementWidth + 45 | radius, 46 | context.positionY * elementWidth + radius, 47 | radius, 48 | 0, 49 | 2 * Math.PI, 50 | context.stroke 51 | ); 52 | }; 53 | 54 | /** 55 | * Mirror drawn circle shape horizontally 56 | * @param {object} context 57 | */ 58 | const drawHorizontallySymmetric = (context) => { 59 | const elementWidth = context.elementWidth; 60 | const radius = context.radius; 61 | drawRaw( 62 | context.ctx, 63 | context.positionX * elementWidth + radius, 64 | (context.elementsPerCol * 2 - context.positionY - 1) * elementWidth + 65 | radius, 66 | radius, 67 | 0, 68 | 2 * Math.PI, 69 | context.stroke 70 | ); 71 | }; 72 | 73 | module.exports = { 74 | draw, 75 | drawVerticallySymmetric, 76 | drawHorizontallySymmetric, 77 | }; 78 | -------------------------------------------------------------------------------- /src/shapes/rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw rectangle shape 3 | * @param {object} context 4 | */ 5 | const draw = (context) => { 6 | const elementWidth = context.elementWidth; 7 | context.ctx.rect( 8 | context.positionX * elementWidth, 9 | context.positionY * elementWidth, 10 | elementWidth, 11 | elementWidth 12 | ); 13 | }; 14 | 15 | /** 16 | * Mirror drawn rectangle shape vertically 17 | * @param {object} context 18 | */ 19 | const drawVerticallySymmetric = (context) => { 20 | const elementWidth = context.elementWidth; 21 | context.ctx.rect( 22 | (context.elementsPerRow * 2 - context.positionX - 1) * elementWidth, 23 | context.positionY * elementWidth, 24 | elementWidth, 25 | elementWidth 26 | ); 27 | }; 28 | 29 | /** 30 | * Mirror drawn rectangle shape horizontally 31 | * @param {object} context 32 | */ 33 | const drawHorizontallySymmetric = (context) => { 34 | const elementWidth = context.elementWidth; 35 | context.ctx.rect( 36 | context.positionX * elementWidth, 37 | (context.elementsPerCol * 2 - context.positionY - 1) * elementWidth, 38 | elementWidth, 39 | elementWidth 40 | ); 41 | }; 42 | 43 | module.exports = { 44 | draw, 45 | drawVerticallySymmetric, 46 | drawHorizontallySymmetric, 47 | }; 48 | -------------------------------------------------------------------------------- /src/shapes/triangle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw top facing triangle 3 | * @param {object} ctx - canvas context 4 | * @param {number} x 5 | * @param {number} y 6 | * @param {number} width 7 | * @param {number} height 8 | * @param {boolean} stroke - flag 9 | */ 10 | const drawRawTop = (ctx, x, y, width, height, stroke) => { 11 | ctx.beginPath(); 12 | ctx.moveTo(x + width / 2, y); 13 | ctx.lineTo(x + width, y + height); 14 | ctx.lineTo(x, y + height); 15 | ctx.closePath(); 16 | ctx.fill(); 17 | stroke && ctx.stroke(); 18 | }; 19 | 20 | /** 21 | * Draw bottom facing triangle 22 | * @param {object} ctx - canvas context 23 | * @param {number} x 24 | * @param {number} y 25 | * @param {number} width 26 | * @param {number} height 27 | * @param {boolean} stroke - flag 28 | */ 29 | const drawRawBottom = (ctx, x, y, width, height, stroke) => { 30 | ctx.beginPath(); 31 | ctx.moveTo(x + width / 2, y + height); 32 | ctx.lineTo(x + width, y); 33 | ctx.lineTo(x, y); 34 | ctx.closePath(); 35 | ctx.fill(); 36 | stroke && ctx.stroke(); 37 | }; 38 | 39 | /** 40 | * Draw left facing triangle 41 | * @param {object} ctx - canvas context 42 | * @param {number} x 43 | * @param {number} y 44 | * @param {number} width 45 | * @param {number} height 46 | * @param {boolean} stroke - flag 47 | */ 48 | const drawRawLeft = (ctx, x, y, width, height, stroke) => { 49 | ctx.beginPath(); 50 | ctx.moveTo(x, y + height / 2); 51 | ctx.lineTo(x + width, y + height); 52 | ctx.lineTo(x + width, y); 53 | ctx.closePath(); 54 | ctx.fill(); 55 | stroke && ctx.stroke(); 56 | }; 57 | 58 | /** 59 | * Draw right facing triangle 60 | * @param {object} ctx - canvas context 61 | * @param {number} x 62 | * @param {number} y 63 | * @param {number} width 64 | * @param {number} height 65 | * @param {boolean} stroke - flag 66 | */ 67 | const drawRawRight = (ctx, x, y, width, height, stroke) => { 68 | ctx.beginPath(); 69 | ctx.moveTo(x + width, y + height / 2); 70 | ctx.lineTo(x, y + height); 71 | ctx.lineTo(x, y); 72 | ctx.closePath(); 73 | ctx.fill(); 74 | stroke && ctx.stroke(); 75 | }; 76 | 77 | /** 78 | * Draw triangle facing specified direction 79 | * @param {object} context 80 | */ 81 | const draw = (context) => { 82 | const elementWidth = context.elementWidth; 83 | let drawMethod = () => {}; 84 | switch (context.direction) { 85 | case 'top': 86 | drawMethod = drawRawTop; 87 | break; 88 | case 'bottom': 89 | drawMethod = drawRawBottom; 90 | break; 91 | case 'left': 92 | drawMethod = drawRawLeft; 93 | break; 94 | case 'right': 95 | drawMethod = drawRawRight; 96 | break; 97 | } 98 | 99 | drawMethod( 100 | context.ctx, 101 | context.positionX * elementWidth, 102 | context.positionY * elementWidth, 103 | elementWidth, 104 | elementWidth, 105 | context.stroke 106 | ); 107 | }; 108 | 109 | /** 110 | * Mirror triangle facing specified direction vertically 111 | * @param {object} context 112 | */ 113 | const drawVerticallySymmetric = (context) => { 114 | const elementWidth = context.elementWidth; 115 | let drawMethod = () => {}; 116 | switch (context.direction) { 117 | case 'top': 118 | drawMethod = drawRawTop; 119 | break; 120 | case 'bottom': 121 | drawMethod = drawRawBottom; 122 | break; 123 | case 'left': 124 | drawMethod = drawRawLeft; 125 | break; 126 | case 'right': 127 | drawMethod = drawRawRight; 128 | break; 129 | } 130 | 131 | drawMethod( 132 | context.ctx, 133 | (context.elementsPerRow * 2 - context.positionX - 1) * elementWidth, 134 | context.positionY * elementWidth, 135 | elementWidth, 136 | elementWidth, 137 | context.stroke 138 | ); 139 | }; 140 | 141 | /** 142 | * Mirror facing triangle facing specified direction horizontally 143 | * @param {object} context 144 | */ 145 | const drawHorizontallySymmetric = (context) => { 146 | const elementWidth = context.elementWidth; 147 | let drawMethod = () => {}; 148 | switch (context.direction) { 149 | case 'top': 150 | drawMethod = drawRawTop; 151 | break; 152 | case 'bottom': 153 | drawMethod = drawRawBottom; 154 | break; 155 | case 'left': 156 | drawMethod = drawRawLeft; 157 | break; 158 | case 'right': 159 | drawMethod = drawRawRight; 160 | break; 161 | } 162 | 163 | drawMethod( 164 | context.ctx, 165 | context.positionX * elementWidth, 166 | (context.elementsPerCol * 2 - context.positionY - 1) * elementWidth, 167 | elementWidth, 168 | elementWidth, 169 | context.stroke 170 | ); 171 | }; 172 | 173 | module.exports = { 174 | draw, 175 | drawVerticallySymmetric, 176 | drawHorizontallySymmetric, 177 | }; 178 | -------------------------------------------------------------------------------- /src/utils/avatar.js: -------------------------------------------------------------------------------- 1 | const { createCanvas } = require('canvas'); 2 | 3 | const shapes = { 4 | circle: require('../shapes/circle'), 5 | rect: require('../shapes/rect'), 6 | triangle: require('../shapes/triangle'), 7 | }; 8 | 9 | /** 10 | * Parse config values 11 | * @param {object} conf - config 12 | * @returns {object} config 13 | */ 14 | const parseConfig = (conf) => { 15 | const intKeys = ['width', 'height', 'elementWidth']; 16 | for (let i = 0; i < intKeys.length; i++) { 17 | if (conf[intKeys[i]]) { 18 | conf[intKeys[i]] = parseInt(conf[intKeys[i]], 10); 19 | } 20 | } 21 | const boolKeys = ['verticallySymmetric', 'horizontallySymmetric', 'stroke']; 22 | for (let i = 0; i < boolKeys.length; i++) { 23 | if (conf[boolKeys[i]]) { 24 | conf[boolKeys[i]] = conf[boolKeys[i]] === 'true'; 25 | } 26 | } 27 | 28 | return conf; 29 | }; 30 | 31 | /** 32 | * Draw selected shape with context provided 33 | * @param {string} type - shape type 34 | * @param {object} context 35 | */ 36 | const drawShape = (type, context) => { 37 | const shape = shapes[type]; 38 | shape.draw(context); 39 | if (context.verticallySymmetric) { 40 | // mirror vertically 41 | shape.drawVerticallySymmetric(context); 42 | } else if (context.horizontallySymmetric && !context.verticallySymmetric) { 43 | // mirror horizontally 44 | shape.drawHorizontallySymmetric(context); 45 | } 46 | }; 47 | 48 | /** 49 | * Translate config to context 50 | * @param {object} conf - config 51 | * @returns {object} context 52 | */ 53 | const createContext = (conf) => { 54 | const canvas = createCanvas(conf.width || 256, conf.height || 256); 55 | const ctx = canvas.getContext('2d'); 56 | const WIDTH = conf.width || 256; 57 | const HEIGHT = conf.height || 256; 58 | const elementWidth = conf.elementWidth || 16; 59 | const radius = elementWidth / 2; 60 | const verticallySymmetric = !( 61 | conf.horizontallySymmetric || conf.verticallySymmetric === false 62 | ); 63 | const horizontallySymmetric = conf.horizontallySymmetric || false; 64 | // triangles 65 | const direction = conf.direction || 'top'; 66 | // /traingles 67 | 68 | const elementsPerRow = !verticallySymmetric 69 | ? WIDTH / elementWidth 70 | : WIDTH / elementWidth / 2; 71 | const elementsPerCol = 72 | horizontallySymmetric && !verticallySymmetric 73 | ? HEIGHT / elementWidth / 2 74 | : HEIGHT / elementWidth; 75 | 76 | return { 77 | canvas, 78 | ctx, 79 | WIDTH, 80 | HEIGHT, 81 | elementWidth, 82 | radius, 83 | verticallySymmetric, 84 | horizontallySymmetric, 85 | direction, 86 | stroke: conf.stroke, 87 | bgColor: conf.bgColor, 88 | elementsPerRow, 89 | elementsPerCol, 90 | type: conf.type, 91 | body: conf.body, 92 | }; 93 | }; 94 | 95 | /** 96 | * Draw avatar specified in request body 97 | * @param {object} context 98 | */ 99 | const drawSpecified = (context) => { 100 | const lines = context.body.split('\n'); 101 | for (let i = 0; i < lines.length; i++) { 102 | const line = lines[i]; 103 | for (let j = 0; j < line.length; j++) { 104 | if (line.charAt(j) !== ' ') { 105 | context.positionX = j; 106 | context.positionY = i; 107 | drawShape(context.type || 'rect', context); 108 | } 109 | } 110 | } 111 | }; 112 | 113 | /** 114 | * Draw random avatar 115 | * @param {object} context 116 | */ 117 | const drawRandom = (context) => { 118 | for (let i = 0; i < context.elementsPerCol; i++) { 119 | for (let j = 0; j < context.elementsPerRow; j++) { 120 | if (Math.random() > 0.5) { 121 | context.positionX = j; 122 | context.positionY = i; 123 | drawShape(context.type || 'rect', context); 124 | } 125 | } 126 | } 127 | }; 128 | 129 | /** 130 | * Create an avatar 131 | * @param {object} conf - config 132 | * @param {function} cb - callback 133 | */ 134 | const createAvatar = (conf, cb) => { 135 | conf = parseConfig(conf); 136 | 137 | const context = createContext(conf); 138 | const ctx = context.ctx; 139 | const mimeType = conf.mimeType || 'image/png'; 140 | 141 | if (context.bgColor) { 142 | ctx.beginPath(); 143 | ctx.fillStyle = conf.bgColor; 144 | ctx.fillRect(0, 0, context.WIDTH, context.HEIGHT); 145 | } 146 | 147 | // NOTE: Need to change colors after bg is drawn 148 | ctx.fillStyle = conf.color || 'blue'; 149 | ctx.strokeStyle = conf.strokeColor || 'black'; 150 | 151 | if (context.body) { 152 | drawSpecified(context); 153 | } else { 154 | drawRandom(context); 155 | } 156 | 157 | ctx.fill(); 158 | context.stroke && ctx.stroke(); 159 | 160 | const buffer = context.canvas.toBuffer(mimeType); 161 | 162 | return cb(null, { 163 | contentType: mimeType, 164 | buffer, 165 | }); 166 | }; 167 | 168 | module.exports = { 169 | createAvatar, 170 | }; 171 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | const { createCanvas } = require('canvas'); 2 | 3 | /** 4 | * Parse config values 5 | * @param {object} conf - config 6 | * @returns {object} config 7 | */ 8 | const parseConfig = (conf) => { 9 | // Ensure the values of the following keys are numbers 10 | const intKeys = ['width', 'height', 'elementWidth']; 11 | for (let i = 0; i < intKeys.length; i++) { 12 | if (conf[intKeys[i]]) { 13 | conf[intKeys[i]] = parseInt(conf[intKeys[i]], 10); 14 | } 15 | } 16 | 17 | // Ensure the values of the following keys are bools 18 | const boolKeys = ['verticallySymmetric', 'horizontallySymmetric', 'stroke']; 19 | for (let i = 0; i < boolKeys.length; i++) { 20 | if (conf[boolKeys[i]]) { 21 | conf[boolKeys[i]] = conf[boolKeys[i]] === 'true'; 22 | } 23 | } 24 | 25 | return conf; 26 | }; 27 | 28 | /** 29 | * Translate config to context 30 | * @param {object} conf - config 31 | * @returns {object} context 32 | */ 33 | const createContext = (conf) => { 34 | const canvas = createCanvas(conf.width || 256, conf.height || 256); 35 | const ctx = canvas.getContext('2d'); 36 | const WIDTH = conf.width || 256; 37 | const HEIGHT = conf.height || 256; 38 | const elementWidth = conf.elementWidth || 16; 39 | const radius = elementWidth / 2; 40 | const verticallySymmetric = !( 41 | conf.horizontallySymmetric || conf.verticallySymmetric === false 42 | ); 43 | const horizontallySymmetric = conf.horizontallySymmetric || false; 44 | // triangles 45 | const direction = conf.direction || 'top'; 46 | // /triangles 47 | // initials 48 | const letters = conf.letters || 'AA'; 49 | const rotation = conf.rotation || 0; 50 | const fontFamily = conf.fontFamily || 'Helvetica'; 51 | const fontSize = conf.fontSize || '100'; 52 | const positionX = conf.positionX; 53 | const positionY = conf.positionY; 54 | const widthFactor = conf.widthFactor || 1.3; 55 | const heightFactor = conf.heighFactor || 0.8; 56 | // /initials 57 | 58 | const elementsPerRow = !verticallySymmetric 59 | ? WIDTH / elementWidth 60 | : WIDTH / elementWidth / 2; 61 | const elementsPerCol = 62 | horizontallySymmetric && !verticallySymmetric 63 | ? HEIGHT / elementWidth / 2 64 | : HEIGHT / elementWidth; 65 | 66 | return { 67 | canvas, 68 | ctx, 69 | WIDTH, 70 | HEIGHT, 71 | elementWidth, 72 | radius, 73 | verticallySymmetric, 74 | horizontallySymmetric, 75 | // triangles 76 | direction, 77 | // /triangles 78 | // initials 79 | letters, 80 | rotation, 81 | fontFamily, 82 | fontSize, 83 | positionX, 84 | positionY, 85 | widthFactor, 86 | heightFactor, 87 | // /initials 88 | stroke: conf.stroke, 89 | bgColor: conf.bgColor, 90 | elementsPerRow, 91 | elementsPerCol, 92 | type: conf.type, 93 | body: conf.body, 94 | }; 95 | }; 96 | 97 | module.exports = { 98 | parseConfig, 99 | createContext, 100 | }; 101 | -------------------------------------------------------------------------------- /src/utils/initials.js: -------------------------------------------------------------------------------- 1 | const { parseConfig, createContext } = require('./config'); 2 | 3 | /** 4 | * Draw selected shape with context provided 5 | * @param {object} context 6 | */ 7 | const draw = (context) => { 8 | const ctx = context.ctx; 9 | ctx.font = context.fontSize + 'px' + ' ' + context.fontFamily; // default: '30px Helvetica' 10 | ctx.rotate(context.rotation); 11 | if (context.type === 'custom') { 12 | ctx.fillText( 13 | context.letters, 14 | context.positionX, 15 | context.positionY 16 | ); 17 | } else { // center 18 | ctx.fillText( 19 | context.letters, 20 | context.WIDTH / 2 - context.fontSize * context.widthFactor / 2, 21 | context.HEIGHT / 2 + context.fontSize * context.heightFactor / 2 22 | ); 23 | } 24 | }; 25 | 26 | const createInitialsAvatar = (conf, cb) => { 27 | conf = parseConfig(conf); 28 | 29 | const context = createContext(conf); 30 | const ctx = context.ctx; 31 | const mimeType = conf.mimeType || 'image/png'; 32 | 33 | if (context.bgColor) { 34 | ctx.beginPath(); 35 | ctx.fillStyle = conf.bgColor; 36 | ctx.fillRect(0, 0, context.WIDTH, context.HEIGHT); 37 | } 38 | 39 | // NOTE: Need to change colors after bg is drawn 40 | ctx.fillStyle = conf.color || 'blue'; 41 | ctx.strokeStyle = conf.strokeColor || 'black'; 42 | 43 | draw(context); 44 | 45 | // ctx.fill(); 46 | // context.stroke && ctx.stroke(); 47 | 48 | const buffer = context.canvas.toBuffer(mimeType); 49 | 50 | return cb(null, { 51 | contentType: mimeType, 52 | buffer, 53 | }); 54 | }; 55 | 56 | module.exports = { 57 | createInitialsAvatar, 58 | }; 59 | -------------------------------------------------------------------------------- /src/utils/random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Select random shape type 3 | * @returns {string} shape type 4 | */ 5 | const getRandomShapeType = () => { 6 | const factor = Math.round(Math.random() * 2); 7 | let type = ''; // '' equals rect 8 | switch (factor) { 9 | case 0: 10 | type = ''; 11 | break; 12 | case 1: 13 | type = 'circle'; 14 | break; 15 | case 2: 16 | type = 'triangle'; 17 | break; 18 | default: 19 | type = ''; 20 | break; 21 | } 22 | 23 | return type; 24 | }; 25 | 26 | module.exports = { 27 | getRandomShapeType, 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/respond.js: -------------------------------------------------------------------------------- 1 | const logger = require('../logger') 2 | 3 | /** 4 | * Response helper 5 | * @param {object} conf - config 6 | */ 7 | const respond = (conf) => { 8 | if (typeof conf !== 'object') { 9 | return false; 10 | } 11 | 12 | const contentType = conf.contentType; 13 | const data = conf.data; 14 | const err = conf.err; 15 | const next = conf.next; 16 | const res = conf.res; 17 | const status = conf.status; 18 | 19 | if (typeof next !== 'function') { 20 | return false; 21 | } 22 | 23 | if (err) { 24 | logger.error(err); 25 | 26 | res.set('Content-Type', 'application/json'); 27 | res.status(status || 400); 28 | res.send({ 29 | message: err.message, 30 | }); 31 | return next(); 32 | } 33 | res.set('Content-Type', contentType || 'application/json'); 34 | res.status(200); 35 | res.send(data); 36 | 37 | return next(); 38 | }; 39 | 40 | module.exports = respond; 41 | --------------------------------------------------------------------------------