diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index eaca9390..253924c3 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -5,50 +5,7 @@ on: push jobs: build: runs-on: ubuntu-latest - container: - image: elixir:1.10.3 - services: - db: - image: postgres:latest - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: 'postgres' - POSTGRES_USER: 'postgres' - POSTGRES_DB: 'philomena_test' - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - elasticsearch: - image: elasticsearch:7.8.1 - ports: ['9200:9200'] - redis: - image: redis:5.0.9 - ports: ['6379:6379'] steps: - - uses: actions/checkout@v1 - - name: Install Ubuntu Dependencies - run: | - apt-get update - apt-get -yqq install libpq-dev postgresql-client - - name: Install Dependencies - env: - MIX_ENV: 'test' - run: | - mix local.rebar --force - mix local.hex --force - mix deps.get - - name: Run Tests - env: - MIX_ENV: 'test' - PGUSER: 'postgres' - PGPASSWORD: 'postgres' - PGDATABASE: 'philomena_test' - PGPORT: 5432 - PGHOST: db - run: | - mix ecto.create - mix ecto.load - mix reindex_all - mix test + - uses: actions/checkout@v2 + - name: Build and test + run: docker-compose run app run-test diff --git a/assets/package-lock.json b/assets/package-lock.json index 4ab665aa..b565b494 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -3,9 +3,9 @@ "lockfileVersion": 1, "dependencies": { "@fortawesome/fontawesome-free": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.14.0.tgz", - "integrity": "sha512-OfdMsF+ZQgdKHP9jUbmDcRrP0eX90XXrsXIdyjLbkmSBzmMXPABB8eobUJtivaupucYaByz6WNe1PI1JuYm3qA==" + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz", + "integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ==" }, "@nodelib/fs.scandir": { "version": "2.1.3", @@ -262,9 +262,9 @@ "dev": true }, "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, "acorn-dynamic-import": { "version": "4.0.0", @@ -2381,9 +2381,9 @@ "dev": true }, "copy-webpack-plugin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.1.0.tgz", - "integrity": "sha512-aWjIuLt1OVQxaDVffnt3bnGmLA8zGgAJaFwPA+a+QYVPh1vhIKjVfh3SbOFLV0kRPvGBITbw17n5CsmiBS4LQQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", + "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", "dev": true, "requires": { "cacache": "^15.0.5", @@ -2394,15 +2394,21 @@ "loader-utils": "^2.0.0", "normalize-path": "^3.0.0", "p-limit": "^3.0.2", - "schema-utils": "^2.7.1", - "serialize-javascript": "^4.0.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", "webpack-sources": "^1.4.3" }, "dependencies": { + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2456,15 +2462,24 @@ } }, "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } } } }, @@ -3319,12 +3334,20 @@ "dev": true }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -3610,19 +3633,25 @@ "dev": true }, "file-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.0.tgz", - "integrity": "sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", "dev": true, "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.7.1" + "schema-utils": "^3.0.0" }, "dependencies": { + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -3658,13 +3687,13 @@ } }, "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } } @@ -7073,9 +7102,9 @@ } }, "rollup": { - "version": "2.26.10", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.10.tgz", - "integrity": "sha512-dUnjCWOA0h9qNX6qtcHidyatz8FAFZxVxt1dbcGtKdlJkpSxGK3G9+DLCYvtZr9v94D129ij9zUhG+xbRoqepw==", + "version": "2.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz", + "integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==", "dev": true, "requires": { "fsevents": "~2.1.2" @@ -7822,19 +7851,19 @@ } }, "style-loader": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", - "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", "dev": true, "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.6.6" + "schema-utils": "^2.7.0" }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -7843,6 +7872,12 @@ "uri-js": "^4.2.2" } }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -7864,14 +7899,14 @@ } }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } } } @@ -8147,9 +8182,9 @@ } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "tty-browserify": { @@ -8428,9 +8463,9 @@ }, "dependencies": { "chokidar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", - "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", "dev": true, "optional": true, "requires": { @@ -8441,13 +8476,13 @@ "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.5.0" } }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "optional": true, "requires": { @@ -8648,9 +8683,9 @@ } }, "webpack": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", - "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -8679,9 +8714,9 @@ }, "dependencies": { "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, "cacache": { @@ -8766,15 +8801,6 @@ "semver": "^5.6.0" } }, - "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8791,16 +8817,16 @@ } }, "terser-webpack-plugin": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", - "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", diff --git a/assets/package.json b/assets/package.json index 2432c13d..4e2e7c69 100644 --- a/assets/package.json +++ b/assets/package.json @@ -4,33 +4,33 @@ "watch": "webpack --watch" }, "dependencies": { - "@fortawesome/fontawesome-free": "^5.14.0", + "@fortawesome/fontawesome-free": "^5.15.1", "brunch": "^3.0.0", "copycat-brunch": "^1.1.1" }, "devDependencies": { - "acorn": "^7.4.0", + "acorn": "^7.4.1", "autoprefixer": "^9.8.6", - "copy-webpack-plugin": "^6.1.0", + "copy-webpack-plugin": "^6.2.1", "cross-env": "^7.0.2", "css-loader": "^3.6.0", "extract-loader": "^5.1.0", - "file-loader": "^6.1.0", + "file-loader": "^6.1.1", "node-sass": "^4.14.1", "normalize-scss": "^7.0.1", "optimize-css-assets-webpack-plugin": "^5.0.4", "postcss-loader": "^3.0.0", "postcss-scss": "^2.1.1", - "rollup": "^2.26.10", + "rollup": "^2.32.1", "rollup-plugin-buble": "^0.19.8", "rollup-plugin-includepaths": "^0.2.4", "rollup-plugin-multi-entry": "^2.1.0", "rollup-plugin-virtual": "^1.0.1", "sass-loader": "^8.0.2", "source-map-support": "^0.5.19", - "style-loader": "^1.2.1", + "style-loader": "^1.3.0", "terser-webpack-plugin": "^3.1.0", - "webpack": "^4.44.1", + "webpack": "^4.44.2", "webpack-cli": "^3.3.12", "webpack-rollup-loader": "^0.8.0" } diff --git a/config/config.exs b/config/config.exs index cebcd0c8..716e2ff6 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,44 +7,23 @@ # General application configuration import Config +config :logger, + compile_time_purge_matching: [ + [application: :remote_ip], + [application: :mint] + ] + config :philomena, - ecto_repos: [Philomena.Repo], - elasticsearch_url: "http://localhost:9200", - redis_host: "localhost", - password_pepper: "dn2e0EpZrvBLoxUM3gfQveBhjf0bG/6/bYhrOyq3L3hV9hdo/bimJ+irbDWsuXLP", - otp_secret_key: "Wn7O/8DD+qxL0X4X7bvT90wOkVGcA90bIHww4twR03Ci//zq7PnMw8ypqyyT/b/C", - tumblr_api_key: "fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4", - image_url_root: "/img", - avatar_url_root: "/avatars", - advert_url_root: "/spns", - badge_url_root: "/media", - tag_url_root: "/media", - channel_url_root: "/media", - image_file_root: "priv/static/system/images", - advert_file_root: "priv/static/system/images/adverts", - avatar_file_root: "priv/static/system/images/avatars", - badge_file_root: "priv/static/system/images", - channel_image_file_root: "priv/static/system/images", - channel_banner_file_root: "priv/static/system/images", - tag_file_root: "priv/static/system/images", - hcaptcha_site_key: "10000000-ffff-ffff-ffff-000000000001", - hcaptcha_secret_key: "0x0000000000000000000000000000000000000000", - cdn_host: "", - proxy_host: nil, - app_dir: File.cwd!() - -config :exq, - queues: [{"videos", 2}, {"images", 4}, {"indexing", 16}], - scheduler_enable: true, - max_retries: 1, - start_on_application: false - -config :bcrypt_elixir, - log_rounds: 12 + ecto_repos: [Philomena.Repo] config :elastix, json_codec: Jason +config :exq, + max_retries: 2, + scheduler_enable: true, + start_on_application: false + config :canary, repo: Philomena.Repo, unauthorized_handler: {PhilomenaWeb.NotAuthorizedPlug, :call}, @@ -60,7 +39,6 @@ config :philomena, PhilomenaWeb.Endpoint, config :phoenix, :template_engines, slim: PhoenixSlime.Engine, slime: PhoenixSlime.Engine, - # If you want to use LiveView slimleex: PhoenixSlime.LiveViewEngine config :tesla, adapter: Tesla.Adapter.Mint @@ -79,4 +57,4 @@ config :bamboo, :json_library, Jason # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{Mix.env()}.exs" +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 443e4ac5..16da50e6 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,20 +1,7 @@ import Config # Configure your database -config :philomena, Philomena.Repo, - username: "postgres", - password: "postgres", - database: "philomena_dev", - hostname: "postgres", - show_sensitive_data_on_connection_error: true, - pool_size: 10 - -config :philomena, - elasticsearch_url: "http://elasticsearch:9200", - redis_host: "redis" - -config :exq, - host: "redis" +config :philomena, Philomena.Repo, show_sensitive_data_on_connection_error: true # For development, we disable any cache and enable # debugging and code reloading. @@ -68,15 +55,6 @@ config :philomena, PhilomenaWeb.Endpoint, # Do not include metadata nor timestamps in development logs config :logger, :console, format: "[$level] $message\n" -config :logger, compile_time_purge_matching: [[application: :remote_ip], [application: :mint]] - -# Set up mailer -config :philomena, Philomena.Mailer, adapter: Bamboo.LocalAdapter - -config :philomena, :mailer_address, "noreply@philomena.lc" - -# Use this to debug slime templates -# config :slime, :keep_lines, true # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. diff --git a/config/prod.exs b/config/prod.exs index 3a7bfce0..1480344d 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -9,9 +9,7 @@ import Config # manifest is generated by the `mix phx.digest` task, # which you should run after static files are built and # before starting your production server. -config :philomena, PhilomenaWeb.Endpoint, - url: [host: "example.com", port: 80], - cache_static_manifest: "priv/static/cache_manifest.json" +config :philomena, PhilomenaWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" # Do not print debug messages in production config :logger, level: :warn diff --git a/config/releases.exs b/config/releases.exs deleted file mode 100644 index 8234efaa..00000000 --- a/config/releases.exs +++ /dev/null @@ -1,62 +0,0 @@ -import Config - -# ## Using releases (Elixir v1.9+) -# -# If you are doing OTP releases, you need to instruct Phoenix -# to start each relevant endpoint: -# -# config :philomena, PhilomenaWeb.Endpoint, server: true -# -# Then you can assemble a release by calling `mix release`. -# See `mix help release` for more information. - -config :bcrypt_elixir, - log_rounds: String.to_integer(System.get_env("BCRYPT_ROUNDS") || "12") - -config :philomena, - channel_image_file_root: System.fetch_env!("CHANNEL_IMAGE_FILE_ROOT"), - channel_banner_file_root: System.fetch_env!("CHANNEL_BANNER_FILE_ROOT"), - anonymous_name_salt: System.fetch_env!("ANONYMOUS_NAME_SALT"), - hcaptcha_secret_key: System.fetch_env!("HCAPTCHA_SECRET_KEY"), - hcaptcha_site_key: System.fetch_env!("HCAPTCHA_SITE_KEY"), - elasticsearch_url: System.get_env("ELASTICSEARCH_URL") || "http://localhost:9200", - advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"), - avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"), - channel_url_root: System.fetch_env!("CHANNEL_URL_ROOT"), - badge_file_root: System.fetch_env!("BADGE_FILE_ROOT"), - password_pepper: System.fetch_env!("PASSWORD_PEPPER"), - avatar_url_root: System.fetch_env!("AVATAR_URL_ROOT"), - advert_url_root: System.fetch_env!("ADVERT_URL_ROOT"), - image_file_root: System.fetch_env!("IMAGE_FILE_ROOT"), - tumblr_api_key: System.fetch_env!("TUMBLR_API_KEY"), - otp_secret_key: System.fetch_env!("OTP_SECRET_KEY"), - image_url_root: System.fetch_env!("IMAGE_URL_ROOT"), - badge_url_root: System.fetch_env!("BADGE_URL_ROOT"), - mailer_address: System.fetch_env!("MAILER_ADDRESS"), - tag_file_root: System.fetch_env!("TAG_FILE_ROOT"), - tag_url_root: System.fetch_env!("TAG_URL_ROOT"), - redis_host: System.get_env("REDIS_HOST") || "localhost", - proxy_host: System.get_env("PROXY_HOST"), - camo_host: System.fetch_env!("CAMO_HOST"), - camo_key: System.fetch_env!("CAMO_KEY"), - cdn_host: System.fetch_env!("CDN_HOST") - -config :philomena, Philomena.Repo, - url: System.fetch_env!("DATABASE_URL"), - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "16") - -config :philomena, Philomena.Mailer, - adapter: Bamboo.SMTPAdapter, - server: System.fetch_env!("SMTP_RELAY"), - hostname: System.fetch_env!("SMTP_DOMAIN"), - port: System.get_env("SMTP_PORT") || 587, - username: System.fetch_env!("SMTP_USERNAME"), - password: System.fetch_env!("SMTP_PASSWORD"), - tls: :always, - auth: :always - -config :philomena, PhilomenaWeb.Endpoint, - http: [ip: {127, 0, 0, 1}, port: {:system, "PORT"}], - url: [host: System.fetch_env!("APP_HOSTNAME"), scheme: "https", port: 443], - secret_key_base: System.fetch_env!("SECRET_KEY_BASE"), - server: true diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 00000000..65fc4d90 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,80 @@ +import Config + +# ## Using releases (Elixir v1.9+) +# +# If you are doing OTP releases, you can assemble a release +# by calling `mix release`. +# +# See `mix help release` for more information. + +config :bcrypt_elixir, + log_rounds: String.to_integer(System.get_env("BCRYPT_ROUNDS", "12")) + +config :philomena, + anonymous_name_salt: System.fetch_env!("ANONYMOUS_NAME_SALT"), + hcaptcha_secret_key: System.fetch_env!("HCAPTCHA_SECRET_KEY"), + hcaptcha_site_key: System.fetch_env!("HCAPTCHA_SITE_KEY"), + elasticsearch_url: System.get_env("ELASTICSEARCH_URL", "http://localhost:9200"), + advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"), + avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"), + channel_url_root: System.fetch_env!("CHANNEL_URL_ROOT"), + badge_file_root: System.fetch_env!("BADGE_FILE_ROOT"), + password_pepper: System.fetch_env!("PASSWORD_PEPPER"), + avatar_url_root: System.fetch_env!("AVATAR_URL_ROOT"), + advert_url_root: System.fetch_env!("ADVERT_URL_ROOT"), + image_file_root: System.fetch_env!("IMAGE_FILE_ROOT"), + tumblr_api_key: System.fetch_env!("TUMBLR_API_KEY"), + otp_secret_key: System.fetch_env!("OTP_SECRET_KEY"), + image_url_root: System.fetch_env!("IMAGE_URL_ROOT"), + badge_url_root: System.fetch_env!("BADGE_URL_ROOT"), + mailer_address: System.fetch_env!("MAILER_ADDRESS"), + tag_file_root: System.fetch_env!("TAG_FILE_ROOT"), + tag_url_root: System.fetch_env!("TAG_URL_ROOT"), + redis_host: System.get_env("REDIS_HOST", "localhost"), + proxy_host: System.get_env("PROXY_HOST"), + camo_host: System.get_env("CAMO_HOST"), + camo_key: System.get_env("CAMO_KEY"), + cdn_host: System.fetch_env!("CDN_HOST"), + app_dir: System.get_env("APP_DIR", File.cwd!()) + +config :exq, + host: System.get_env("REDIS_HOST", "localhost"), + queues: [{"videos", 2}, {"images", 4}, {"indexing", 16}] + +if is_nil(System.get_env("START_WORKER")) do + # Make queueing available but don't process any jobs + config :exq, queues: [] +end + +if config_env() != :test do + # Database config + config :philomena, Philomena.Repo, + url: System.fetch_env!("DATABASE_URL"), + pool_size: String.to_integer(System.get_env("POOL_SIZE", "16")) +end + +if config_env() == :prod do + # Production mailer config + config :philomena, Philomena.Mailer, + adapter: Bamboo.SMTPAdapter, + server: System.fetch_env!("SMTP_RELAY"), + hostname: System.fetch_env!("SMTP_DOMAIN"), + port: System.get_env("SMTP_PORT") || 587, + username: System.fetch_env!("SMTP_USERNAME"), + password: System.fetch_env!("SMTP_PASSWORD"), + tls: :always, + auth: :always + + # Production endpoint config + config :philomena, PhilomenaWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: {:system, "PORT"}], + url: [host: System.fetch_env!("APP_HOSTNAME"), scheme: "https", port: 443], + secret_key_base: System.fetch_env!("SECRET_KEY_BASE"), + server: not is_nil(System.get_env("START_ENDPOINT")) +else + # Don't send email in development + config :philomena, Philomena.Mailer, adapter: Bamboo.LocalAdapter + + # Use this to debug slime templates + # config :slime, :keep_lines, true +end diff --git a/config/test.exs b/config/test.exs index 6291bab8..c5c016f1 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,27 +1,17 @@ import Config -# Only in tests, remove the complexity from the password hashing algorithm -config :bcrypt_elixir, :log_rounds, 1 - # Configure your database config :philomena, Philomena.Repo, + hostname: "postgres", username: "postgres", password: "postgres", database: "philomena_test", pool: Ecto.Adapters.SQL.Sandbox config :philomena, - elasticsearch_url: "http://elasticsearch:9200", - redis_host: "redis", pwned_passwords: false, captcha: false -config :exq, - host: "redis" - -config :philomena, Philomena.Mailer, adapter: Bamboo.LocalAdapter -config :philomena, :mailer_address, "test@philomena.lc" - # We don't run a server during test. If one is required, # you can enable the server option below. config :philomena, PhilomenaWeb.Endpoint, diff --git a/docker-compose.yml b/docker-compose.yml index 88891054..3fb6810d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,29 @@ services: environment: - MIX_ENV=dev - PGPASSWORD=postgres + - ANONYMOUS_NAME_SALT=2fmJRo0OgMFe65kyAJBxPT0QtkVes/jnKDdtP21fexsRqiw8TlSY7yO+uFyMZycp + - HCAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000 + - HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001 + - PASSWORD_PEPPER=dn2e0EpZrvBLoxUM3gfQveBhjf0bG/6/bYhrOyq3L3hV9hdo/bimJ+irbDWsuXLP + - TUMBLR_API_KEY=fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4 + - OTP_SECRET_KEY=Wn7O/8DD+qxL0X4X7bvT90wOkVGcA90bIHww4twR03Ci//zq7PnMw8ypqyyT/b/C + - ADVERT_FILE_ROOT=priv/static/system/images/adverts + - AVATAR_FILE_ROOT=priv/static/system/images/avatars + - BADGE_FILE_ROOT=priv/static/system/images + - IMAGE_FILE_ROOT=priv/static/system/images + - TAG_FILE_ROOT=priv/static/system/images + - CHANNEL_URL_ROOT=/media + - AVATAR_URL_ROOT=/avatars + - ADVERT_URL_ROOT=/spns + - IMAGE_URL_ROOT=/img + - BADGE_URL_ROOT=/media + - TAG_URL_ROOT=/media + - ELASTICSEARCH_URL=http://elasticsearch:9200 + - REDIS_HOST=redis + - DATABASE_URL=ecto://postgres:postgres@postgres/philomena_dev + - CDN_HOST= + - MAILER_ADDRESS=noreply@philomena.local + - START_ENDPOINT=true working_dir: /srv/philomena tty: true volumes: @@ -21,7 +44,7 @@ services: - redis postgres: - image: postgres:12.4 + image: postgres:13.0 environment: - POSTGRES_PASSWORD=postgres volumes: @@ -30,7 +53,7 @@ services: driver: "none" elasticsearch: - image: elasticsearch:7.8.1 + image: elasticsearch:7.9.3 volumes: - elastic_data:/var/lib/elasticsearch logging: @@ -43,7 +66,7 @@ services: hard: 65536 redis: - image: redis:6.0.7 + image: redis:6.0.8 logging: driver: "none" diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index c8ccb816..2a4078b3 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,26 +1,30 @@ -FROM elixir:1.10.4 +FROM hexpm/elixir:1.11.1-erlang-23.1.1-ubuntu-focal-20200703 + +ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && apt-get -qq -y install apt-transport-https \ - && echo "deb https://deb.nodesource.com/node_12.x stretch main" >> /etc/apt/sources.list \ - && echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" >> /etc/apt/sources.list \ + && apt-get -qq -y install apt-transport-https wget gnupg \ + && echo "deb https://deb.nodesource.com/node_12.x focal main" >> /etc/apt/sources.list \ + && echo "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main" >> /etc/apt/sources.list \ && wget -qO - "https://www.postgresql.org/media/keys/ACCC4CF8.asc" | apt-key add - \ && wget -qO - "https://deb.nodesource.com/gpgkey/nodesource.gpg.key" | apt-key add - \ && apt-get update \ - && apt-get -qq -y install inotify-tools postgresql-client build-essential git ffmpeg libavformat-dev libavcodec-dev libswscale-dev nodejs libmagic-dev libpng-dev gifsicle optipng libjpeg-progs librsvg2-bin \ + && apt-get -qq -y dist-upgrade \ + && apt-get -qq -y install inotify-tools postgresql-client build-essential git ffmpeg libavformat-dev libavcodec-dev libswscale-dev nodejs libmagic-dev libpng-dev gifsicle optipng libjpeg-progs librsvg2-bin imagemagick \ && mix local.hex --force \ && mix local.rebar --force -ADD https://api.github.com/repos/derpibooru/cli_intensities/git/refs/heads/master /tmp/cli_intensities_version.json -RUN git clone https://github.com/derpibooru/cli_intensities /tmp/cli_intensities \ +ADD https://api.github.com/repos/philomena-dev/cli_intensities/git/refs/heads/master /tmp/cli_intensities_version.json +RUN git clone https://github.com/philomena-dev/cli_intensities /tmp/cli_intensities \ && cd /tmp/cli_intensities \ && make install -ADD https://api.github.com/repos/derpibooru/mediatools/git/refs/heads/master /tmp/mediatools_version.json -RUN git clone https://github.com/derpibooru/mediatools /tmp/mediatools \ +ADD https://api.github.com/repos/philomena-dev/mediatools/git/refs/heads/master /tmp/mediatools_version.json +RUN git clone https://github.com/philomena-dev/mediatools /tmp/mediatools \ && cd /tmp/mediatools \ && make install COPY docker/app/run-development /usr/local/bin/run-development +COPY docker/app/run-test /usr/local/bin/run-test COPY docker/app/safe-rsvg-convert /usr/local/bin/safe-rsvg-convert CMD run-development diff --git a/docker/app/run-development b/docker/app/run-development index fc501b91..d76bcfc3 100755 --- a/docker/app/run-development +++ b/docker/app/run-development @@ -1,5 +1,15 @@ #!/bin/bash +background() { + while :; do + mix run -e 'Philomena.Release.update_channels()' + mix run -e 'Philomena.Release.verify_user_links()' + mix run -e 'Philomena.Release.update_stats()' + + sleep 300 + done +} + # Always install assets ( cd /srv/philomena/assets @@ -27,5 +37,8 @@ createdb -h postgres -U postgres philomena_dev && mix ecto.setup_dev # Reindex mix reindex_all +# Run background jobs +background & + # Run the application -mix phx.server +START_WORKER=true mix phx.server diff --git a/docker/app/run-test b/docker/app/run-test new file mode 100755 index 00000000..99eb2072 --- /dev/null +++ b/docker/app/run-test @@ -0,0 +1,24 @@ +#!/bin/bash + +export MIX_ENV=test + +# Always install mix dependencies +(cd /srv/philomena && mix deps.get) + +# Sleep to allow Elasticsearch to finish initializing +# if it's not done doing whatever it does yet +echo -n "Waiting for Elasticsearch" + +until wget -qO - elasticsearch:9200; do + echo -n "." + sleep 2 +done + +echo + +# Create the database +mix ecto.create +mix ecto.load + +# Test the application +mix test diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index ef0b21c9..6e09bff0 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.18.0 +FROM nginx:1.19.3 ENV APP_DIR /srv/philomena COPY docker/web/nginx.conf /tmp/docker.nginx RUN envsubst '$APP_DIR' < /tmp/docker.nginx > /etc/nginx/conf.d/default.conf diff --git a/lib/mix/tasks/reindex_all.ex b/lib/mix/tasks/reindex_all.ex index 2af00554..809dd207 100644 --- a/lib/mix/tasks/reindex_all.ex +++ b/lib/mix/tasks/reindex_all.ex @@ -18,14 +18,13 @@ defmodule Mix.Tasks.ReindexAll do import Ecto.Query @shortdoc "Destroys and recreates all Elasticsearch indices." + @requirements ["app.start"] @impl Mix.Task def run(args) do if Mix.env() == :prod and not Enum.member?(args, "--i-know-what-im-doing") do raise "do not run this task unless you know what you're doing" end - {:ok, _apps} = Application.ensure_all_started(:philomena) - for {context, schema} <- [ {Images, Image}, {Comments, Comment}, diff --git a/lib/philomena/application.ex b/lib/philomena/application.ex index 4413bf01..f26f2023 100644 --- a/lib/philomena/application.ex +++ b/lib/philomena/application.ex @@ -16,16 +16,18 @@ defmodule Philomena.Application do # Starts a worker by calling: Philomena.Worker.start_link(arg) # {Philomena.Worker, arg}, - Philomena.Servers.UserLinkUpdater, - Philomena.Servers.PicartoChannelUpdater, - Philomena.Servers.PiczelChannelUpdater, Philomena.Servers.Config, {Redix, name: :redix, host: Application.get_env(:philomena, :redis_host)}, - {Phoenix.PubSub, [name: Philomena.PubSub, adapter: Phoenix.PubSub.PG2]}, + {Phoenix.PubSub, + [ + name: Philomena.PubSub, + adapter: Phoenix.PubSub.Redis, + host: Application.get_env(:philomena, :redis_host), + node_name: valid_node_name(node()) + ]}, # Start the endpoint when the application starts PhilomenaWeb.AdvertUpdater, - PhilomenaWeb.StatsUpdater, PhilomenaWeb.UserFingerprintUpdater, PhilomenaWeb.UserIpUpdater, PhilomenaWeb.Endpoint, @@ -46,4 +48,11 @@ defmodule Philomena.Application do PhilomenaWeb.Endpoint.config_change(changed, removed) :ok end + + # Redis adapter really really wants you to have a unique node name, + # so just fake one if iex is being started + defp valid_node_name(node) when node in [nil, :nonode@nohost], + do: Base.encode16(:crypto.strong_rand_bytes(6)) + + defp valid_node_name(node), do: node end diff --git a/lib/philomena/channels.ex b/lib/philomena/channels.ex index cfdbbc70..4e42e633 100644 --- a/lib/philomena/channels.ex +++ b/lib/philomena/channels.ex @@ -7,19 +7,48 @@ defmodule Philomena.Channels do alias Philomena.Repo alias Philomena.Channels.Channel + alias Philomena.Channels.PicartoChannel + alias Philomena.Channels.PiczelChannel alias Philomena.Notifications @doc """ - Returns the list of channels. - - ## Examples - - iex> list_channels() - [%Channel{}, ...] - + Updates all the tracked channels for which an update + scheme is known. """ - def list_channels do - Repo.all(Channel) + def update_tracked_channels! do + now = DateTime.utc_now() |> DateTime.truncate(:second) + + picarto_channels = PicartoChannel.live_channels(now) + live_picarto_channels = Map.keys(picarto_channels) + + piczel_channels = PiczelChannel.live_channels(now) + live_piczel_channels = Map.keys(piczel_channels) + + # Update all channels which are offline to reflect offline status + offline_query = + from c in Channel, + where: c.type == "PicartoChannel" and c.short_name not in ^live_picarto_channels, + or_where: c.type == "PiczelChannel" and c.short_name not in ^live_piczel_channels + + Repo.update_all(offline_query, set: [is_live: false, updated_at: now]) + + # Update all channels which are online to reflect online status using + # changeset functions + online_query = + from c in Channel, + where: c.type == "PicartoChannel" and c.short_name in ^live_picarto_channels, + or_where: c.type == "PiczelChannel" and c.short_name in ^live_picarto_channels + + online_query + |> Repo.all() + |> Enum.map(fn + %{type: "PicartoChannel", short_name: name} = channel -> + Channel.update_changeset(channel, Map.get(picarto_channels, name, [])) + + %{type: "PiczelChannel", short_name: name} = channel -> + Channel.update_changeset(channel, Map.get(piczel_channels, name, [])) + end) + |> Enum.map(&Repo.update!/1) end @doc """ diff --git a/lib/philomena/channels/channel.ex b/lib/philomena/channels/channel.ex index a7136257..0eb163cd 100644 --- a/lib/philomena/channels/channel.ex +++ b/lib/philomena/channels/channel.ex @@ -49,4 +49,16 @@ defmodule Philomena.Channels.Channel do |> validate_inclusion(:type, ["PicartoChannel", "PiczelChannel"]) |> put_change(:associated_artist_tag_id, tag_id) end + + def update_changeset(channel, attrs) do + cast(channel, attrs, [ + :title, + :is_live, + :nsfw, + :viewers, + :thumbnail_url, + :last_fetched_at, + :last_live_at + ]) + end end diff --git a/lib/philomena/channels/picarto_channel.ex b/lib/philomena/channels/picarto_channel.ex new file mode 100644 index 00000000..00a3d307 --- /dev/null +++ b/lib/philomena/channels/picarto_channel.ex @@ -0,0 +1,31 @@ +defmodule Philomena.Channels.PicartoChannel do + @api_online "https://api.picarto.tv/v1/online?adult=true&gaming=true" + + @spec live_channels(DateTime.t()) :: map() + def live_channels(now) do + @api_online + |> Philomena.Http.get() + |> case do + {:ok, %Tesla.Env{body: body, status: 200}} -> + body + |> Jason.decode!() + |> Map.new(&{&1["name"], fetch(&1, now)}) + + _error -> + %{} + end + end + + defp fetch(api, now) do + %{ + title: api["title"], + is_live: true, + nsfw: api["adult"], + viewers: api["viewers"], + thumbnail_url: api["thumbnails"]["web"], + last_fetched_at: now, + last_live_at: now, + description: nil + } + end +end diff --git a/lib/philomena/channels/piczel_channel.ex b/lib/philomena/channels/piczel_channel.ex new file mode 100644 index 00000000..23ce8a0d --- /dev/null +++ b/lib/philomena/channels/piczel_channel.ex @@ -0,0 +1,30 @@ +defmodule Philomena.Channels.PiczelChannel do + @api_online "https://api.piczel.tv/api/streams" + + @spec live_channels(DateTime.t()) :: map() + def live_channels(now) do + @api_online + |> Philomena.Http.get() + |> case do + {:ok, %Tesla.Env{body: body, status: 200}} -> + body + |> Jason.decode!() + |> Map.new(&{&1["slug"], fetch(&1, now)}) + + _error -> + %{} + end + end + + defp fetch(api, now) do + %{ + title: api["title"], + is_live: api["live"], + nsfw: api["adult"], + viewers: api["viewers"], + thumbnail_url: api["user"]["avatar"]["avatar"]["url"], + last_fetched_at: now, + last_live_at: now + } + end +end diff --git a/lib/philomena/release.ex b/lib/philomena/release.ex index 2ae529de..6ee19331 100644 --- a/lib/philomena/release.ex +++ b/lib/philomena/release.ex @@ -14,6 +14,21 @@ defmodule Philomena.Release do {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) end + def update_channels do + start_app() + Philomena.Channels.update_tracked_channels!() + end + + def verify_user_links do + start_app() + Philomena.UserLinks.automatic_verify!() + end + + def update_stats do + start_app() + PhilomenaWeb.StatsUpdater.update_stats!() + end + defp repos do Application.fetch_env!(@app, :ecto_repos) end @@ -21,4 +36,8 @@ defmodule Philomena.Release do defp load_app do Application.load(@app) end + + defp start_app do + Application.ensure_all_started(@app) + end end diff --git a/lib/philomena/servers/picarto_channel_updater.ex b/lib/philomena/servers/picarto_channel_updater.ex deleted file mode 100644 index b136058b..00000000 --- a/lib/philomena/servers/picarto_channel_updater.ex +++ /dev/null @@ -1,70 +0,0 @@ -defmodule Philomena.Servers.PicartoChannelUpdater do - alias Philomena.Channels.Channel - alias Philomena.Repo - import Ecto.Query - - @api_online "https://api.picarto.tv/v1/online?adult=true&gaming=true" - - def child_spec([]) do - %{ - id: Philomena.Servers.PicartoChannelUpdater, - start: {Philomena.Servers.PicartoChannelUpdater, :start_link, [[]]} - } - end - - def start_link([]) do - {:ok, spawn_link(&run/0)} - end - - defp run do - :timer.sleep(:timer.seconds(60)) - - now = DateTime.utc_now() |> DateTime.truncate(:second) - - @api_online - |> Philomena.Http.get() - |> handle_response(now) - - run() - end - - defp handle_response({:ok, %Tesla.Env{body: body, status: 200}}, now) do - resp = - body - |> Jason.decode!() - |> Map.new(&{&1["name"], &1}) - - live_channel_names = Map.keys(resp) - - Channel - |> where([c], c.type == "PicartoChannel" and c.short_name not in ^live_channel_names) - |> Repo.update_all([set: [is_live: false, updated_at: now]], log: false) - - Channel - |> where([c], c.type == "PicartoChannel" and c.short_name in ^live_channel_names) - |> Repo.all(log: false) - |> Enum.map(&fetch(&1, resp[&1.short_name], now)) - end - - defp handle_response(_response, _now), do: nil - - defp fetch(channel, api_response, now) do - Channel - |> where(id: ^channel.id) - |> Repo.update_all( - [ - set: [ - title: api_response["title"], - is_live: true, - nsfw: api_response["adult"], - viewers: api_response["viewers"], - thumbnail_url: api_response["thumbnails"]["web"], - last_fetched_at: now, - last_live_at: now, - description: nil - ] - ], - log: false - ) - end -end diff --git a/lib/philomena/servers/piczel_channel_updater.ex b/lib/philomena/servers/piczel_channel_updater.ex deleted file mode 100644 index bd703f77..00000000 --- a/lib/philomena/servers/piczel_channel_updater.ex +++ /dev/null @@ -1,70 +0,0 @@ -defmodule Philomena.Servers.PiczelChannelUpdater do - alias Philomena.Channels.Channel - alias Philomena.Repo - import Ecto.Query - - @api_online "https://api.piczel.tv/api/streams" - - def child_spec([]) do - %{ - id: Philomena.Servers.PiczelChannelUpdater, - start: {Philomena.Servers.PiczelChannelUpdater, :start_link, [[]]} - } - end - - def start_link([]) do - {:ok, spawn_link(&run/0)} - end - - defp run do - :timer.sleep(:timer.seconds(60)) - - now = DateTime.utc_now() |> DateTime.truncate(:second) - - @api_online - |> Philomena.Http.get() - |> handle_response(now) - - run() - end - - defp handle_response({:ok, %Tesla.Env{body: body, status: 200}}, now) do - resp = - body - |> Jason.decode!() - |> Map.new(&{&1["slug"], &1}) - - live_channel_names = Map.keys(resp) - - Channel - |> where([c], c.type == "PiczelChannel" and c.short_name not in ^live_channel_names) - |> Repo.update_all([set: [is_live: false, updated_at: now]], log: false) - - Channel - |> where([c], c.type == "PiczelChannel" and c.short_name in ^live_channel_names) - |> Repo.all(log: false) - |> Enum.map(&fetch(&1, resp[&1.short_name], now)) - end - - defp handle_response(_response, _now), do: nil - - defp fetch(channel, api_response, now) do - Channel - |> where(id: ^channel.id) - |> Repo.update_all( - [ - set: [ - title: api_response["title"], - is_live: api_response["live"], - nsfw: api_response["adult"], - viewers: api_response["viewers"], - thumbnail_url: api_response["user"]["avatar"]["avatar"]["url"], - last_fetched_at: now, - last_live_at: now, - description: nil - ] - ], - log: false - ) - end -end diff --git a/lib/philomena/servers/user_link_updater.ex b/lib/philomena/servers/user_link_updater.ex deleted file mode 100644 index db0e2c3b..00000000 --- a/lib/philomena/servers/user_link_updater.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule Philomena.Servers.UserLinkUpdater do - alias Philomena.UserLinks.UserLink - alias Philomena.Repo - import Ecto.Query - - @seven_days 7 * 24 * 60 * 60 - @three_days 3 * 24 * 60 * 60 - @twelve_hours 12 * 60 * 60 - @one_hour 60 * 60 - @two_minutes 2 * 60 - - def child_spec([]) do - %{ - id: Philomena.Servers.UserLinkUpdater, - start: {Philomena.Servers.UserLinkUpdater, :start_link, [[]]} - } - end - - def start_link([]) do - {:ok, spawn_link(&run/0)} - end - - defp run do - now = DateTime.utc_now() - - UserLink - |> where([ul], ul.aasm_state == "unverified" and ul.next_check_at < ^now) - |> Repo.all(log: false) - |> Enum.map(&automatic_verify/1) - - :timer.sleep(:timer.seconds(120)) - run() - end - - defp automatic_verify(user_link) do - now = DateTime.utc_now() |> DateTime.truncate(:second) - diff = DateTime.diff(now, DateTime.from_naive!(user_link.created_at, "Etc/UTC"), :second) - - # Set next check time according to how long link has been pending - - next_check_at = - cond do - diff > @seven_days -> - DateTime.add(now, @seven_days) - - diff > @three_days -> - DateTime.add(now, @twelve_hours) - - diff > @one_hour -> - DateTime.add(now, @one_hour) - - true -> - DateTime.add(now, @two_minutes) - end - - UserLink - |> where(id: ^user_link.id) - |> Repo.update_all(set: [next_check_at: next_check_at]) - - user_link - |> Map.get(:uri) - |> Philomena.Http.get() - |> handle_response(user_link) - end - - defp handle_response({:ok, %Tesla.Env{body: body, status: 200}}, user_link) do - case :binary.match(body, user_link.verification_code) do - :nomatch -> - nil - - _match -> - UserLink - |> where(id: ^user_link.id) - |> Repo.update_all(set: [next_check_at: nil, aasm_state: "link_verified"]) - end - end - - defp handle_response(_, _user_link), do: nil -end diff --git a/lib/philomena/user_links.ex b/lib/philomena/user_links.ex index 3e2ae7b8..6609939f 100644 --- a/lib/philomena/user_links.ex +++ b/lib/philomena/user_links.ex @@ -8,21 +8,33 @@ defmodule Philomena.UserLinks do alias Philomena.Repo alias Philomena.UserLinks.UserLink + alias Philomena.UserLinks.AutomaticVerifier alias Philomena.Badges.Badge alias Philomena.Badges.Award alias Philomena.Tags.Tag @doc """ - Returns the list of user_links. - - ## Examples - - iex> list_user_links() - [%UserLink{}, ...] - + Check links pending verification to see if the user placed + the appropriate code on the page. """ - def list_user_links do - Repo.all(UserLink) + def automatic_verify! do + now = DateTime.utc_now() |> DateTime.truncate(:second) + + # Automatically retry in an hour if we don't manage to + # successfully verify any given link + recheck_time = DateTime.add(now, 3600, :second) + + recheck_query = + from ul in UserLink, + where: ul.aasm_state == "unverified", + where: ul.next_check_at < ^now + + recheck_query + |> Repo.all() + |> Enum.map(fn link -> + UserLink.automatic_verify_changeset(link, AutomaticVerifier.check_link(link, recheck_time)) + end) + |> Enum.map(&Repo.update!/1) end @doc """ diff --git a/lib/philomena/user_links/automatic_verifier.ex b/lib/philomena/user_links/automatic_verifier.ex new file mode 100644 index 00000000..7cd3e6b5 --- /dev/null +++ b/lib/philomena/user_links/automatic_verifier.ex @@ -0,0 +1,22 @@ +defmodule Philomena.UserLinks.AutomaticVerifier do + def check_link(user_link, recheck_time) do + user_link.uri + |> Philomena.Http.get() + |> contains_verification_code?(user_link.verification_code) + |> case do + true -> + %{next_check_at: nil, aasm_state: "link_verified"} + + false -> + %{next_check_at: recheck_time} + end + end + + defp contains_verification_code?({:ok, %Tesla.Env{body: body, status: 200}}, code) do + String.contains?(body, code) + end + + defp contains_verification_code?(_response, _code) do + false + end +end diff --git a/lib/philomena/user_links/user_link.ex b/lib/philomena/user_links/user_link.ex index 48e3ba79..2f670fcd 100644 --- a/lib/philomena/user_links/user_link.ex +++ b/lib/philomena/user_links/user_link.ex @@ -63,6 +63,10 @@ defmodule Philomena.UserLinks.UserLink do change(user_link, aasm_state: "rejected") end + def automatic_verify_changeset(user_link, attrs) do + cast(user_link, attrs, [:next_check_at, :aasm_state]) + end + def verify_changeset(user_link, user) do change(user_link) |> put_change(:verified_by_user_id, user.id) diff --git a/lib/philomena_web/controllers/profile/commission_controller.ex b/lib/philomena_web/controllers/profile/commission_controller.ex index 2a74da70..b2b93817 100644 --- a/lib/philomena_web/controllers/profile/commission_controller.ex +++ b/lib/philomena_web/controllers/profile/commission_controller.ex @@ -28,7 +28,7 @@ defmodule PhilomenaWeb.Profile.CommissionController do items = commission.items - |> Enum.sort(&(Decimal.cmp(&1.base_price, &2.base_price) != :gt)) + |> Enum.sort(&(Decimal.compare(&1.base_price, &2.base_price) != :gt)) item_descriptions = items diff --git a/lib/philomena_web/stats_updater.ex b/lib/philomena_web/stats_updater.ex index 21063bcb..11054ab4 100644 --- a/lib/philomena_web/stats_updater.ex +++ b/lib/philomena_web/stats_updater.ex @@ -16,20 +16,7 @@ defmodule PhilomenaWeb.StatsUpdater do alias Philomena.Repo import Ecto.Query - def child_spec([]) do - %{ - id: PhilomenaWeb.StatsUpdater, - start: {PhilomenaWeb.StatsUpdater, :start_link, [[]]} - } - end - - def start_link([]) do - {:ok, spawn_link(&run/0)} - end - - defp run do - :timer.sleep(:timer.seconds(300)) - + def update_stats! do {gallery_count, gallery_size, distinct_creators, images_in_galleries} = galleries() {open_reports, report_count, response_time} = moderation() {open_commissions, commission_items} = commissions() @@ -73,8 +60,6 @@ defmodule PhilomenaWeb.StatsUpdater do on_conflict: {:replace, [:body, :updated_at]}, conflict_target: :slug ) - - run() end defp aggregations do diff --git a/mix.exs b/mix.exs index a22572fb..e6fcc114 100644 --- a/mix.exs +++ b/mix.exs @@ -45,6 +45,7 @@ defmodule Philomena.MixProject do {:jason, "~> 1.1"}, {:plug_cowboy, "~> 2.3"}, {:phoenix_slime, "~> 0.13"}, + {:phoenix_pubsub_redis, "~> 3.0"}, {:ecto_network, "~> 1.3"}, {:bcrypt_elixir, "~> 2.0"}, {:pot, "~> 0.11"}, diff --git a/mix.lock b/mix.lock index c58794e2..edb758f9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,65 +1,67 @@ %{ - "artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"}, - "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, + "bamboo": {:hex, :bamboo, "1.6.0", "adfb583bef028923aae6f22deaea6667290561da1246058556ecaeb0fec5a175", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "454e67feacbc9b6e00553ce1d2fba003c861e0035600d59b09d6159985b17f9b"}, "bamboo_smtp": {:hex, :bamboo_smtp, "2.1.0", "4be58f3c51d9f7875dc169ae58a1d2f08e5b718bf3895f70d130548c0598f422", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.15.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "0aad00ef93d0e0c83a0e1ca6998fea070c8a720a990fbda13ce834136215ee49"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"}, "briefly": {:hex, :briefly, "0.3.0", "16e6b76d2070ebc9cbd025fa85cf5dbaf52368c4bd896fb482b5a6b95a540c2f", [:mix], [], "hexpm", "c6ebf8fc3dcd4950dd10c03e953fb4f553a8bcf0ff4c8c40d71542434cd7e046"}, "canada": {:hex, :canada, "1.0.2", "040e4c47609b0a67d5773ac1fbe5e99f840cef173d69b739beda7c98453e0770", [:mix], [], "hexpm", "4269f74153fe89583fe50bd4d5de57bfe01f31258a6b676d296f3681f1483c68"}, "canary": {:hex, :canary, "1.1.1", "4138d5e05db8497c477e4af73902eb9ae06e49dceaa13c2dd9f0b55525ded48b", [:mix], [{:canada, "~> 1.0.1", [hex: :canada, repo: "hexpm", optional: false]}, {:ecto, ">= 1.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f348d9848693c830a65b707bba9e4dfdd6434e8c356a8d4477e4535afb0d653b"}, - "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"}, + "castore": {:hex, :castore, "0.1.8", "1b61eaba71bb755b756ac42d4741f4122f8beddb92456a84126d6177ec0af1fc", [:mix], [], "hexpm", "23ab8305baadb057bc689adc0088309f808cb2247dc9a48b87849bb1d242bb6c"}, "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.0", "69fdb5cf92df6373e15675eb4018cf629f5d8e35e74841bb637d6596cb797bbc", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "42868c229d9a2900a1501c5d0355bfd46e24c862c322b0b4f5a6f14fe0216753"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, - "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, - "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "db_connection": {:hex, :db_connection, "2.3.0", "d56ef906956a37959bcb385704fc04035f4f43c0f560dd23e00740daf8028c49", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "dcc082b8f723de9a630451b49fdbd7a59b065c4b38176fb147aaf773574d4520"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"}, - "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"}, + "ecto": {:hex, :ecto, "3.5.3", "64aa70c6a64b8ee6a28ee186083b317b082beac8fed4d55bcc3f23199667a2f3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c481b220bb080e94fd1ab528c3b62bdfdd29188c74aef44fc2b204efa8769532"}, "ecto_network": {:hex, :ecto_network, "1.3.0", "1e77fa37c20e0f6a426d3862732f3317b0fa4c18f123d325f81752a491d7304e", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "053a5e46ef2837e8ea5ea97c82fa0f5494699209eddd764e663c85f11b2865bd"}, - "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, + "ecto_sql": {:hex, :ecto_sql, "3.5.1", "7c03f302caa3c2bbc4f5397281a5d0d8653f246d47c353e3cd46750b16ad310c", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "338150ecb2398f013f98e4a70d5413b70fed4b6383d4f7c400314d315cdf87a9"}, "elastix": {:hex, :elastix, "0.8.0", "09b7ce7f0541bdd26225e29ade6360e3cf0c0e26088367b97b798e2f40e6c038", [:mix], [{:httpoison, "~> 1.4", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}, {:retry, "~> 0.8", [hex: :retry, repo: "hexpm", optional: false]}], "hexpm", "8e7efc4bf6daf37bc99e2253a7f540c4e40464725a1ed3667a81de6ab5ed58d6"}, - "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, + "elixir_make": {:hex, :elixir_make, "0.6.1", "8faa29a5597faba999aeeb72bbb9c91694ef8068f0131192fb199f98d32994ef", [:mix], [], "hexpm", "35d33270680f8d839a4003c3e9f43afb595310a592405a00afc12de4c7f55a18"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "exq": {:hex, :exq, "0.14.0", "2ed2efa9abd94d7ed89ab95bf39395dbef1a1e35633954772158b6c57e78b402", [:mix], [{:elixir_uuid, ">= 1.2.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:redix, ">= 0.9.0", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "4f3edea5626813c90fda061b4c844b42ce4576ee0d1e7b34257242e9e89300ab"}, - "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, + "file_system": {:hex, :file_system, "0.2.9", "545b9c9d502e8bfa71a5315fac2a923bd060fd9acb797fe6595f54b0f975fd32", [:mix], [], "hexpm", "3cf87a377fe1d93043adeec4889feacf594957226b4f19d5897096d6f61345d8"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gettext": {:hex, :gettext, "0.18.1", "89e8499b051c7671fa60782faf24409b5d2306aa71feb43d79648a8bc63d0522", [:mix], [], "hexpm", "e70750c10a5f88cb8dc026fc28fa101529835026dec4a06dba3b614f2a99c7a9"}, + "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, - "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.1.0", "1fd0189edd9e3ffdbd7fcd8bc3835902b987a63ec6c4fd1aa8c2a56e2165f252", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bfd316c3789340b682d5679a8116bcf2112e332447bdc20c1d62909ee45f48d"}, + "mint": {:hex, :mint, "1.2.0", "65e9d75c60c456a5fb1b800febb88f061f56157d103d755b99fcaeaeb3e956f3", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "19cbb3a5be91b7df4a35377ba94b26199481a541add055cf5d1d4299b55125ab"}, "neotoma": {:hex, :neotoma, "1.7.3", "d8bd5404b73273989946e4f4f6d529e5c2088f5fa1ca790b4dbe81f4be408e61", [:rebar], [], "hexpm", "2da322b9b1567ffa0706a7f30f6bbbde70835ae44a1050615f4b4a3d436e0f28"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2": {:hex, :pbkdf2, "2.0.0", "11c23279fded5c0027ab3996cfae77805521d7ef4babde2bd7ec04a9086cf499", [:rebar3], [], "hexpm", "1e793ce6fdb0576613115714deae9dfc1d1537eaba74f07efb36de139774488d"}, - "phoenix": {:hex, :phoenix, "1.5.4", "0fca9ce7e960f9498d6315e41fcd0c80bfa6fbeb5fa3255b830c67fdfb7e703f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4e516d131fde87b568abd62e1b14aa07ba7d5edfd230bab4e25cc9dedbb39135"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.0", "4ac3300a22240a37ed54dfe6c0be1b5623304385d1a2c210a70f011d9e7af7ac", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "59e7e2a550d7ea082a665c0fc29485f06f55d1a51dd02f513aafdb9d16fc72c4"}, + "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.4", "940c0344b1d66a2e46eef02af3a70e0c5bb45a4db0bf47917add271b76cd3914", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "38f9308357dea4cc77f247e216da99fcb0224e05ada1469167520bed4cb8cccd"}, "phoenix_mtm": {:hex, :phoenix_mtm, "1.0.0", "36a2292f84f0712aa4fa029cb6618d9388371d70951b0269bdf372df576aab56", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "ab2126fb6079e391874fb2e089a4e5658ab3e9bc88fa0ab94f14fdc1eb8be957"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "phoenix_pubsub_redis": {:hex, :phoenix_pubsub_redis, "3.0.0", "75a3f3908210196c499a961ab7cf7202e68947ba33994f9219dc153367ac8662", [:mix], [{:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1 or ~> 1.6", [hex: :poolboy, repo: "hexpm", optional: false]}, {:redix, "~> 0.10.0", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "57b4ac98239600340ae485976ae5400f39f48ce8741105e1709f4e16d0d60e92"}, "phoenix_slime": {:hex, :phoenix_slime, "0.13.1", "a5d4d8febb87a618b02d690519f7106832c8bd0b4d1937fbba73d6e8666f2891", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:slime, "~> 1.0", [hex: :slime, repo: "hexpm", optional: false]}], "hexpm", "ff818744be2c903fb0174ba22b230c1c335238578fccf274b1d95d08f4844377"}, "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, - "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.4.0", "e936ef151751f386804c51f87f7300f5aaae6893cdad726559c3930c6c032948", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e25ddcfc06b1b76e55af79d078b03cbc86bbcb99ce4e5e0a5e4a8114ee039be6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"}, "pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"}, "qrcode": {:hex, :qrcode, "0.1.5", "551271830515c150f34568345b060c625deb0e6691db2a01b0a6de3aafc93886", [:mix], [], "hexpm", "a266b7fb7be0d3b713912055dde3575927eca920e5d604ded45cd534f6b7a447"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch_connection_drainer": {:hex, :ranch_connection_drainer, "0.1.4", "d6af517b42944a60ca65f0e4a0e76ad4fa81b241f9bad4c26a22d3a7a7767f85", [:mix], [{:ranch, "~> 1.6", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "f3621b1a486f33e24fa25f7e249a65d05eb75d8fe32fc5d009c39228343c5545"}, - "redix": {:hex, :redix, "0.11.2", "f6c696928bc0c81f9a04ea53299c353f7552ae3f4c5d1e884f3a6257098b9d66", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae74e25026c4a0d17cad3c12814b313b28ac4277f3725220ae1df2f96320c38d"}, + "redix": {:hex, :redix, "0.10.7", "758916c71fc09e223e18d6715344581d7768c430983dabf77e792ba2087729e6", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73fdf73c0472278dc040dcd1a5da91d4febe218201ae8ac0454b37e136886c34"}, "remote_ip": {:hex, :remote_ip, "0.2.1", "cd27cd8ea54ecaaf3532776ff4c5e353b3804e710302e88c01eadeaaf42e7e24", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:inet_cidr, "~> 1.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2e7ab1a461cc3cd5719f37e116a08f45c8b8493923063631b164315d6b7ee8e0"}, - "retry": {:hex, :retry, "0.14.0", "751c0f6db0b5127acf346ea6f6c363ec4588320db785c62aa51776b4d280bf07", [:mix], [], "hexpm", "5c158bccf5e4de2a13d044b9930ceb7e7499c29e3fccd7c96f131a6b83a1cff3"}, + "retry": {:hex, :retry, "0.14.1", "722d1b0cf87096b71213f5801d99fface7ca76adc83fc9dbf3e1daee952aef10", [:mix], [], "hexpm", "b3a609f286f6fe4f6b2c15f32cd4a8a60427d78d05d7b68c2dd9110981111ae0"}, "scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm", "30da36a427f2519cf75993271fb7c5aad1759682a70f90d880a85c3d743d2c57"}, - "scrivener_ecto": {:hex, :scrivener_ecto, "2.5.0", "0fb56e243ce228b8f0f2b4ba36a370d8c21570a264a6f9fac3f322e2e114519f", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "e3d2a57db3d5508b50dcb8a75d2a88b8e6360c050970a85917fc5e9c864ae7e9"}, + "scrivener_ecto": {:hex, :scrivener_ecto, "2.6.0", "81a83b6f29fdc6f85735fa0b77f23f1ddb6d27350c31ff143f67934d5a23969d", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "717f8f373bcc1b8c12eba30737dc34af63e7ea8650ff736ced4c8e3da37cb2c9"}, "secure_compare": {:hex, :secure_compare, "0.1.0", "01b3c93c8edb696e8a5b38397ed48e10958c8a5ec740606656445bcbec0aadb8", [:mix], [], "hexpm", "6391a49eb4a6182f0d7425842fc774bbed715e78b2bfb0c83b99c94e02c78b5c"}, "slime": {:hex, :slime, "1.2.1", "71e036056051f0a6fae136af34eaa1322e8e11cdd2da3a56196fd31bca34dd49", [:mix], [{:neotoma, "~> 1.7", [hex: :neotoma, repo: "hexpm", optional: false]}], "hexpm", "298568e64291fed4eb690be094f6c46400daa03b594bab34fcaa0167e139c263"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},