Compare commits
506 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a4c309da0 | |||
| 5e76c797c2 | |||
| 4fdef7b837 | |||
| 7988241e11 | |||
| 309e6d1084 | |||
| 92bb040640 | |||
| 9b42d0e25e | |||
| f286c3c1c9 | |||
| e16870a638 | |||
| f0eb6b2688 | |||
| 29a06511a9 | |||
| bfb4fc1c36 | |||
| 06c37a132f | |||
| 70b3061946 | |||
| 356d32c6fe | |||
| 6046a74a64 | |||
| 56fbea4bd3 | |||
| ee0dcdf753 | |||
| 5550d662a2 | |||
| 3dc8e5ebaf | |||
| c85b19098d | |||
| c3250257b1 | |||
| fffa74f782 | |||
| fa3de272e8 | |||
| b13c7e328a | |||
| 30d4b8986a | |||
| aadbd5adf3 | |||
| b552c293ca | |||
| a2f15b3c36 | |||
| 736fb7320e | |||
| 765b4c6424 | |||
| df472254d3 | |||
| 98167487aa | |||
| 257ef7e573 | |||
| 30288fec8d | |||
| e234b86af6 | |||
| d00803617e | |||
| af74e39fa7 | |||
| 8646d61182 | |||
| 28775f5e10 | |||
| 095bec072c | |||
| bd0f21e139 | |||
| 69414dcd7c | |||
| 28ee9cad19 | |||
| 7d1609bd93 | |||
| 1aeb1b20da | |||
| 228bb62df4 | |||
| c4e2eaa609 | |||
| 118e18190d | |||
| 31c4172540 | |||
| 93b43d37df | |||
| 3987a2ce8a | |||
| fe039b3c55 | |||
| 7537932154 | |||
| 902f6cb9c0 | |||
| 790ce5be6d | |||
| bcfc7a883c | |||
| 9fdbd180df | |||
| 4c0c658152 | |||
| 88062ab9f9 | |||
| 85856baf13 | |||
| 2d33b7df6e | |||
| 5d8a8878d5 | |||
| 8d2dab44d8 | |||
| d435e67a36 | |||
| 33c4b5188c | |||
| 8161085714 | |||
| 6493239484 | |||
| a143681af3 | |||
| 5a88e0465e | |||
| 30bb901d7c | |||
| 40418fcf6f | |||
| ad85e5be80 | |||
| aa9b18a8d6 | |||
| 8b6dd3e599 | |||
| 49d2596fc6 | |||
| 9a826bbe6f | |||
| acc9186a59 | |||
| 7d252ea45b | |||
| 140a40acaf | |||
| 56efba66f5 | |||
| 7e612cddd3 | |||
| 034e46700c | |||
| ac8f64d7a1 | |||
| b536e05fee | |||
| 6eed037186 | |||
| 5912400dee | |||
| ab209df78f | |||
| 11a352dcfd | |||
| 9ae6922bdc | |||
| ea8c3c801a | |||
| eb9508502c | |||
| b46b5dfb2a | |||
| 877dbed5e8 | |||
| 79ff381c4c | |||
| f6a672738a | |||
| e4964d6933 | |||
| 755c9af47d | |||
| 02e4b8eadc | |||
| aaa1625724 | |||
| bb5f3ec6a8 | |||
| 8804eb9a78 | |||
| 2c2700a31d | |||
| 8f9ccc6540 | |||
| ff8d68c151 | |||
| 1fb0bc29db | |||
| a1593b8942 | |||
| 20c4cb770e | |||
| db7a4ae7e9 | |||
| aabb766fad | |||
| 8dce3099d9 | |||
| fb2b43176f | |||
| 2f6034c63c | |||
| 67190fc988 | |||
| e4c0495646 | |||
| a7508b2746 | |||
| 34408b50a7 | |||
| 48acd1feca | |||
| 8d51fd8ff5 | |||
| a44ba4abcb | |||
| b338c9b3c2 | |||
| 08f7835f5d | |||
| d38fc80bad | |||
| b2fd84e0d3 | |||
| e64f488654 | |||
| 8017ed2cf6 | |||
| 65febebd40 | |||
| 2e047dff98 | |||
| 6612e42668 | |||
| 2956bbad8d | |||
| 3285a00ebc | |||
| c0c8e47344 | |||
| 3fc02df70f | |||
| c071201e37 | |||
| 690d66ce0b | |||
| ef312ef770 | |||
| c0bed53b42 | |||
| c0ee3ed375 | |||
| ce35b46fee | |||
| e12c263fc9 | |||
| 281c9be6c8 | |||
| b92debf9c9 | |||
| b6c368c39c | |||
| de92e3e472 | |||
| d9777f1439 | |||
| b4643fd27f | |||
| d9cf3f9c38 | |||
| dfd6b769b9 | |||
| 68780de6fc | |||
| 93715b542c | |||
| de1a06e3e8 | |||
| bfebf946c2 | |||
| 6094130f61 | |||
| d1bd0073cc | |||
| 789e30fdff | |||
| 04507a8a34 | |||
| 1ded3f8bc7 | |||
| 3d64162f06 | |||
| 79357a527f | |||
| 1e16e19d11 | |||
| efa8bfcf0e | |||
| 1351761f81 | |||
| 962a0460cc | |||
| 10207967c4 | |||
| 242eeaf07e | |||
| 550e7d0415 | |||
| 3f14d08095 | |||
| 206be5ba15 | |||
| 83e1f154b5 | |||
| ec47106c26 | |||
| 74b69a4c7c | |||
| 84d5a9a354 | |||
| eb86773039 | |||
| 44ca5b8591 | |||
| 95e125dfc7 | |||
| 7f6ce16d85 | |||
| aa47789f55 | |||
| 7a961babd4 | |||
| d947b0194b | |||
| cd7f01f573 | |||
| 159abf7b7d | |||
| 1b6eafa7d9 | |||
| 42928e0a9d | |||
| 70c58bd346 | |||
| 556d88c96d | |||
| e0aa9459c9 | |||
| e8fe4ed881 | |||
| 98c166c950 | |||
| 3f64e4b943 | |||
| 133a1f83e3 | |||
| c50a7d9938 | |||
| 753940e0cf | |||
| 138fd926ee | |||
| 183cb491b3 | |||
| ee070de860 | |||
| bbc087d2ad | |||
| b8c3153b75 | |||
| d7f978da49 | |||
| 0afad251e0 | |||
| 4d847c6a74 | |||
| f404ddde91 | |||
| c8a3d953cf | |||
| f851ecac78 | |||
| 1c1fcdcd3d | |||
| 594e9a6039 | |||
| ffafa810da | |||
| 1ca35e9364 | |||
| 4dc79833da | |||
| e8c4ce995c | |||
| 9daaffa23f | |||
| 63bada09e0 | |||
| 612e86cf86 | |||
| 5abf8c6798 | |||
| 992422c91f | |||
| 32ae3c4d4b | |||
| 9ee9c0579f | |||
| 39a8c8c10d | |||
| 49c2d3e34d | |||
| bcef6e2de4 | |||
| 26d1b8eb4c | |||
| cecab91298 | |||
| 3eb3ac06b6 | |||
| aefadfeed4 | |||
| 13fa08ad39 | |||
| 071e9106a8 | |||
| 405d4febd9 | |||
| 36df7b36ec | |||
| 9ba15e9649 | |||
| bd1dc3149e | |||
| 03a1cfcd5e | |||
| 45a9fd5af6 | |||
| 224584312a | |||
| 5f29af30b0 | |||
| 15268ef4f6 | |||
| 3ac5095776 | |||
| d7cf782019 | |||
| 055097fdeb | |||
| 9805654247 | |||
| 6e50ce3293 | |||
| 3a0aaddafd | |||
| 2659883ce5 | |||
| b1f6f2e7ac | |||
| cf96ab45c1 | |||
| abe568b5b3 | |||
| c0eab5ebc5 | |||
| f0a698c6fe | |||
| 806a069785 | |||
| d85908e3de | |||
| 9c6ea92122 | |||
| 229774d65d | |||
| aba2f83371 | |||
| 69dca2b619 | |||
| fc4bfb2dc1 | |||
| a904c634b8 | |||
| 938ae6828c | |||
| 79df37ac1e | |||
| f546fc3060 | |||
| 6bf89f4b32 | |||
| 5a07fd8932 | |||
| 9fa086e471 | |||
| 1658db7f10 | |||
| 780c9e3696 | |||
| 74fb430697 | |||
| d118aa8649 | |||
| 953c1742e0 | |||
| a88996fb3f | |||
| 0014aac201 | |||
| 8e6babe5d4 | |||
| cdeb2a8fc0 | |||
| a6e9f88e7a | |||
| ab3fdf509f | |||
| fb585d0086 | |||
| fa8431c5c6 | |||
| ec2b911881 | |||
| 0f2b29741e | |||
| 2f26b581f4 | |||
| ad266e45ca | |||
| f6825c1c60 | |||
| 40f958c5ef | |||
| 02e975d3f3 | |||
| 40b54b458d | |||
| ca60f3cff5 | |||
| c1ece47532 | |||
| b2e5eda89e | |||
| 1eb01b39e7 | |||
| c9e0f0b3e4 | |||
| 1e357d3ced | |||
| 4831acba39 | |||
| 658b8591b6 | |||
| 950b001675 | |||
| 0b5b41acd9 | |||
| 6b4b9e9cff | |||
| da89831116 | |||
| 3243d88c9b | |||
| 824fc310a9 | |||
| c891ab2588 | |||
| 33e9b26fe9 | |||
| 6e402f60dc | |||
| 4b56c84a8c | |||
| cee70e04db | |||
| 808635e314 | |||
| 7a1429bcc1 | |||
| 21bbaf2691 | |||
| 50c5850f0d | |||
| d22c6c535e | |||
| 6a41eb382f | |||
| aa70ebcdca | |||
| 7111b3749d | |||
| 93277f4307 | |||
| 9e3ce3f3a4 | |||
| a98e479e71 | |||
| 897d8644c5 | |||
| 2f7ad3ac73 | |||
| 20318e3a79 | |||
| cc3e65dd3c | |||
| 7b0a4a7803 | |||
| bab7734582 | |||
| 3dda84bf9a | |||
| f18ded6117 | |||
| 41d47244af | |||
| b113af8472 | |||
| 2309b27458 | |||
| 8329e9b46c | |||
| 5879404fd2 | |||
| bb0d2bf675 | |||
| dcf87bd642 | |||
| 4445cdd053 | |||
| 0b3c3a1f28 | |||
| 4b6e46d9ab | |||
| 2935aaef45 | |||
| 33fd0a617e | |||
| 2fedd8f234 | |||
| 527c5f49cb | |||
| 10d2c88e88 | |||
| fd8e95c878 | |||
| 686dd92cb9 | |||
| 28d036434f | |||
| 4b12043cea | |||
| 0f6f4c8c60 | |||
| 47b47cc32c | |||
| ec4e8710f2 | |||
| b3dae675d9 | |||
| c1c3624593 | |||
| b21f12015e | |||
| ca87894ab2 | |||
| 42dcc52ed1 | |||
| 45bfbb8a2f | |||
| d6ad9a471b | |||
| b585dab6b4 | |||
| d45e1b3bed | |||
| 03fd403e38 | |||
| 2dcabc840c | |||
| 87d58987a6 | |||
| 5f9d6fd81b | |||
| c2c9db737b | |||
| 4565dd406b | |||
| 22bbe1ac50 | |||
| 72fbbe311a | |||
| 423e06cbe8 | |||
| 1f3a9dabc2 | |||
| 937899194d | |||
| cf1939f063 | |||
| edee11931d | |||
| 612f9cdbc8 | |||
| fcd18f0b9c | |||
| 78f6bbe93d | |||
| 27d170cbec | |||
| 49119eff00 | |||
| 34dd358eb0 | |||
| 93596c1218 | |||
| 9fbcfc213b | |||
| 01f678c3b1 | |||
| 2f6895e118 | |||
| b7c229f30f | |||
| b88d46ee7f | |||
| b58a46455b | |||
| ce6fa4c0fc | |||
| 9965abccfb | |||
| 8bc06a5d65 | |||
| 7f1c17e396 | |||
| c5594fdae0 | |||
| b8b16c3bdf | |||
| fc766de02d | |||
| 79810413bd | |||
| dc21991599 | |||
| cc41065d9b | |||
| e877800779 | |||
| d5efd776b7 | |||
| 164bcc2f4a | |||
| 21ffd79c63 | |||
| 638643cda7 | |||
| 6703e03ce6 | |||
| 56c3f1193f | |||
| d189df95db | |||
| 0bcfc81b1f | |||
| 571d7bf17c | |||
| 93fd98f8d4 | |||
| 238a3ee9be | |||
| 897cd7cb34 | |||
| 409d05eec8 | |||
| 4b2a25eed4 | |||
| 1fc95a83b6 | |||
| cc888e2a55 | |||
| d9f91fd919 | |||
| dd3f496d06 | |||
| 09be3d2bac | |||
| 1fd4c4ef13 | |||
| 20cd023c07 | |||
| 1655ca235d | |||
| 77dc6aaf27 | |||
| b4cdef6875 | |||
| 36868e648c | |||
| c119a2c011 | |||
| e0557d8961 | |||
| 36d3b1e9fb | |||
| 012eb61c52 | |||
| c4a56105ca | |||
| 1005fb973e | |||
| 75ac58abdf | |||
| e46b438b06 | |||
| 264f4db567 | |||
| 0c56b4d580 | |||
| 32439ca2f0 | |||
| fa49bcaf8b | |||
| 89e8e94bf1 | |||
| 5b8eb4735c | |||
| 1c9f000576 | |||
| de53d8762f | |||
| daa56efe27 | |||
| f1db010a68 | |||
| dbb5906767 | |||
| 18e414ec45 | |||
| fc00a1b3a8 | |||
| 556b74f4fc | |||
| 6e2e2d1665 | |||
| 9242350f0e | |||
| b0c14aa8f9 | |||
| 4b2516532e | |||
| 1aec8d727c | |||
| e60e0ba3c4 | |||
| 5877048749 | |||
| c8c1f05328 | |||
| 96f3e888cf | |||
| 14888f1cb8 | |||
| 49171c5e8c | |||
| 51fb9495ad | |||
| 89c1b3b4d8 | |||
| 1d80c12e18 | |||
| d092fc8436 | |||
| 8a812cd8ec | |||
| ae13b4e420 | |||
| 548c4d4efe | |||
| f6ed2feab4 | |||
| b730aa520d | |||
| 3d16c3e4e6 | |||
| 0d935a863f | |||
| 053b1d0f8d | |||
| b247f3f089 | |||
| 20ec435d6d | |||
| 53cd369b05 | |||
| ec3c604a83 | |||
| c3f871b49b | |||
| e5b2eab727 | |||
| bbf93430b7 | |||
| 3cb04b2c56 | |||
| e3c28a6c96 | |||
| c2571f9f47 | |||
| 4855fbd13f | |||
| 9569666259 | |||
| 42edd357bc | |||
| b33706006a | |||
| 0de80c2ff0 | |||
| 5d65a85bdb | |||
| a352aff93b | |||
| 713d575bad | |||
| 421bb467d0 | |||
| dde2ca06c3 | |||
| 091308787b | |||
| 04f9c456d5 | |||
| 6b7ffe0e26 | |||
| f29940d784 | |||
| 0413daf76b | |||
| 5043749b9f | |||
| 793f475edd | |||
| 03cfbfe4ac | |||
| e8d0360e64 | |||
| 05fd83bd6f | |||
| 864dd9f219 | |||
| 9a9a271b1f | |||
| 89d17107af | |||
| b6ddaae82a | |||
| 0bece2df7d | |||
| 4464802947 | |||
| 7c44238bae | |||
| b45c1cf327 | |||
| f1d7385294 | |||
| 77f5b911b5 | |||
| f8ca825524 | |||
| 6377f3a8b3 | |||
| 533d5dfc98 | |||
| 1ed3816d6f | |||
| 4ff52bd730 | |||
| 02a4c9db53 | |||
| 433de17562 | |||
| 23e4183bec | |||
| 926ddf6d09 |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "Miniflux",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
"remoteUser": "vscode",
|
||||
"forwardPorts": [
|
||||
8080
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"golang.go"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
image: mcr.microsoft.com/devcontainers/go
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
command: sleep infinity
|
||||
network_mode: service:db
|
||||
environment:
|
||||
- CREATE_ADMIN=1
|
||||
- ADMIN_USERNAME=admin
|
||||
- ADMIN_PASSWORD=test123
|
||||
db:
|
||||
image: postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
hostname: postgres
|
||||
environment:
|
||||
POSTGRES_DB: miniflux2
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
postgres-data: null
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] I have read this document: https://miniflux.app/opinionated.html#feature-request
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feed Problems
|
||||
about: Problems with a feed or a website
|
||||
title: ''
|
||||
labels: feed problems, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Improvement
|
||||
about: Do you have an idea to improve an existing feature?
|
||||
title: ''
|
||||
labels: improvements
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
paths-ignore:
|
||||
- ./http/client/testdata
|
||||
@@ -2,6 +2,15 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/packaging/docker/alpine"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
@@ -10,6 +19,15 @@ updates:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/packaging/docker/distroless"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Do you follow the guidelines?
|
||||
|
||||
- [ ] I have tested my changes
|
||||
- [ ] I read this document: https://miniflux.app/faq.html#pull-request
|
||||
+10
-50
@@ -1,40 +1,12 @@
|
||||
name: CI Workflow
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
linters:
|
||||
name: Linter Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install linters
|
||||
run: |
|
||||
cd /tmp && go get -u golang.org/x/lint/golint
|
||||
sudo npm install -g jshint
|
||||
env:
|
||||
GO111MODULE: off
|
||||
- name: Run golint
|
||||
run: |
|
||||
export PATH=/home/runner/go/bin:$PATH
|
||||
make lint
|
||||
- name: Run jshint
|
||||
run: jshint ui/static/js/*.js
|
||||
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -42,20 +14,14 @@ jobs:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
go-version: [1.14, 1.15]
|
||||
go-version: ["1.19", "1.20"]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: actions/checkout@v3
|
||||
- name: Run unit tests
|
||||
run: make test
|
||||
|
||||
@@ -74,19 +40,13 @@ jobs:
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.15
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
go-version: "1.20"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Postgres client
|
||||
run: sudo apt-get install -y postgresql-client
|
||||
run: sudo apt update && sudo apt install -y postgresql-client
|
||||
- name: Run integration tests
|
||||
run: make integration-test
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: "CodeQL"
|
||||
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '45 22 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/config.yml
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -5,53 +5,113 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
docker-images:
|
||||
test-docker-images:
|
||||
if: github.event.pull_request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Build Alpine image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/alpine/Dockerfile
|
||||
push: false
|
||||
tags: ${{ github.repository_owner }}/miniflux:alpine-dev
|
||||
- name: Test Alpine Docker image
|
||||
run: docker run --rm ${{ github.repository_owner }}/miniflux:alpine-dev miniflux -i
|
||||
- name: Build Distroless image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/distroless/Dockerfile
|
||||
push: false
|
||||
tags: ${{ github.repository_owner }}/miniflux:distroless-dev
|
||||
- name: Test Distroless Docker image
|
||||
run: docker run --rm ${{ github.repository_owner }}/miniflux:distroless-dev miniflux -i
|
||||
|
||||
publish-docker-images:
|
||||
if: ${{ ! github.event.pull_request }}
|
||||
permissions:
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate Docker tag
|
||||
id: docker_tag
|
||||
- name: Generate Alpine Docker tag
|
||||
id: docker_alpine_tag
|
||||
run: |
|
||||
DOCKER_IMAGE=miniflux/miniflux
|
||||
DOCKER_IMAGE=${{ github.repository_owner }}/miniflux
|
||||
DOCKER_VERSION=dev
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
DOCKER_VERSION=nightly
|
||||
TAGS="${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
|
||||
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
DOCKER_VERSION=${GITHUB_REF#refs/tags/}
|
||||
TAGS="${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},${DOCKER_IMAGE}:latest,ghcr.io/${DOCKER_IMAGE}:latest"
|
||||
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION},docker.io/${DOCKER_IMAGE}:latest,ghcr.io/${DOCKER_IMAGE}:latest,quay.io/${DOCKER_IMAGE}:latest"
|
||||
fi
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
|
||||
- name: Generate Distroless Docker tag
|
||||
id: docker_distroless_tag
|
||||
run: |
|
||||
DOCKER_IMAGE=${{ github.repository_owner }}/miniflux
|
||||
DOCKER_VERSION=dev-distroless
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
DOCKER_VERSION=nightly-distroless
|
||||
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
DOCKER_VERSION=${GITHUB_REF#refs/tags/}-distroless
|
||||
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION},docker.io/${DOCKER_IMAGE}:latest-distroless,ghcr.io/${DOCKER_IMAGE}:latest-distroless,quay.io/${DOCKER_IMAGE}:latest-distroless"
|
||||
fi
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
- name: Login to Quay Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_TOKEN }}
|
||||
|
||||
- name: Build and Push Alpine images
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/Dockerfile
|
||||
file: ./packaging/docker/alpine/Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.docker_tag.outputs.tags }}
|
||||
tags: ${{ steps.docker_alpine_tag.outputs.tags }}
|
||||
|
||||
- name: Build and Push Distroless images
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/distroless/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.docker_distroless_tag.outputs.tags }}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Linters
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
jshint:
|
||||
name: Javascript Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install jshint
|
||||
run: |
|
||||
sudo npm install -g jshint@2.13.3
|
||||
- name: Run jshint
|
||||
run: jshint ui/static/js/*.js
|
||||
|
||||
golangci:
|
||||
name: Golang Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20"
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
args: --timeout 10m --skip-dirs tests --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Debian and RPM Package Builders
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
jobs:
|
||||
debian-package-builder:
|
||||
name: Build Debian Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
id: buildx
|
||||
with:
|
||||
install: true
|
||||
- name: Available Docker Platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Build Debian Packages
|
||||
run: make debian-packages
|
||||
- name: List generated files
|
||||
run: ls -l *.deb
|
||||
- name: Upload packages to repository
|
||||
env:
|
||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||
run: for f in *.deb; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
|
||||
rpm-package-builder:
|
||||
name: Build RPM Package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build RPM Package
|
||||
run: make rpm
|
||||
- name: List generated files
|
||||
run: ls -l *.rpm
|
||||
- name: Upload package to repository
|
||||
env:
|
||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||
run: for f in *.rpm; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
|
||||
@@ -2,3 +2,5 @@ miniflux-*
|
||||
miniflux
|
||||
*.rpm
|
||||
*.deb
|
||||
.idea
|
||||
.vscode
|
||||
@@ -1,3 +1,461 @@
|
||||
Version 2.0.46 (July 21, 2023)
|
||||
------------------------------
|
||||
|
||||
* Add scraper and rewrite rules for Webtoons
|
||||
* Fix regression in integration page and simplify SQL query
|
||||
* Wallabag integration: add more information in log messages
|
||||
* Add support for custom Youtube embed URL
|
||||
* Fix accessibility issues in modal component
|
||||
* Fix modal aria role
|
||||
* Trap focusing with tab / shift+tab inside the modal
|
||||
* Restore keyboard focus when closing modal
|
||||
* Automatically move keyboard focus to first focusable element unless specified otherwise
|
||||
* Keyboard shortcut help modal: move keyboard focus to modal title
|
||||
* Keyboard shortcut help modal: change close control from link to button
|
||||
* Add Notion integration
|
||||
* Update `golang.org/x/*` dependencies and `go-oidc` to v3.6.0
|
||||
* Improve responsive design
|
||||
* Add user setting for marking entry as read on view
|
||||
* Improve Russian translation
|
||||
* Add the possibility to run cleanup tasks from the command line
|
||||
* Add the possibility to run Miniflux as a cronjob
|
||||
* Use `go-httpbin` to run tests locally and avoid remote calls to `httpbin.org`
|
||||
* Display tags when viewing entries
|
||||
* Update categories API endpoint to return `total_unread` and `feed_count`
|
||||
* Improve date parser to handle various broken date formats
|
||||
* Avoid `pq: time zone displacement out of range` errors
|
||||
* Improve entry existance check to make better use of index
|
||||
* Add unique index `enclosures_user_entry_url_idx`
|
||||
* Add mark as unread for Linkding integration
|
||||
* Add sub-folder support for Wallabag integration
|
||||
* Use RockyLinux to build RPM package
|
||||
* Disable CGO when building RPM package
|
||||
* Disable CGO when building Docker images
|
||||
|
||||
Version 2.0.45 (June 21, 2023)
|
||||
------------------------------
|
||||
|
||||
* Add media player to listen to audio and video podcasts with the possiblity to resume to last playback position
|
||||
* Add default tag names for Linkding integration
|
||||
* Mark only globally visible entries when marking all entries from UI
|
||||
* Use image included in feed as feed icon when available
|
||||
* Order history by `changed_at` and `published_at`
|
||||
* Remove title attribute from entry title links
|
||||
* Fix reading time that is not aligned correctly with the latest version of Safari
|
||||
* Use glyphs of the same size on keyboard shortcuts page
|
||||
* Add maskable versions of the PWA icon
|
||||
* Replace copyright header with SPDX identifier
|
||||
* Remove the "í" letter from the Portuguese "lido" word
|
||||
* Increase golangci-lint timeout value
|
||||
* Bump `github.com/tdewolff/minify/v2`, `github.com/prometheus/client_golang`, `golang.org/x/*` dependencies
|
||||
|
||||
Version 2.0.44 (May 6, 2023)
|
||||
----------------------------
|
||||
|
||||
* Add link to the URL rewrite rules documentation
|
||||
* Update scraping rules for `ilpost.it`
|
||||
* Update rewrite rules for `theverge.com`
|
||||
* Add a rewrite rule to remove clickbait titles
|
||||
* Make sure `PROXY_IMAGES` option is backward compatible with `PROXY_OPTION` and `PROXY_MEDIA_TYPES`
|
||||
* Add new rule to remove tables
|
||||
* Add support for searching well-known URLs in subdirectory
|
||||
* Add CSS `word-wrap` rule to break very long entry title into multiple lines
|
||||
* Add swipe as option for gesture navigation between entries. There are now 3 possible choices: `none`, `double-tap`, and `swipe`.
|
||||
* Prefer typographic punctuation in English translation
|
||||
* Process older entries first:
|
||||
- Feed entries are usually ordered from most to least recent.
|
||||
- Processing older entries first ensures that their creation timestamp
|
||||
is lower than that of newer entries.
|
||||
- This is useful when we order by creation, because then we get a
|
||||
consistent timeline.
|
||||
* Fix Grafana dashboard
|
||||
* Push Docker images to `Quay.io` (RedHat)
|
||||
* Bump `golang.org/x/*`, `github.com/lib/pq`, `mvdan.cc/xurls/v2` and `github.com/prometheus/client_golang` dependencies
|
||||
|
||||
Version 2.0.43 (March 16, 2023)
|
||||
-------------------------------
|
||||
|
||||
* Avoid XSS when opening a broken image due to unescaped ServerError in proxy handler (CVE-2023-27592)
|
||||
|
||||
Creating an RSS feed item with the inline description containing an `<img>` tag
|
||||
with a `srcset` attribute pointing to an invalid URL like
|
||||
`http:a<script>alert(1)</script>`, we can coerce the proxy handler into an error
|
||||
condition where the invalid URL is returned unescaped and in full.
|
||||
|
||||
This results in JavaScript execution on the Miniflux instance as soon as the
|
||||
user is convinced to open the broken image.
|
||||
|
||||
* Use `r.RemoteAddr` to check `/metrics` endpoint network access (CVE-2023-27591)
|
||||
|
||||
HTTP headers like `X-Forwarded-For` or `X-Real-Ip` can be easily spoofed. As
|
||||
such, it cannot be used to test if the client IP is allowed.
|
||||
|
||||
The recommendation is to use HTTP Basic authentication to protect the
|
||||
metrics endpoint, or run Miniflux behind a trusted reverse-proxy.
|
||||
|
||||
* Add HTTP Basic authentication for `/metrics` endpoint
|
||||
* Add proxy support for several media types
|
||||
* Parse feed categories from RSS, Atom and JSON feeds
|
||||
* Ignore empty link when discovering feeds
|
||||
* Disable CGO explicitly to make sure the binary is statically linked
|
||||
* Add CSS classes to differentiate between category/feed/entry view and icons
|
||||
* Add rewrite and scraper rules for `blog.cloudflare.com`
|
||||
* Add `color-scheme` to themes
|
||||
* Add new keyboard shortcut to toggle open/close entry attachments section
|
||||
* Sanitizer: allow `id` attribute in `<sup>` element
|
||||
* Add Indonesian Language
|
||||
* Update translations
|
||||
* Update Docker Compose examples:
|
||||
- Run the application in one command
|
||||
- Bring back the health check condition to `depends_on`
|
||||
- Remove deprecated `version` element
|
||||
* Update scraping rules for `ilpost.it`
|
||||
* Bump `github.com/PuerkitoBio/goquery` from `1.8.0` to `1.8.1`
|
||||
* Bump `github.com/tdewolff/minify/v2` from `2.12.4` to `2.12.5`
|
||||
* Bump `github.com/yuin/goldmark` from `1.5.3` to `1.5.4`
|
||||
* Bump `golang.org/x/*` dependencies
|
||||
|
||||
Version 2.0.42 (January 29, 2023)
|
||||
---------------------------------
|
||||
|
||||
* Fix header items wrapping
|
||||
* Add option to enable or disable double tap
|
||||
* Improve PWA display mode label in settings page
|
||||
* Bump `golang.org/x/*` dependencies
|
||||
* Update translations
|
||||
* Add scraping rule for `ilpost.it`
|
||||
* Update reading time HTML element after fetching the original web page
|
||||
* Add category feeds refresh feature
|
||||
|
||||
Version 2.0.41 (December 10, 2022)
|
||||
----------------------------------
|
||||
|
||||
* Reverted PR #1290 (follow the only link) because it leads to several panics/segfaults that prevent feed updates
|
||||
* Disable double-tap mobile gesture if swipe gesture is disabled
|
||||
* Skip integrations if there are no entries to push
|
||||
* Enable TLS-ALPN-01 challenge for ACME
|
||||
- This type of challenge works purely at the TLS layer and is compatible
|
||||
with SNI proxies. The existing HTTP-01 challenge support has been left
|
||||
as-is.
|
||||
* Preconfigure Miniflux for GitHub Codespaces
|
||||
* Updated `golang.org/x/net/*` dependencies
|
||||
|
||||
Version 2.0.40 (November 13, 2022)
|
||||
----------------------------------
|
||||
|
||||
* Update dependencies
|
||||
* Pin Postgres image version in Docker Compose examples to avoid unexpected upgrades
|
||||
* Make English and Spanish translation more consistent:
|
||||
- Use "Feed" everywhere instead of "Subscription"
|
||||
- Use "Entry" instead of "Article"
|
||||
* Allow Content-Type and Accept headers in CORS policy
|
||||
* Use dirs file for Debian package
|
||||
* Use custom home page in PWA manifest
|
||||
* Fix scraper rule that could be incorrect when there is a redirect
|
||||
* Improve web scraper to fetch the only link present as workaround to some landing pages
|
||||
* Add Matrix bot integration
|
||||
* Proxify images in API responses
|
||||
* Add new options in user preferences to configure sorting of entries in the category page
|
||||
* Remove dependency on `github.com/mitchellh/go-server-timing`
|
||||
* Add support for the `continuation` parameter and result for Google Reader API ID calls
|
||||
* Use automatic variable for build target file names
|
||||
* Add rewrite rule for `recalbox.com`
|
||||
* Improve Dutch translation
|
||||
|
||||
Version 2.0.39 (October 16, 2022)
|
||||
---------------------------------
|
||||
|
||||
* Add support for date filtering in Google Reader API item ID calls
|
||||
* Handle RSS entries with only a GUID permalink
|
||||
* Go API Client: Accept endpoint URLs ending with `/v1/`
|
||||
* CORS API headers: Allow `Basic` authorization header
|
||||
* Log feed URL when submitting a subscription that returns an error
|
||||
* Update `make run` command to execute migrations automatically
|
||||
* Add option to send only the URL to Wallabag
|
||||
* Do not convert anchors to absolute links
|
||||
* Add config option to use a custom image proxy URL
|
||||
* Allow zoom on mobile devices
|
||||
* Add scraping rules for `theverge.com`, `royalroad.com`, `swordscomic.com`, and `smbc-comics.com`
|
||||
* Add Ukrainian translation
|
||||
* Update `golang.org/x/*` dependencies
|
||||
* Bump `github.com/tdewolff/minify/v2` from `2.12.0` to `2.12.4`
|
||||
* Bump `github.com/yuin/goldmark` from `1.4.13` to `1.5.2`
|
||||
* Bump `github.com/lib/pq` from `1.10.6` to `1.10.7`
|
||||
|
||||
Version 2.0.38 (August 13, 2022)
|
||||
--------------------------------
|
||||
|
||||
* Rename default branch from master to main
|
||||
* Update GitHub Actions
|
||||
* Bump `github.com/prometheus/client_golang` from `1.12.2` to `1.13.0`
|
||||
* Fix some linter issues
|
||||
* Handle Atom links with a text/html type defined
|
||||
* Add `parse_markdown` rewrite function
|
||||
* Build RPM and Debian packages automatically using GitHub Actions
|
||||
* Add `explosm.net` scraper rule
|
||||
* Make default home page configurable
|
||||
* Add title attribute to entry links because text could be truncated
|
||||
* Highlight categories with unread entries
|
||||
* Allow option to order by title and author in API entry endpoint
|
||||
* Update Russian translation
|
||||
* Make reading speed user-configurable
|
||||
* Added translation for Hindi language used in India
|
||||
* Add rewrite rules for article URL before fetching content
|
||||
* Bump `github.com/tdewolff/minify/v2` from `2.11.7` to `2.12.0`
|
||||
* Support other repo owners in GitHub Docker Action
|
||||
* Proxify empty URL should not crash
|
||||
* Avoid stretched image if specified width is larger than Miniflux's layout
|
||||
* Add support for OPML files with several nested outlines
|
||||
* sanitizer: handle image URLs in `srcset` attribute with comma
|
||||
* Allow `width` and `height` attributes for `img` tags
|
||||
* Document that `-config-dump` command line argument shows sensitive info
|
||||
* Add System-V init service in contrib folder
|
||||
* Fix syntax error in `RequestBuilder.getCsrfToken()` method
|
||||
|
||||
Version 2.0.37 (May 27, 2022)
|
||||
-----------------------------
|
||||
|
||||
* Add rewrite rule to decode base64 content
|
||||
* Add Linkding integration
|
||||
* Add comment button to Telegram message
|
||||
* Add API endpoint to fetch unread and read counters
|
||||
* Fixes logic bug in Google Reader API sanity check
|
||||
* Reduce number of CORS preflight check to save network brandwidth
|
||||
* Add Espial integration
|
||||
* Allow API search for entries which are not starred
|
||||
* Try to use outermost element text when title is empty
|
||||
* Make swipe gestures feel more natural
|
||||
- Removes opacity transition when swiping an article read/unread
|
||||
- Adds "resistance" to the swiped entry when the 75px threshold is
|
||||
reached
|
||||
- Fixes an issue in which a swiped article couldn't be moved <15px
|
||||
* Add support for feed streams to Google Reader API IDs API
|
||||
* Fix invalid parsing of icon data URL
|
||||
* Add Traditional Chinese translation
|
||||
* Add distroless Docker image variant
|
||||
* Add Go 1.18 to GitHub Action
|
||||
* Bump `github.com/tdewolff/minify/v2` from `2.10.0` to `2.11`
|
||||
* Bump `github.com/prometheus/client_golang` from `1.12.1` to `1.12.2`
|
||||
* Bump `github.com/lib/pq` from `1.10.4` to `1.10.6`
|
||||
|
||||
Version 2.0.36 (March 8, 2022)
|
||||
------------------------------
|
||||
|
||||
* Gray out pagination buttons when they are not applicable
|
||||
* Use truncated entry description as title if unavailable
|
||||
* Do not fallback to InnerXML if XHTML title is empty
|
||||
* Add `+` keyboard shortcut for new subscription page
|
||||
* Add `(+)` action next to Feeds to quickly add new feeds
|
||||
* Fix unstar not working via Google Reader API
|
||||
* Remove circles in front of page header list items
|
||||
* Fix CSS hover style for links styled as buttons
|
||||
* Avoid showing `undefined` when clicking on read/unread
|
||||
* Add new keyboard shortcut `M` to toggle read/unread, and go to previous item
|
||||
* Add several icons to menus according to their roles
|
||||
* Add missing event argument to `onClick()` function call
|
||||
* Add links to scraper/rewrite/filtering docs when editing feeds
|
||||
* Add a rewrite rule for Castopod episodes
|
||||
* Fix regression: reset touch-item if not in `/unread` page
|
||||
* Add API endpoint to fetch original article
|
||||
* Show the category first in feed settings
|
||||
* Add pagination on top of all entries
|
||||
* Display Go version in "About" page
|
||||
* Bump `mvdan.cc/xurls/v2` from 2.3.0 to 2.4.0
|
||||
* Bump `github.com/prometheus/client_golang` from 1.11.0 to 1.12.1
|
||||
* Bump `github.com/tdewolff/minify/v2` from 2.9.28 to 2.10.0
|
||||
|
||||
Version 2.0.35 (January 21, 2022)
|
||||
---------------------------------
|
||||
|
||||
* Set `read-all` permission to `GITHUB_TOKEN` for GitHub Actions
|
||||
* Pin `jshint` version in linter job
|
||||
* Fix incorrect conversion between integer types
|
||||
* Add new GitHub Actions workflows: CodeQL and Scorecards analysis
|
||||
* Handle Atom feeds with space around CDATA
|
||||
* Bump `github.com/tdewolff/minify/v2` from 2.9.22 to 2.9.28
|
||||
* Add Documentation directive to Systemd service
|
||||
* Do not reset `touch-item` if successfully swiped
|
||||
* Add support for multiple authors in Atom feeds
|
||||
* Omit `User-Agent` header in image proxy to avoid being blocked
|
||||
* Use custom feed user agent to fetch website icon
|
||||
* Make default Invidious instance configurable
|
||||
* Add new rewrite rule `add_youtube_video_from_id` to add Youtube videos in Quanta articles
|
||||
* Add scrape and rewrite rules for `quantamagazine.org`
|
||||
* Expose entry unshare link in the entry and list views
|
||||
* Add Google Reader API implementation (experimental)
|
||||
* Add `Content-Security-Policy` header to feed icon and image proxy endpoints
|
||||
- SVG images could contain Javascript. This CSP blocks inline script.
|
||||
- Feed icons are served using `<img>` tag and Javascript is not interpreted.
|
||||
* Add Finnish translation
|
||||
* Add scraper rule for `ikiwiki.iki.fi`
|
||||
* Remove `SystemCallFilter` from `miniflux.service`
|
||||
* Fix minor typo in French translation
|
||||
|
||||
Version 2.0.34 (December 16, 2021)
|
||||
----------------------------------
|
||||
|
||||
* Add rewrite rule for comics website http://monkeyuser.com
|
||||
* Add `<head>` tag to OPML export
|
||||
* Tighten Systemd sandboxing and update comments in `miniflux.service`
|
||||
* Add `RuntimeDirectory` to Systemd service
|
||||
* Order disabled feeds at the end of the list
|
||||
* Add support for theme color based on preferred color scheme of OS
|
||||
* Bump `github.com/lib/pq` from 1.10.3 to 1.10.4
|
||||
* Bump `github.com/PuerkitoBio/goquery` from 1.7.1 to 1.8.0
|
||||
* Fix typos in `model/icon.go`
|
||||
* Add `data-srcset` support to `add_dynamic_image rewrite` rewrite rule
|
||||
* Fix Docker Compose example files compatibility to v3
|
||||
* Added the `role="article"` to `<article>` elements for better accessibility with screen readers
|
||||
* Redact secrets shown on the about page
|
||||
* Handle `srcset` images with no space after comma
|
||||
* Hide the logout link when using auth proxy
|
||||
* Fix wrong CSS variable
|
||||
* Change `-config-dump` command to use `KEY=VALUE` format
|
||||
|
||||
Version 2.0.33 (September 25, 2021)
|
||||
-----------------------------------
|
||||
|
||||
* Build RPM and Debian package with PIE mode enabled
|
||||
* Add CSS rule to hide `<template>` tag in old browsers
|
||||
* Bump `github.com/tdewolff/minify/v2 from 2.9.21 to 2.9.22`
|
||||
* Bump `github.com/lib/pq from 1.10.2 to 1.10.3`
|
||||
* Remove `RequestURI()` hack
|
||||
* Improve `zh_CN` translation
|
||||
* Add ability to change entry sort order in the UI
|
||||
* Add minor improvements in integration package
|
||||
* Add Telegram integration
|
||||
* Add rewrite rule to remove DOM elements
|
||||
* Add proxy argument to `scraper.Fetch()`
|
||||
* Add mime type `application/feed+json` to discover JSON Feed v1.1
|
||||
* Update scraper rule for `theregister.com`
|
||||
* Add Go 1.17 to GitHub Actions
|
||||
* Display option to hide feed only when category is not already hidden
|
||||
* Add option to hide feeds from the global Unread list
|
||||
|
||||
Version 2.0.32 (August 14, 2021)
|
||||
--------------------------------
|
||||
|
||||
* Bump `github.com/tdewolff/minify/v2` from 2.9.17 to 2.9.21
|
||||
* Bump `mvdan.cc/xurls/v2` from 2.2.0 to 2.3.0
|
||||
* Bump `github.com/PuerkitoBio/goquery` from 1.6.1 to 1.7.1
|
||||
* Bump `github.com/prometheus/client_golang` from 1.10.0 to 1.11.0
|
||||
* Add `/rss/` to the list of well known URLs during feed discovery
|
||||
* Use `authors` entry for JSON 1.1 feeds
|
||||
* Added Greek translation
|
||||
* Added the ability to mark an entire category as read in the web ui
|
||||
* Added "in" in "logged in" for en_US `tooltip.logged_user`
|
||||
* Added option to hide categories from the global unread list
|
||||
* Show "saving" labels for entry status button
|
||||
* Golang client: Try to parse response body on `InternalServerError` errors
|
||||
* contrib: Add support for a $MINIFLUX_IMAGE env var in docker-compose
|
||||
* contrib: Bump docker-compose version to 3.4
|
||||
|
||||
Version 2.0.31 (June 6, 2021)
|
||||
-----------------------------
|
||||
|
||||
* Expose comments_url entry field in Golang API client
|
||||
* Use unique file names for cache busting instead of query string
|
||||
* Highlight and sort feeds with unread entries in feeds list
|
||||
* Mark items as read on click/middle click of external links
|
||||
* Fix: Firefox on Windows does not show the active link as bold
|
||||
* Avoid extra HTTP request for fetching custom stylesheet
|
||||
* Remove invalid CSRF HTML meta tag
|
||||
* Add lang attribute to root HTML tag
|
||||
* Use runes instead of bytes to truncate JSON feed titles (avoid breaking Unicode strings)
|
||||
* Expose changed_at time through the API
|
||||
* Add new config option CLEANUP_ARCHIVE_BATCH_SIZE
|
||||
* Add new option DATABASE_CONNECTION_LIFETIME
|
||||
* Add database stats to Prometheus exporter
|
||||
* Add Systemd watchdog
|
||||
* Avoid custom stylesheet to be cached by third-party CDN
|
||||
* Update a shared entry label translation in zh_CN
|
||||
* Bump github.com/tdewolff/minify/v2 from 2.9.16 to 2.9.17
|
||||
* Bump github.com/lib/pq from 1.10.1 to 1.10.2
|
||||
|
||||
Version 2.0.30 (May 7, 2021)
|
||||
----------------------------
|
||||
|
||||
* Security fix: any user can delete any feed (Regression introduced in commit 51fb949)
|
||||
* Fix password reset via CLI
|
||||
* Increase default batch size value
|
||||
* Handle RSS feed title with encoded Unicode entities
|
||||
* Show number of unread per category in category list instead of number of feeds
|
||||
* Bump github.com/lib/pq from 1.10.0 to 1.10.1
|
||||
* Filtering doesn't work when selecting from multiple found feeds
|
||||
* Bump github.com/tdewolff/minify/v2 from 2.9.15 to 2.9.16
|
||||
* Use an appropriate color for visited links on dark theme
|
||||
* Fix typo in reader/json/doc.go
|
||||
* Create SECURITY.md
|
||||
* Setup golangci-lint Github Action
|
||||
* Add per feed cookies option
|
||||
* Bump github.com/prometheus/client_golang from 1.9.0 to 1.10.0
|
||||
* Bump github.com/tdewolff/minify/v2 from 2.9.13 to 2.9.15
|
||||
|
||||
Version 2.0.29 (Mar 21, 2021)
|
||||
-----------------------------
|
||||
|
||||
* Miniflux requires at least Go 1.16 now
|
||||
* Improved support of Atom text constructs
|
||||
- Improve handling of CDATA in text elements
|
||||
- Omit XHTML root element because it should not be part of the content
|
||||
- Fix incorrect parsing of HTML elements
|
||||
* Handle RDF feed with HTML encoded entry title
|
||||
* Add Turkish language
|
||||
* Improve deletion of feeds with lots of entries
|
||||
* Add support of Systemd readiness notification using the sd_notify protocol
|
||||
* Remove feed_icons service worker cache because it's causing more problems than it solves (and HTTP cache seems faster)
|
||||
* Add basic PWA offline page
|
||||
- Add basic offline mode when using the service worker
|
||||
- Starting in Chrome 93, offline mode is going to be a requirement to install the PWA
|
||||
* Replace icon for "Add to home screen" button
|
||||
* Use SVG icons for "toast" notifications
|
||||
* Use SVG sprite for icons instead of inline elements
|
||||
* Reset scroll position on mark page as read
|
||||
* Add link to mark all feed entries as read
|
||||
* Make web app display mode configurable (The change is visible after reinstalling the web app)
|
||||
* Handle RSS feeds with CDATA in author item element
|
||||
* Add read time on the article page
|
||||
* Avoid showing a broken image when there is no feed icon
|
||||
* Add option to allow self-signed or invalid certificates
|
||||
* Add new config option to scrape YouTube's website to get video duration as read time (disabled by default)
|
||||
* Send full article content to Wallabag
|
||||
* Add more extensive health check support
|
||||
- Improve endpoint to test database connection
|
||||
- Add new cli argument: -healthcheck
|
||||
- Update Docker Compose examples
|
||||
* Update iframe "allow list" to support Bilibili videos
|
||||
* Remove completely generated files and use embed package to bundle JS/CSS/Images/Translations files into the final binary
|
||||
* Remove deprecated io/ioutil package
|
||||
* Show Postgres version in "About" page
|
||||
|
||||
Version 2.0.28 (Feb 15, 2021)
|
||||
-----------------------------
|
||||
|
||||
* Add HTTP header "Referrer-Policy: no-referrer"
|
||||
* Handle entry title with double encoded entities
|
||||
* Add Open Containers annotations to Docker image
|
||||
* Remove iframe inner HTML contents (iframe element never has fallback content)
|
||||
* Update date parser to fix another time zone issue
|
||||
* Update German translation for blocklist and keeplist
|
||||
* Validate Keep list and Block list rules syntax
|
||||
* Add support for IPv6 with zone index
|
||||
* Allow images with data URLs
|
||||
* Limit full-text search indexation to first 500K characters (tsvector has a size limit of 1MB)
|
||||
* Change PWA display mode to standalone
|
||||
* ETag value is not set correctly in HTTP client (regression)
|
||||
* Add database backed Let's Encrypt certificate cache
|
||||
* Add global option POLLING_PARSING_ERROR_LIMIT
|
||||
* Update systemd service file comments to use `systemctl edit` for editing
|
||||
* Update Go version to 1.15 in go.mod
|
||||
* Don't discard the "Fetch via Proxy" option
|
||||
* Update man page to show the default values
|
||||
* Add PostgreSQL indices
|
||||
* Add API endpoints to get feeds and entries of a category
|
||||
* Create feed query builder
|
||||
* Bump github.com/PuerkitoBio/goquery from 1.6.0 to 1.6.1
|
||||
* Show global options in the about page
|
||||
* Update man page to mention -1 can be used for CLEANUP_ARCHIVE_* options
|
||||
|
||||
Version 2.0.27 (Jan 9, 2021)
|
||||
----------------------------
|
||||
|
||||
@@ -377,7 +835,7 @@ Version 2.0.13 (Nov 25, 2018)
|
||||
|
||||
* Add man page
|
||||
* Add support for Systemd Socket Activation (experimental)
|
||||
* Add the possiblity to listen on Unix sockets
|
||||
* Add the possibility to listen on Unix sockets
|
||||
* Add config options to disable HTTP and scheduler services
|
||||
* Archive more read entries in cleanup job
|
||||
* Change default database connection string (backward compatible)
|
||||
|
||||
@@ -10,7 +10,7 @@ DEB_IMG_ARCH := amd64
|
||||
|
||||
export PGPASSWORD := postgres
|
||||
|
||||
.PHONY: generate \
|
||||
.PHONY: \
|
||||
miniflux \
|
||||
linux-amd64 \
|
||||
linux-arm64 \
|
||||
@@ -19,6 +19,7 @@ export PGPASSWORD := postgres
|
||||
linux-armv5 \
|
||||
linux-x86 \
|
||||
darwin-amd64 \
|
||||
darwin-arm64 \
|
||||
freebsd-amd64 \
|
||||
freebsd-x86 \
|
||||
openbsd-amd64 \
|
||||
@@ -35,67 +36,68 @@ export PGPASSWORD := postgres
|
||||
integration-test \
|
||||
clean-integration-test \
|
||||
docker-image \
|
||||
docker-image-distroless \
|
||||
docker-images \
|
||||
rpm \
|
||||
debian \
|
||||
debian-packages
|
||||
|
||||
generate:
|
||||
@ go generate
|
||||
miniflux:
|
||||
@ go build -buildmode=pie -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
||||
|
||||
miniflux: generate
|
||||
@ go build -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
||||
linux-amd64:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-amd64: generate
|
||||
@ GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-amd64 main.go
|
||||
linux-arm64:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-arm64: generate
|
||||
@ GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-arm64 main.go
|
||||
linux-armv7:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv7: generate
|
||||
@ GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv7 main.go
|
||||
linux-armv6:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv6: generate
|
||||
@ GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv6 main.go
|
||||
linux-armv5:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv5: generate
|
||||
@ GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv5 main.go
|
||||
darwin-amd64:
|
||||
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
darwin-amd64: generate
|
||||
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-darwin-amd64 main.go
|
||||
darwin-arm64:
|
||||
@ GOOS=darwin GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
freebsd-amd64: generate
|
||||
@ GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-amd64 main.go
|
||||
freebsd-amd64:
|
||||
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
openbsd-amd64: generate
|
||||
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-openbsd-amd64 main.go
|
||||
openbsd-amd64:
|
||||
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
windows-amd64: generate
|
||||
@ GOOS=windows GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-windows-amd64 main.go
|
||||
windows-amd64:
|
||||
@ GOOS=windows GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
build: linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-armv5 darwin-amd64 freebsd-amd64 openbsd-amd64 windows-amd64
|
||||
build: linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-armv5 darwin-amd64 darwin-arm64 freebsd-amd64 openbsd-amd64 windows-amd64
|
||||
|
||||
# NOTE: unsupported targets
|
||||
netbsd-amd64: generate
|
||||
@ GOOS=netbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-netbsd-amd64 main.go
|
||||
netbsd-amd64:
|
||||
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-x86: generate
|
||||
@ GOOS=linux GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-x86 main.go
|
||||
linux-x86:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
freebsd-x86: generate
|
||||
@ GOOS=freebsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-x86 main.go
|
||||
freebsd-x86:
|
||||
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
netbsd-x86: generate
|
||||
@ GOOS=netbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-netbsd-x86 main.go
|
||||
netbsd-x86:
|
||||
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
openbsd-x86: generate
|
||||
@ GOOS=openbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-x86 main.go
|
||||
openbsd-x86:
|
||||
@ GOOS=openbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
windows-x86: generate
|
||||
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-windows-x86 main.go
|
||||
windows-x86:
|
||||
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
run: generate
|
||||
@ LOG_DATE_TIME=1 go run main.go -debug
|
||||
run:
|
||||
@ LOG_DATE_TIME=1 DEBUG=1 RUN_MIGRATIONS=1 go run main.go
|
||||
|
||||
clean:
|
||||
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb
|
||||
@@ -109,11 +111,17 @@ lint:
|
||||
integration-test:
|
||||
psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||
psql -U postgres -c 'create database miniflux_test;'
|
||||
DATABASE_URL=$(DB_URL) go run main.go -migrate
|
||||
DATABASE_URL=$(DB_URL) ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go -create-admin
|
||||
go build -o miniflux-test main.go
|
||||
DATABASE_URL=$(DB_URL) ./miniflux-test -debug >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||
while ! echo exit | nc localhost 8080; do sleep 1; done >/dev/null
|
||||
|
||||
DATABASE_URL=$(DB_URL) \
|
||||
ADMIN_USERNAME=admin \
|
||||
ADMIN_PASSWORD=test123 \
|
||||
CREATE_ADMIN=1 \
|
||||
RUN_MIGRATIONS=1 \
|
||||
DEBUG=1 \
|
||||
./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||
|
||||
while ! nc -z localhost 8080; do sleep 1; done
|
||||
go test -v -tags=integration -count=1 miniflux.app/tests
|
||||
|
||||
clean-integration-test:
|
||||
@@ -123,12 +131,15 @@ clean-integration-test:
|
||||
@ psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||
|
||||
docker-image:
|
||||
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/Dockerfile .
|
||||
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/alpine/Dockerfile .
|
||||
|
||||
docker-image-distroless:
|
||||
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/distroless/Dockerfile .
|
||||
|
||||
docker-images:
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
||||
--file packaging/docker/Dockerfile \
|
||||
--file packaging/docker/alpine/Dockerfile \
|
||||
--tag $(DOCKER_IMAGE):$(VERSION) \
|
||||
--push .
|
||||
|
||||
@@ -142,7 +153,7 @@ rpm: clean
|
||||
rpmbuild -bb --define "_miniflux_version $(VERSION)" /root/rpmbuild/SPECS/miniflux.spec
|
||||
|
||||
debian:
|
||||
@ docker build \
|
||||
@ docker build --load \
|
||||
--build-arg BASE_IMAGE_ARCH=$(DEB_IMG_ARCH) \
|
||||
-t $(DEB_IMG_ARCH)/miniflux-deb-builder \
|
||||
-f packaging/debian/Dockerfile \
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest stable version is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Preferably, [report the vulnerability privately using GitHub](https://github.com/miniflux/v2/security/advisories/new) ([documentation](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)).
|
||||
|
||||
If you do not want to use GitHub, send an email to `security AT miniflux DOT net` with all the steps to reproduce the problem.
|
||||
+12
-6
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -14,13 +13,14 @@ import (
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
store *storage.Storage
|
||||
pool *worker.Pool
|
||||
store *storage.Storage
|
||||
pool *worker.Pool
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// Serve declares API routes for the application.
|
||||
func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
|
||||
handler := &handler{store, pool}
|
||||
handler := &handler{store, pool, router}
|
||||
|
||||
sr := router.PathPrefix("/v1").Subrouter()
|
||||
middleware := newMiddleware(store)
|
||||
@@ -41,9 +41,14 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
|
||||
sr.HandleFunc("/categories/{categoryID}", handler.updateCategory).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/categories/{categoryID}", handler.removeCategory).Methods(http.MethodDelete)
|
||||
sr.HandleFunc("/categories/{categoryID}/mark-all-as-read", handler.markCategoryAsRead).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/categories/{categoryID}/feeds", handler.getCategoryFeeds).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/categories/{categoryID}/refresh", handler.refreshCategory).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/categories/{categoryID}/entries", handler.getCategoryEntries).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/categories/{categoryID}/entries/{entryID}", handler.getCategoryEntry).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/discover", handler.discoverSubscriptions).Methods(http.MethodPost)
|
||||
sr.HandleFunc("/feeds", handler.createFeed).Methods(http.MethodPost)
|
||||
sr.HandleFunc("/feeds", handler.getFeeds).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/feeds/counters", handler.fetchCounters).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/feeds/{feedID}/refresh", handler.refreshFeed).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/feeds/{feedID}", handler.getFeed).Methods(http.MethodGet)
|
||||
@@ -59,4 +64,5 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
|
||||
sr.HandleFunc("/entries", handler.setEntryStatus).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/entries/{entryID}", handler.getEntry).Methods(http.MethodGet)
|
||||
sr.HandleFunc("/entries/{entryID}/bookmark", handler.toggleBookmark).Methods(http.MethodPut)
|
||||
sr.HandleFunc("/entries/{entryID}/fetch-content", handler.fetchContent).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
+29
-5
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -98,12 +97,20 @@ func (h *handler) markCategoryAsRead(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *handler) getCategories(w http.ResponseWriter, r *http.Request) {
|
||||
categories, err := h.store.Categories(request.UserID(r))
|
||||
var categories model.Categories
|
||||
var err error
|
||||
includeCounts := request.QueryStringParam(r, "counts", "false")
|
||||
|
||||
if includeCounts == "true" {
|
||||
categories, err = h.store.CategoriesWithFeedCount(request.UserID(r))
|
||||
} else {
|
||||
categories, err = h.store.Categories(request.UserID(r))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
json.OK(w, r, categories)
|
||||
}
|
||||
|
||||
@@ -123,3 +130,20 @@ func (h *handler) removeCategory(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
json.NoContent(w, r)
|
||||
}
|
||||
|
||||
func (h *handler) refreshCategory(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
categoryID := request.RouteInt64Param(r, "categoryID")
|
||||
|
||||
jobs, err := h.store.NewCategoryBatch(userID, categoryID, h.store.CountFeeds(userID))
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
h.pool.Push(jobs)
|
||||
}()
|
||||
|
||||
json.NoContent(w, r)
|
||||
}
|
||||
|
||||
+120
-32
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -8,15 +7,50 @@ import (
|
||||
json_parser "encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/http/request"
|
||||
"miniflux.app/http/response/json"
|
||||
"miniflux.app/model"
|
||||
"miniflux.app/proxy"
|
||||
"miniflux.app/reader/processor"
|
||||
"miniflux.app/storage"
|
||||
"miniflux.app/url"
|
||||
"miniflux.app/validator"
|
||||
)
|
||||
|
||||
func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b *storage.EntryQueryBuilder) {
|
||||
entry, err := b.GetEntry()
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
|
||||
proxyOption := config.Opts.ProxyOption()
|
||||
|
||||
for i := range entry.Enclosures {
|
||||
if proxyOption == "all" || proxyOption != "none" && !url.IsHTTPS(entry.Enclosures[i].URL) {
|
||||
for _, mediaType := range config.Opts.ProxyMediaTypes() {
|
||||
if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
|
||||
entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.OK(w, r, entry)
|
||||
}
|
||||
|
||||
func (h *handler) getFeedEntry(w http.ResponseWriter, r *http.Request) {
|
||||
feedID := request.RouteInt64Param(r, "feedID")
|
||||
entryID := request.RouteInt64Param(r, "entryID")
|
||||
@@ -25,18 +59,18 @@ func (h *handler) getFeedEntry(w http.ResponseWriter, r *http.Request) {
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
h.getEntryFromBuilder(w, r, builder)
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
func (h *handler) getCategoryEntry(w http.ResponseWriter, r *http.Request) {
|
||||
categoryID := request.RouteInt64Param(r, "categoryID")
|
||||
entryID := request.RouteInt64Param(r, "entryID")
|
||||
|
||||
json.OK(w, r, entry)
|
||||
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
|
||||
builder.WithCategoryID(categoryID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
h.getEntryFromBuilder(w, r, builder)
|
||||
}
|
||||
|
||||
func (h *handler) getEntry(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -44,30 +78,24 @@ func (h *handler) getEntry(w http.ResponseWriter, r *http.Request) {
|
||||
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
json.OK(w, r, entry)
|
||||
h.getEntryFromBuilder(w, r, builder)
|
||||
}
|
||||
|
||||
func (h *handler) getFeedEntries(w http.ResponseWriter, r *http.Request) {
|
||||
feedID := request.RouteInt64Param(r, "feedID")
|
||||
h.findEntries(w, r, feedID)
|
||||
h.findEntries(w, r, feedID, 0)
|
||||
}
|
||||
|
||||
func (h *handler) getCategoryEntries(w http.ResponseWriter, r *http.Request) {
|
||||
categoryID := request.RouteInt64Param(r, "categoryID")
|
||||
h.findEntries(w, r, 0, categoryID)
|
||||
}
|
||||
|
||||
func (h *handler) getEntries(w http.ResponseWriter, r *http.Request) {
|
||||
h.findEntries(w, r, 0)
|
||||
h.findEntries(w, r, 0, 0)
|
||||
}
|
||||
|
||||
func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int64) {
|
||||
func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int64, categoryID int64) {
|
||||
statuses := request.QueryStringParamList(r, "status")
|
||||
for _, status := range statuses {
|
||||
if err := validator.ValidateEntryStatus(status); err != nil {
|
||||
@@ -96,7 +124,7 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
|
||||
}
|
||||
|
||||
userID := request.UserID(r)
|
||||
categoryID := request.QueryInt64Param(r, "category_id", 0)
|
||||
categoryID = request.QueryInt64Param(r, "category_id", categoryID)
|
||||
if categoryID > 0 && !h.store.CategoryIDExists(userID, categoryID) {
|
||||
json.BadRequest(w, r, errors.New("Invalid category ID"))
|
||||
return
|
||||
@@ -108,14 +136,16 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
|
||||
return
|
||||
}
|
||||
|
||||
tags := request.QueryStringParamList(r, "tags")
|
||||
|
||||
builder := h.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithCategoryID(categoryID)
|
||||
builder.WithStatuses(statuses)
|
||||
builder.WithOrder(order)
|
||||
builder.WithDirection(direction)
|
||||
builder.WithSorting(order, direction)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(limit)
|
||||
builder.WithTags(tags)
|
||||
configureFilters(builder, r)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
@@ -130,6 +160,10 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
|
||||
return
|
||||
}
|
||||
|
||||
for i := range entries {
|
||||
entries[i].Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entries[i].Content)
|
||||
}
|
||||
|
||||
json.OK(w, r, &entriesResponse{Total: count, Entries: entries})
|
||||
}
|
||||
|
||||
@@ -163,6 +197,57 @@ func (h *handler) toggleBookmark(w http.ResponseWriter, r *http.Request) {
|
||||
json.NoContent(w, r)
|
||||
}
|
||||
|
||||
func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
|
||||
loggedUserID := request.UserID(r)
|
||||
entryID := request.RouteInt64Param(r, "entryID")
|
||||
|
||||
entryBuilder := h.store.NewEntryQueryBuilder(loggedUserID)
|
||||
entryBuilder.WithEntryID(entryID)
|
||||
entryBuilder.WithoutStatus(model.EntryStatusRemoved)
|
||||
|
||||
entry, err := entryBuilder.GetEntry()
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.store.UserByID(entry.UserID)
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
feedBuilder := storage.NewFeedQueryBuilder(h.store, loggedUserID)
|
||||
feedBuilder.WithFeedID(entry.FeedID)
|
||||
feed, err := feedBuilder.GetFeed()
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if feed == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err := processor.ProcessEntryWebPage(feed, entry, user); err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
json.OK(w, r, map[string]string{"content": entry.Content})
|
||||
}
|
||||
|
||||
func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
|
||||
beforeEntryID := request.QueryInt64Param(r, "before_entry_id", 0)
|
||||
if beforeEntryID > 0 {
|
||||
@@ -190,7 +275,10 @@ func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
|
||||
}
|
||||
|
||||
if request.HasQueryParam(r, "starred") {
|
||||
builder.WithStarred()
|
||||
starred, err := strconv.ParseBool(r.URL.Query().Get("starred"))
|
||||
if err == nil {
|
||||
builder.WithStarred(starred)
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery := request.QueryStringParam(r, "search", "")
|
||||
|
||||
+36
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -136,6 +135,30 @@ func (h *handler) markFeedAsRead(w http.ResponseWriter, r *http.Request) {
|
||||
json.NoContent(w, r)
|
||||
}
|
||||
|
||||
func (h *handler) getCategoryFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
categoryID := request.RouteInt64Param(r, "categoryID")
|
||||
|
||||
category, err := h.store.Category(userID, categoryID)
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if category == nil {
|
||||
json.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
feeds, err := h.store.FeedsByCategoryWithCounters(userID, categoryID)
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
json.OK(w, r, feeds)
|
||||
}
|
||||
|
||||
func (h *handler) getFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
feeds, err := h.store.Feeds(request.UserID(r))
|
||||
if err != nil {
|
||||
@@ -146,6 +169,16 @@ func (h *handler) getFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
json.OK(w, r, feeds)
|
||||
}
|
||||
|
||||
func (h *handler) fetchCounters(w http.ResponseWriter, r *http.Request) {
|
||||
counters, err := h.store.FetchCounters(request.UserID(r))
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
json.OK(w, r, counters)
|
||||
}
|
||||
|
||||
func (h *handler) getFeed(w http.ResponseWriter, r *http.Request) {
|
||||
feedID := request.RouteInt64Param(r, "feedID")
|
||||
feed, err := h.store.FeedByID(request.UserID(r), feedID)
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
|
||||
+4
-4
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -25,8 +24,9 @@ func (m *middleware) handleCORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, Authorization, Content-Type, Accept")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
|
||||
+4
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
@@ -29,9 +28,11 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
|
||||
subscriptions, finderErr := subscription.FindSubscriptions(
|
||||
subscriptionDiscoveryRequest.URL,
|
||||
subscriptionDiscoveryRequest.UserAgent,
|
||||
subscriptionDiscoveryRequest.Cookie,
|
||||
subscriptionDiscoveryRequest.Username,
|
||||
subscriptionDiscoveryRequest.Password,
|
||||
subscriptionDiscoveryRequest.FetchViaProxy,
|
||||
subscriptionDiscoveryRequest.AllowSelfSignedCertificates,
|
||||
)
|
||||
if finderErr != nil {
|
||||
json.ServerError(w, r, finderErr)
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api // import "miniflux.app/api"
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
@@ -10,13 +9,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func askCredentials() (string, string) {
|
||||
fd := int(os.Stdin.Fd())
|
||||
|
||||
if !terminal.IsTerminal(fd) {
|
||||
if !term.IsTerminal(fd) {
|
||||
fmt.Fprintf(os.Stderr, "This is not a terminal, exiting.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -28,9 +27,9 @@ func askCredentials() (string, string) {
|
||||
|
||||
fmt.Print("Enter Password: ")
|
||||
|
||||
state, _ := terminal.GetState(fd)
|
||||
defer terminal.Restore(fd, state)
|
||||
bytePassword, _ := terminal.ReadPassword(fd)
|
||||
state, _ := term.GetState(fd)
|
||||
defer term.Restore(fd, state)
|
||||
bytePassword, _ := term.ReadPassword(fd)
|
||||
|
||||
fmt.Printf("\n")
|
||||
return strings.TrimSpace(username), strings.TrimSpace(string(bytePassword))
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/metric"
|
||||
"miniflux.app/model"
|
||||
"miniflux.app/storage"
|
||||
)
|
||||
|
||||
func runCleanupTasks(store *storage.Storage) {
|
||||
nbSessions := store.CleanOldSessions(config.Opts.CleanupRemoveSessionsDays())
|
||||
nbUserSessions := store.CleanOldUserSessions(config.Opts.CleanupRemoveSessionsDays())
|
||||
logger.Info("[Sessions] Removed %d application sessions and %d user sessions", nbSessions, nbUserSessions)
|
||||
|
||||
startTime := time.Now()
|
||||
if rowsAffected, err := store.ArchiveEntries(model.EntryStatusRead, config.Opts.CleanupArchiveReadDays(), config.Opts.CleanupArchiveBatchSize()); err != nil {
|
||||
logger.Error("[ArchiveReadEntries] %v", err)
|
||||
} else {
|
||||
logger.Info("[ArchiveReadEntries] %d entries changed", rowsAffected)
|
||||
|
||||
if config.Opts.HasMetricsCollector() {
|
||||
metric.ArchiveEntriesDuration.WithLabelValues(model.EntryStatusRead).Observe(time.Since(startTime).Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
startTime = time.Now()
|
||||
if rowsAffected, err := store.ArchiveEntries(model.EntryStatusUnread, config.Opts.CleanupArchiveUnreadDays(), config.Opts.CleanupArchiveBatchSize()); err != nil {
|
||||
logger.Error("[ArchiveUnreadEntries] %v", err)
|
||||
} else {
|
||||
logger.Info("[ArchiveUnreadEntries] %d entries changed", rowsAffected)
|
||||
|
||||
if config.Opts.HasMetricsCollector() {
|
||||
metric.ArchiveEntriesDuration.WithLabelValues(model.EntryStatusUnread).Observe(time.Since(startTime).Seconds())
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-11
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
@@ -10,13 +9,15 @@ import (
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/database"
|
||||
"miniflux.app/locale"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/storage"
|
||||
"miniflux.app/ui/static"
|
||||
"miniflux.app/version"
|
||||
)
|
||||
|
||||
const (
|
||||
flagInfoHelp = "Show application information"
|
||||
flagInfoHelp = "Show build information"
|
||||
flagVersionHelp = "Show application version"
|
||||
flagMigrateHelp = "Run SQL migrations"
|
||||
flagFlushSessionsHelp = "Flush all sessions (disconnect users)"
|
||||
@@ -26,6 +27,9 @@ const (
|
||||
flagDebugModeHelp = "Show debug logs"
|
||||
flagConfigFileHelp = "Load configuration file"
|
||||
flagConfigDumpHelp = "Print parsed configuration values"
|
||||
flagHealthCheckHelp = `Perform a health check on the given endpoint (the value "auto" try to guess the health check endpoint).`
|
||||
flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
|
||||
flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archives old entries)"
|
||||
)
|
||||
|
||||
// Parse parses command line arguments.
|
||||
@@ -42,6 +46,9 @@ func Parse() {
|
||||
flagDebugMode bool
|
||||
flagConfigFile string
|
||||
flagConfigDump bool
|
||||
flagHealthCheck string
|
||||
flagRefreshFeeds bool
|
||||
flagRunCleanupTasks bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
|
||||
@@ -57,6 +64,9 @@ func Parse() {
|
||||
flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
|
||||
flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
|
||||
flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
|
||||
flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
|
||||
flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
|
||||
flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
|
||||
flag.Parse()
|
||||
|
||||
cfg := config.NewParser()
|
||||
@@ -86,6 +96,11 @@ func Parse() {
|
||||
logger.EnableDebug()
|
||||
}
|
||||
|
||||
if flagHealthCheck != "" {
|
||||
doHealthCheck(flagHealthCheck)
|
||||
return
|
||||
}
|
||||
|
||||
if flagInfo {
|
||||
info()
|
||||
return
|
||||
@@ -100,22 +115,47 @@ func Parse() {
|
||||
logger.Info("The default value for DATABASE_URL is used")
|
||||
}
|
||||
|
||||
logger.Debug("Loading translations...")
|
||||
if err := locale.LoadCatalogMessages(); err != nil {
|
||||
logger.Fatal("Unable to load translations: %v", err)
|
||||
}
|
||||
|
||||
logger.Debug("Loading static assets...")
|
||||
if err := static.CalculateBinaryFileChecksums(); err != nil {
|
||||
logger.Fatal("Unable to calculate binary files checksum: %v", err)
|
||||
}
|
||||
|
||||
if err := static.GenerateStylesheetsBundles(); err != nil {
|
||||
logger.Fatal("Unable to generate Stylesheet bundles: %v", err)
|
||||
}
|
||||
|
||||
if err := static.GenerateJavascriptBundles(); err != nil {
|
||||
logger.Fatal("Unable to generate Javascript bundles: %v", err)
|
||||
}
|
||||
|
||||
db, err := database.NewConnectionPool(
|
||||
config.Opts.DatabaseURL(),
|
||||
config.Opts.DatabaseMinConns(),
|
||||
config.Opts.DatabaseMaxConns(),
|
||||
config.Opts.DatabaseConnectionLifetime(),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Fatal("Unable to connect to the database: %v", err)
|
||||
logger.Fatal("Unable to initialize database connection pool: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if flagMigrate {
|
||||
database.Migrate(db)
|
||||
return
|
||||
store := storage.NewStorage(db)
|
||||
|
||||
if err := store.Ping(); err != nil {
|
||||
logger.Fatal("Unable to connect to the database: %v", err)
|
||||
}
|
||||
|
||||
store := storage.NewStorage(db)
|
||||
if flagMigrate {
|
||||
if err := database.Migrate(db); err != nil {
|
||||
logger.Fatal(`%v`, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if flagResetFeedErrors {
|
||||
store.ResetFeedErrors()
|
||||
@@ -137,7 +177,7 @@ func Parse() {
|
||||
return
|
||||
}
|
||||
|
||||
// Run migrations and start the deamon.
|
||||
// Run migrations and start the daemon.
|
||||
if config.Opts.RunMigrations() {
|
||||
if err := database.Migrate(db); err != nil {
|
||||
logger.Fatal(`%v`, err)
|
||||
@@ -148,10 +188,20 @@ func Parse() {
|
||||
logger.Fatal(`You must run the SQL migrations, %v`, err)
|
||||
}
|
||||
|
||||
// Create admin user and start the deamon.
|
||||
// Create admin user and start the daemon.
|
||||
if config.Opts.CreateAdmin() {
|
||||
createAdmin(store)
|
||||
}
|
||||
|
||||
if flagRefreshFeeds {
|
||||
refreshFeeds(store)
|
||||
return
|
||||
}
|
||||
|
||||
if flagRunCleanupTasks {
|
||||
runCleanupTasks(store)
|
||||
return
|
||||
}
|
||||
|
||||
startDaemon(store)
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
|
||||
+38
-8
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
@@ -13,16 +12,16 @@ import (
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
httpd "miniflux.app/http/server"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/metric"
|
||||
"miniflux.app/service/httpd"
|
||||
"miniflux.app/service/scheduler"
|
||||
"miniflux.app/storage"
|
||||
"miniflux.app/systemd"
|
||||
"miniflux.app/worker"
|
||||
)
|
||||
|
||||
func startDaemon(store *storage.Storage) {
|
||||
logger.Info("Starting Miniflux...")
|
||||
logger.Info("Starting daemon...")
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
@@ -31,12 +30,12 @@ func startDaemon(store *storage.Storage) {
|
||||
pool := worker.NewPool(store, config.Opts.WorkerPoolSize())
|
||||
|
||||
if config.Opts.HasSchedulerService() && !config.Opts.HasMaintenanceMode() {
|
||||
scheduler.Serve(store, pool)
|
||||
runScheduler(store, pool)
|
||||
}
|
||||
|
||||
var httpServer *http.Server
|
||||
if config.Opts.HasHTTPService() {
|
||||
httpServer = httpd.Serve(store, pool)
|
||||
httpServer = httpd.StartWebServer(store, pool)
|
||||
}
|
||||
|
||||
if config.Opts.HasMetricsCollector() {
|
||||
@@ -44,6 +43,37 @@ func startDaemon(store *storage.Storage) {
|
||||
go collector.GatherStorageMetrics()
|
||||
}
|
||||
|
||||
if systemd.HasNotifySocket() {
|
||||
logger.Info("Sending readiness notification to Systemd")
|
||||
|
||||
if err := systemd.SdNotify(systemd.SdNotifyReady); err != nil {
|
||||
logger.Error("Unable to send readiness notification to systemd: %v", err)
|
||||
}
|
||||
|
||||
if config.Opts.HasWatchdog() && systemd.HasSystemdWatchdog() {
|
||||
logger.Info("Activating Systemd watchdog")
|
||||
|
||||
go func() {
|
||||
interval, err := systemd.WatchdogInterval()
|
||||
if err != nil {
|
||||
logger.Error("Unable to parse watchdog interval from systemd: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
err := store.Ping()
|
||||
if err != nil {
|
||||
logger.Error(`Systemd Watchdog: %v`, err)
|
||||
} else {
|
||||
systemd.SdNotify(systemd.SdNotifyWatchdog)
|
||||
}
|
||||
|
||||
time.Sleep(interval / 3)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
<-stop
|
||||
logger.Info("Shutting down the process...")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package cli implements command line arguments for Miniflux application.
|
||||
|
||||
*/
|
||||
package cli // import "miniflux.app/cli"
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/logger"
|
||||
)
|
||||
|
||||
func doHealthCheck(healthCheckEndpoint string) {
|
||||
if healthCheckEndpoint == "auto" {
|
||||
healthCheckEndpoint = "http://" + config.Opts.ListenAddr() + config.Opts.BasePath() + "/healthcheck"
|
||||
}
|
||||
|
||||
logger.Debug(`Executing health check on %s`, healthCheckEndpoint)
|
||||
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(healthCheckEndpoint)
|
||||
if err != nil {
|
||||
logger.Fatal(`Health check failure: %v`, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
logger.Fatal(`Health check failed with status code %d`, resp.StatusCode)
|
||||
}
|
||||
|
||||
logger.Debug(`Health check is OK`)
|
||||
}
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/model"
|
||||
feedHandler "miniflux.app/reader/handler"
|
||||
"miniflux.app/storage"
|
||||
)
|
||||
|
||||
func refreshFeeds(store *storage.Storage) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
startTime := time.Now()
|
||||
jobs, err := store.NewBatch(config.Opts.BatchSize())
|
||||
if err != nil {
|
||||
logger.Error("[Cronjob] %v", err)
|
||||
}
|
||||
|
||||
nbJobs := len(jobs)
|
||||
logger.Info("[Cronjob]] Created %d jobs from a batch size of %d", nbJobs, config.Opts.BatchSize())
|
||||
var jobQueue = make(chan model.Job, nbJobs)
|
||||
|
||||
logger.Info("[Cronjob] Starting a pool of %d workers", config.Opts.WorkerPoolSize())
|
||||
for i := 0; i < config.Opts.WorkerPoolSize(); i++ {
|
||||
wg.Add(1)
|
||||
go func(workerID int) {
|
||||
defer wg.Done()
|
||||
for job := range jobQueue {
|
||||
logger.Info("[Cronjob] Refreshing feed #%d for user #%d in worker #%d", job.FeedID, job.UserID, workerID)
|
||||
if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID); err != nil {
|
||||
logger.Error("[Cronjob] Refreshing the feed #%d returned this error: %v", job.FeedID, err)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
jobQueue <- job
|
||||
}
|
||||
close(jobQueue)
|
||||
|
||||
wg.Wait()
|
||||
logger.Info("[Cronjob] Refreshed %d feed(s) in %s", nbJobs, time.Since(startTime))
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
@@ -34,6 +33,7 @@ func resetPassword(store *storage.Storage) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
user.Password = password
|
||||
if err := store.UpdateUser(user); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli // import "miniflux.app/cli"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"miniflux.app/config"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/storage"
|
||||
"miniflux.app/worker"
|
||||
)
|
||||
|
||||
func runScheduler(store *storage.Storage, pool *worker.Pool) {
|
||||
logger.Info(`Starting background scheduler...`)
|
||||
|
||||
go feedScheduler(
|
||||
store,
|
||||
pool,
|
||||
config.Opts.PollingFrequency(),
|
||||
config.Opts.BatchSize(),
|
||||
)
|
||||
|
||||
go cleanupScheduler(
|
||||
store,
|
||||
config.Opts.CleanupFrequencyHours(),
|
||||
)
|
||||
}
|
||||
|
||||
func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSize int) {
|
||||
for range time.Tick(time.Duration(frequency) * time.Minute) {
|
||||
jobs, err := store.NewBatch(batchSize)
|
||||
logger.Info("[Scheduler:Feed] Pushing %d jobs to the queue", len(jobs))
|
||||
if err != nil {
|
||||
logger.Error("[Scheduler:Feed] %v", err)
|
||||
} else {
|
||||
pool.Push(jobs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupScheduler(store *storage.Storage, frequency int) {
|
||||
for range time.Tick(time.Duration(frequency) * time.Hour) {
|
||||
runCleanupTasks(store)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -20,7 +20,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
miniflux "miniflux.app/client"
|
||||
)
|
||||
@@ -47,7 +47,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("opml.xml", opml, 0644)
|
||||
err = os.WriteFile("opml.xml", opml, 0644)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
||||
+88
-10
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/client"
|
||||
|
||||
@@ -8,9 +7,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Client holds API procedure calls.
|
||||
@@ -20,6 +19,11 @@ type Client struct {
|
||||
|
||||
// New returns a new Miniflux client.
|
||||
func New(endpoint string, credentials ...string) *Client {
|
||||
// Web gives "API Endpoint = https://miniflux.app/v1/", it doesn't work (/v1/v1/me)
|
||||
endpoint = strings.TrimSuffix(endpoint, "/")
|
||||
endpoint = strings.TrimSuffix(endpoint, "/v1")
|
||||
// trim to https://miniflux.app
|
||||
|
||||
if len(credentials) == 2 {
|
||||
return &Client{request: &request{endpoint: endpoint, username: credentials[0], password: credentials[1]}}
|
||||
}
|
||||
@@ -182,7 +186,6 @@ func (c *Client) CreateCategory(title string) (*Category, error) {
|
||||
body, err := c.request.Post("/v1/categories", map[string]interface{}{
|
||||
"title": title,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,7 +205,6 @@ func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, erro
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
|
||||
"title": title,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -223,11 +225,34 @@ func (c *Client) MarkCategoryAsRead(categoryID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// CategoryFeeds gets feeds of a category.
|
||||
func (c *Client) CategoryFeeds(categoryID int64) (Feeds, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/feeds", categoryID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var feeds Feeds
|
||||
decoder := json.NewDecoder(body)
|
||||
if err := decoder.Decode(&feeds); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return feeds, nil
|
||||
}
|
||||
|
||||
// DeleteCategory removes a category.
|
||||
func (c *Client) DeleteCategory(categoryID int64) error {
|
||||
return c.request.Delete(fmt.Sprintf("/v1/categories/%d", categoryID))
|
||||
}
|
||||
|
||||
// RefreshCategory refreshes a category.
|
||||
func (c *Client) RefreshCategory(categoryID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/refresh", categoryID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Feeds gets all feeds.
|
||||
func (c *Client) Feeds() (Feeds, error) {
|
||||
body, err := c.request.Get("/v1/feeds")
|
||||
@@ -253,7 +278,7 @@ func (c *Client) Export() ([]byte, error) {
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
opml, err := ioutil.ReadAll(body)
|
||||
opml, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -329,7 +354,7 @@ func (c *Client) MarkFeedAsRead(feedID int64) error {
|
||||
|
||||
// RefreshAllFeeds refreshes all feeds.
|
||||
func (c *Client) RefreshAllFeeds() error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/feeds/refresh"), nil)
|
||||
_, err := c.request.Put("/v1/feeds/refresh", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -378,6 +403,23 @@ func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// CategoryEntry gets a single category entry.
|
||||
func (c *Client) CategoryEntry(categoryID, entryID int64) (*Entry, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/entries/%d", categoryID, entryID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var entry *Entry
|
||||
decoder := json.NewDecoder(body)
|
||||
if err := decoder.Decode(&entry); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Entry gets a single entry.
|
||||
func (c *Client) Entry(entryID int64) (*Entry, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d", entryID))
|
||||
@@ -433,6 +475,25 @@ func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, err
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// CategoryEntries fetch entries of a category.
|
||||
func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
|
||||
path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
|
||||
|
||||
body, err := c.request.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var result EntryResultSet
|
||||
decoder := json.NewDecoder(body)
|
||||
if err := decoder.Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// UpdateEntries updates the status of a list of entries.
|
||||
func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
|
||||
type payload struct {
|
||||
@@ -450,6 +511,23 @@ func (c *Client) ToggleBookmark(entryID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchCounters
|
||||
func (c *Client) FetchCounters() (*FeedCounters, error) {
|
||||
body, err := c.request.Get("/v1/feeds/counters")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var result FeedCounters
|
||||
decoder := json.NewDecoder(body)
|
||||
if err := decoder.Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func buildFilterQueryString(path string, filter *Filter) string {
|
||||
if filter != nil {
|
||||
values := url.Values{}
|
||||
@@ -490,8 +568,8 @@ func buildFilterQueryString(path string, filter *Filter) string {
|
||||
values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
|
||||
}
|
||||
|
||||
if filter.Starred {
|
||||
values.Set("starred", "1")
|
||||
if filter.Starred != "" {
|
||||
values.Set("starred", filter.Starred)
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package client // import "miniflux.app/client"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Entry statuses.
|
||||
const (
|
||||
EntryStatusUnread = "unread"
|
||||
EntryStatusRead = "read"
|
||||
EntryStatusRemoved = "removed"
|
||||
)
|
||||
|
||||
// User represents a user in the system.
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
Theme string `json:"theme"`
|
||||
Language string `json:"language"`
|
||||
Timezone string `json:"timezone"`
|
||||
EntryDirection string `json:"entry_sorting_direction"`
|
||||
Stylesheet string `json:"stylesheet"`
|
||||
GoogleID string `json:"google_id"`
|
||||
OpenIDConnectID string `json:"openid_connect_id"`
|
||||
EntriesPerPage int `json:"entries_per_page"`
|
||||
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime bool `json:"show_reading_time"`
|
||||
EntrySwipe bool `json:"entry_swipe"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
return fmt.Sprintf("#%d - %s (admin=%v)", u.ID, u.Username, u.IsAdmin)
|
||||
}
|
||||
|
||||
// UserCreationRequest represents the request to create a user.
|
||||
type UserCreationRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
GoogleID string `json:"google_id"`
|
||||
OpenIDConnectID string `json:"openid_connect_id"`
|
||||
}
|
||||
|
||||
// UserModificationRequest represents the request to update a user.
|
||||
type UserModificationRequest struct {
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
IsAdmin *bool `json:"is_admin"`
|
||||
Theme *string `json:"theme"`
|
||||
Language *string `json:"language"`
|
||||
Timezone *string `json:"timezone"`
|
||||
EntryDirection *string `json:"entry_sorting_direction"`
|
||||
Stylesheet *string `json:"stylesheet"`
|
||||
GoogleID *string `json:"google_id"`
|
||||
OpenIDConnectID *string `json:"openid_connect_id"`
|
||||
EntriesPerPage *int `json:"entries_per_page"`
|
||||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime *bool `json:"show_reading_time"`
|
||||
EntrySwipe *bool `json:"entry_swipe"`
|
||||
}
|
||||
|
||||
// Users represents a list of users.
|
||||
type Users []User
|
||||
|
||||
// Category represents a feed category.
|
||||
type Category struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
func (c Category) String() string {
|
||||
return fmt.Sprintf("#%d %s", c.ID, c.Title)
|
||||
}
|
||||
|
||||
// Categories represents a list of categories.
|
||||
type Categories []*Category
|
||||
|
||||
// Subscription represents a feed subscription.
|
||||
type Subscription struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (s Subscription) String() string {
|
||||
return fmt.Sprintf(`Title="%s", URL="%s", Type="%s"`, s.Title, s.URL, s.Type)
|
||||
}
|
||||
|
||||
// Subscriptions represents a list of subscriptions.
|
||||
type Subscriptions []*Subscription
|
||||
|
||||
// Feed represents a Miniflux feed.
|
||||
type Feed struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
FeedURL string `json:"feed_url"`
|
||||
SiteURL string `json:"site_url"`
|
||||
Title string `json:"title"`
|
||||
CheckedAt time.Time `json:"checked_at,omitempty"`
|
||||
EtagHeader string `json:"etag_header,omitempty"`
|
||||
LastModifiedHeader string `json:"last_modified_header,omitempty"`
|
||||
ParsingErrorMsg string `json:"parsing_error_message,omitempty"`
|
||||
ParsingErrorCount int `json:"parsing_error_count,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
IgnoreHTTPCache bool `json:"ignore_http_cache"`
|
||||
FetchViaProxy bool `json:"fetch_via_proxy"`
|
||||
ScraperRules string `json:"scraper_rules"`
|
||||
RewriteRules string `json:"rewrite_rules"`
|
||||
BlocklistRules string `json:"blocklist_rules"`
|
||||
KeeplistRules string `json:"keeplist_rules"`
|
||||
Crawler bool `json:"crawler"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Category *Category `json:"category,omitempty"`
|
||||
}
|
||||
|
||||
// FeedCreationRequest represents the request to create a feed.
|
||||
type FeedCreationRequest struct {
|
||||
FeedURL string `json:"feed_url"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Crawler bool `json:"crawler"`
|
||||
Disabled bool `json:"disabled"`
|
||||
IgnoreHTTPCache bool `json:"ignore_http_cache"`
|
||||
FetchViaProxy bool `json:"fetch_via_proxy"`
|
||||
ScraperRules string `json:"scraper_rules"`
|
||||
RewriteRules string `json:"rewrite_rules"`
|
||||
BlocklistRules string `json:"blocklist_rules"`
|
||||
KeeplistRules string `json:"keeplist_rules"`
|
||||
}
|
||||
|
||||
// FeedModificationRequest represents the request to update a feed.
|
||||
type FeedModificationRequest struct {
|
||||
FeedURL *string `json:"feed_url"`
|
||||
SiteURL *string `json:"site_url"`
|
||||
Title *string `json:"title"`
|
||||
ScraperRules *string `json:"scraper_rules"`
|
||||
RewriteRules *string `json:"rewrite_rules"`
|
||||
BlocklistRules *string `json:"blocklist_rules"`
|
||||
KeeplistRules *string `json:"keeplist_rules"`
|
||||
Crawler *bool `json:"crawler"`
|
||||
UserAgent *string `json:"user_agent"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
CategoryID *int64 `json:"category_id"`
|
||||
Disabled *bool `json:"disabled"`
|
||||
IgnoreHTTPCache *bool `json:"ignore_http_cache"`
|
||||
FetchViaProxy *bool `json:"fetch_via_proxy"`
|
||||
}
|
||||
|
||||
// FeedIcon represents the feed icon.
|
||||
type FeedIcon struct {
|
||||
ID int64 `json:"id"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// Feeds represents a list of feeds.
|
||||
type Feeds []*Feed
|
||||
|
||||
// Entry represents a subscription item in the system.
|
||||
type Entry struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
FeedID int64 `json:"feed_id"`
|
||||
Status string `json:"status"`
|
||||
Hash string `json:"hash"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Date time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
ShareCode string `json:"share_code"`
|
||||
Starred bool `json:"starred"`
|
||||
ReadingTime int `json:"reading_time"`
|
||||
Enclosures Enclosures `json:"enclosures,omitempty"`
|
||||
Feed *Feed `json:"feed,omitempty"`
|
||||
}
|
||||
|
||||
// Entries represents a list of entries.
|
||||
type Entries []*Entry
|
||||
|
||||
// Enclosure represents an attachment.
|
||||
type Enclosure struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
EntryID int64 `json:"entry_id"`
|
||||
URL string `json:"url"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// Enclosures represents a list of attachments.
|
||||
type Enclosures []*Enclosure
|
||||
|
||||
// Filter is used to filter entries.
|
||||
type Filter struct {
|
||||
Status string
|
||||
Offset int
|
||||
Limit int
|
||||
Order string
|
||||
Direction string
|
||||
Starred bool
|
||||
Before int64
|
||||
After int64
|
||||
BeforeEntryID int64
|
||||
AfterEntryID int64
|
||||
Search string
|
||||
CategoryID int64
|
||||
FeedID int64
|
||||
Statuses []string
|
||||
}
|
||||
|
||||
// EntryResultSet represents the response when fetching entries.
|
||||
type EntryResultSet struct {
|
||||
Total int `json:"total"`
|
||||
Entries Entries `json:"entries"`
|
||||
}
|
||||
+3
-6
@@ -1,12 +1,10 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*
|
||||
|
||||
Package client implements a client library for the Miniflux REST API.
|
||||
|
||||
Examples
|
||||
# Examples
|
||||
|
||||
This code snippet fetch the list of users:
|
||||
|
||||
@@ -30,6 +28,5 @@ This one discover subscriptions on a website:
|
||||
return
|
||||
}
|
||||
fmt.Println(subscriptions)
|
||||
|
||||
*/
|
||||
package client // import "miniflux.app/client"
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/client"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Entry statuses.
|
||||
const (
|
||||
EntryStatusUnread = "unread"
|
||||
EntryStatusRead = "read"
|
||||
EntryStatusRemoved = "removed"
|
||||
)
|
||||
|
||||
// User represents a user in the system.
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
Theme string `json:"theme"`
|
||||
Language string `json:"language"`
|
||||
Timezone string `json:"timezone"`
|
||||
EntryDirection string `json:"entry_sorting_direction"`
|
||||
EntryOrder string `json:"entry_sorting_order"`
|
||||
Stylesheet string `json:"stylesheet"`
|
||||
GoogleID string `json:"google_id"`
|
||||
OpenIDConnectID string `json:"openid_connect_id"`
|
||||
EntriesPerPage int `json:"entries_per_page"`
|
||||
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime bool `json:"show_reading_time"`
|
||||
EntrySwipe bool `json:"entry_swipe"`
|
||||
GestureNav string `json:"gesture_nav"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
DisplayMode string `json:"display_mode"`
|
||||
DefaultReadingSpeed int `json:"default_reading_speed"`
|
||||
CJKReadingSpeed int `json:"cjk_reading_speed"`
|
||||
DefaultHomePage string `json:"default_home_page"`
|
||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
return fmt.Sprintf("#%d - %s (admin=%v)", u.ID, u.Username, u.IsAdmin)
|
||||
}
|
||||
|
||||
// UserCreationRequest represents the request to create a user.
|
||||
type UserCreationRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
GoogleID string `json:"google_id"`
|
||||
OpenIDConnectID string `json:"openid_connect_id"`
|
||||
}
|
||||
|
||||
// UserModificationRequest represents the request to update a user.
|
||||
type UserModificationRequest struct {
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
IsAdmin *bool `json:"is_admin"`
|
||||
Theme *string `json:"theme"`
|
||||
Language *string `json:"language"`
|
||||
Timezone *string `json:"timezone"`
|
||||
EntryDirection *string `json:"entry_sorting_direction"`
|
||||
EntryOrder *string `json:"entry_sorting_order"`
|
||||
Stylesheet *string `json:"stylesheet"`
|
||||
GoogleID *string `json:"google_id"`
|
||||
OpenIDConnectID *string `json:"openid_connect_id"`
|
||||
EntriesPerPage *int `json:"entries_per_page"`
|
||||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime *bool `json:"show_reading_time"`
|
||||
EntrySwipe *bool `json:"entry_swipe"`
|
||||
GestureNav *string `json:"gesture_nav"`
|
||||
DisplayMode *string `json:"display_mode"`
|
||||
DefaultReadingSpeed *int `json:"default_reading_speed"`
|
||||
CJKReadingSpeed *int `json:"cjk_reading_speed"`
|
||||
DefaultHomePage *string `json:"default_home_page"`
|
||||
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
||||
MarkReadOnView *bool `json:"mark_read_on_view"`
|
||||
}
|
||||
|
||||
// Users represents a list of users.
|
||||
type Users []User
|
||||
|
||||
// Category represents a feed category.
|
||||
type Category struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
func (c Category) String() string {
|
||||
return fmt.Sprintf("#%d %s", c.ID, c.Title)
|
||||
}
|
||||
|
||||
// Categories represents a list of categories.
|
||||
type Categories []*Category
|
||||
|
||||
// Subscription represents a feed subscription.
|
||||
type Subscription struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (s Subscription) String() string {
|
||||
return fmt.Sprintf(`Title="%s", URL="%s", Type="%s"`, s.Title, s.URL, s.Type)
|
||||
}
|
||||
|
||||
// Subscriptions represents a list of subscriptions.
|
||||
type Subscriptions []*Subscription
|
||||
|
||||
// Feed represents a Miniflux feed.
|
||||
type Feed struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
FeedURL string `json:"feed_url"`
|
||||
SiteURL string `json:"site_url"`
|
||||
Title string `json:"title"`
|
||||
CheckedAt time.Time `json:"checked_at,omitempty"`
|
||||
EtagHeader string `json:"etag_header,omitempty"`
|
||||
LastModifiedHeader string `json:"last_modified_header,omitempty"`
|
||||
ParsingErrorMsg string `json:"parsing_error_message,omitempty"`
|
||||
ParsingErrorCount int `json:"parsing_error_count,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
IgnoreHTTPCache bool `json:"ignore_http_cache"`
|
||||
AllowSelfSignedCertificates bool `json:"allow_self_signed_certificates"`
|
||||
FetchViaProxy bool `json:"fetch_via_proxy"`
|
||||
ScraperRules string `json:"scraper_rules"`
|
||||
RewriteRules string `json:"rewrite_rules"`
|
||||
BlocklistRules string `json:"blocklist_rules"`
|
||||
KeeplistRules string `json:"keeplist_rules"`
|
||||
Crawler bool `json:"crawler"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Cookie string `json:"cookie"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Category *Category `json:"category,omitempty"`
|
||||
HideGlobally bool `json:"hide_globally"`
|
||||
}
|
||||
|
||||
// FeedCreationRequest represents the request to create a feed.
|
||||
type FeedCreationRequest struct {
|
||||
FeedURL string `json:"feed_url"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Cookie string `json:"cookie"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Crawler bool `json:"crawler"`
|
||||
Disabled bool `json:"disabled"`
|
||||
IgnoreHTTPCache bool `json:"ignore_http_cache"`
|
||||
AllowSelfSignedCertificates bool `json:"allow_self_signed_certificates"`
|
||||
FetchViaProxy bool `json:"fetch_via_proxy"`
|
||||
ScraperRules string `json:"scraper_rules"`
|
||||
RewriteRules string `json:"rewrite_rules"`
|
||||
BlocklistRules string `json:"blocklist_rules"`
|
||||
KeeplistRules string `json:"keeplist_rules"`
|
||||
HideGlobally bool `json:"hide_globally"`
|
||||
}
|
||||
|
||||
// FeedModificationRequest represents the request to update a feed.
|
||||
type FeedModificationRequest struct {
|
||||
FeedURL *string `json:"feed_url"`
|
||||
SiteURL *string `json:"site_url"`
|
||||
Title *string `json:"title"`
|
||||
ScraperRules *string `json:"scraper_rules"`
|
||||
RewriteRules *string `json:"rewrite_rules"`
|
||||
BlocklistRules *string `json:"blocklist_rules"`
|
||||
KeeplistRules *string `json:"keeplist_rules"`
|
||||
Crawler *bool `json:"crawler"`
|
||||
UserAgent *string `json:"user_agent"`
|
||||
Cookie *string `json:"cookie"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
CategoryID *int64 `json:"category_id"`
|
||||
Disabled *bool `json:"disabled"`
|
||||
IgnoreHTTPCache *bool `json:"ignore_http_cache"`
|
||||
AllowSelfSignedCertificates *bool `json:"allow_self_signed_certificates"`
|
||||
FetchViaProxy *bool `json:"fetch_via_proxy"`
|
||||
HideGlobally *bool `json:"hide_globally"`
|
||||
}
|
||||
|
||||
// FeedIcon represents the feed icon.
|
||||
type FeedIcon struct {
|
||||
ID int64 `json:"id"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type FeedCounters struct {
|
||||
ReadCounters map[int64]int `json:"reads"`
|
||||
UnreadCounters map[int64]int `json:"unreads"`
|
||||
}
|
||||
|
||||
// Feeds represents a list of feeds.
|
||||
type Feeds []*Feed
|
||||
|
||||
// Entry represents a subscription item in the system.
|
||||
type Entry struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
FeedID int64 `json:"feed_id"`
|
||||
Status string `json:"status"`
|
||||
Hash string `json:"hash"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
CommentsURL string `json:"comments_url"`
|
||||
Date time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ChangedAt time.Time `json:"changed_at"`
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
ShareCode string `json:"share_code"`
|
||||
Starred bool `json:"starred"`
|
||||
ReadingTime int `json:"reading_time"`
|
||||
Enclosures Enclosures `json:"enclosures,omitempty"`
|
||||
Feed *Feed `json:"feed,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// Entries represents a list of entries.
|
||||
type Entries []*Entry
|
||||
|
||||
// Enclosure represents an attachment.
|
||||
type Enclosure struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
EntryID int64 `json:"entry_id"`
|
||||
URL string `json:"url"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// Enclosures represents a list of attachments.
|
||||
type Enclosures []*Enclosure
|
||||
|
||||
const (
|
||||
FilterNotStarred = "0"
|
||||
FilterOnlyStarred = "1"
|
||||
)
|
||||
|
||||
// Filter is used to filter entries.
|
||||
type Filter struct {
|
||||
Status string
|
||||
Offset int
|
||||
Limit int
|
||||
Order string
|
||||
Direction string
|
||||
Starred string
|
||||
Before int64
|
||||
After int64
|
||||
BeforeEntryID int64
|
||||
AfterEntryID int64
|
||||
Search string
|
||||
CategoryID int64
|
||||
FeedID int64
|
||||
Statuses []string
|
||||
}
|
||||
|
||||
// EntryResultSet represents the response when fetching entries.
|
||||
type EntryResultSet struct {
|
||||
Total int `json:"total"`
|
||||
Entries Entries `json:"entries"`
|
||||
}
|
||||
+14
-9
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/client"
|
||||
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -83,11 +81,11 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
switch data.(type) {
|
||||
switch data := data.(type) {
|
||||
case io.ReadCloser:
|
||||
request.Body = data.(io.ReadCloser)
|
||||
request.Body = data
|
||||
default:
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(r.toJSON(data)))
|
||||
request.Body = io.NopCloser(bytes.NewBuffer(r.toJSON(data)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +103,15 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
|
||||
response.Body.Close()
|
||||
return nil, ErrForbidden
|
||||
case http.StatusInternalServerError:
|
||||
response.Body.Close()
|
||||
return nil, ErrServerError
|
||||
defer response.Body.Close()
|
||||
|
||||
var resp errorResponse
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
// If we failed to decode, just return a generic ErrServerError
|
||||
if err := decoder.Decode(&resp); err != nil {
|
||||
return nil, ErrServerError
|
||||
}
|
||||
return nil, errors.New("miniflux: internal server error: " + resp.ErrorMessage)
|
||||
case http.StatusNotFound:
|
||||
response.Body.Close()
|
||||
return nil, ErrNotFound
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/config"
|
||||
|
||||
|
||||
+286
-49
@@ -1,11 +1,9 @@
|
||||
// Copyright 2019 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/config"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
@@ -409,41 +407,6 @@ func TestDefaultCertDomainValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertCache(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("CERT_CACHE", "foobar")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := "foobar"
|
||||
result := opts.CertCache()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultCertCacheValue(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := defaultCertCache
|
||||
result := opts.CertCache()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultCleanupFrequencyHoursValue(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
@@ -761,6 +724,24 @@ func TestDefautSchedulerCountBasedMinInterval(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollingParsingErrorLimit(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("POLLING_PARSING_ERROR_LIMIT", "100")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := 100
|
||||
result := opts.PollingParsingErrorLimit()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected POLLING_SCHEDULER value, got %v instead of %v`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuth2UserCreationWhenUnset(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
@@ -1181,7 +1162,97 @@ func TestPocketConsumerKeyFromUserPrefs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyImages(t *testing.T) {
|
||||
func TestProxyOption(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("PROXY_OPTION", "all")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := "all"
|
||||
result := opts.ProxyOption()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProxyOptionValue(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := defaultProxyOption
|
||||
result := opts.ProxyOption()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyMediaTypes(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("PROXY_MEDIA_TYPES", "image,audio")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := []string{"audio", "image"}
|
||||
|
||||
if len(expected) != len(opts.ProxyMediaTypes()) {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
|
||||
resultMap := make(map[string]bool)
|
||||
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||
resultMap[mediaType] = true
|
||||
}
|
||||
|
||||
for _, mediaType := range expected {
|
||||
if !resultMap[mediaType] {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyMediaTypesWithDuplicatedValues(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("PROXY_MEDIA_TYPES", "image,audio, image")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := []string{"audio", "image"}
|
||||
if len(expected) != len(opts.ProxyMediaTypes()) {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
|
||||
resultMap := make(map[string]bool)
|
||||
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||
resultMap[mediaType] = true
|
||||
}
|
||||
|
||||
for _, mediaType := range expected {
|
||||
if !resultMap[mediaType] {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyImagesOptionBackwardCompatibility(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("PROXY_IMAGES", "all")
|
||||
|
||||
@@ -1191,15 +1262,30 @@ func TestProxyImages(t *testing.T) {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := "all"
|
||||
result := opts.ProxyImages()
|
||||
expected := []string{"image"}
|
||||
if len(expected) != len(opts.ProxyMediaTypes()) {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
|
||||
resultMap := make(map[string]bool)
|
||||
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||
resultMap[mediaType] = true
|
||||
}
|
||||
|
||||
for _, mediaType := range expected {
|
||||
if !resultMap[mediaType] {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
expectedProxyOption := "all"
|
||||
result := opts.ProxyOption()
|
||||
if result != expectedProxyOption {
|
||||
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expectedProxyOption)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProxyImagesValue(t *testing.T) {
|
||||
func TestDefaultProxyMediaTypes(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
parser := NewParser()
|
||||
@@ -1208,11 +1294,56 @@ func TestDefaultProxyImagesValue(t *testing.T) {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := defaultProxyImages
|
||||
result := opts.ProxyImages()
|
||||
expected := []string{"image"}
|
||||
|
||||
if len(expected) != len(opts.ProxyMediaTypes()) {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
|
||||
resultMap := make(map[string]bool)
|
||||
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||
resultMap[mediaType] = true
|
||||
}
|
||||
|
||||
for _, mediaType := range expected {
|
||||
if !resultMap[mediaType] {
|
||||
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyHTTPClientTimeout(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := 24
|
||||
result := opts.ProxyHTTPClientTimeout()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
|
||||
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProxyHTTPClientTimeoutValue(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := defaultProxyHTTPClientTimeout
|
||||
result := opts.ProxyHTTPClientTimeout()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,6 +1446,41 @@ func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPServerTimeout(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("HTTP_SERVER_TIMEOUT", "342")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := 342
|
||||
result := opts.HTTPServerTimeout()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultHTTPServerTimeoutValue(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := defaultHTTPServerTimeout
|
||||
result := opts.HTTPServerTimeout()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
content := []byte(`
|
||||
# This is a comment
|
||||
@@ -1326,7 +1492,7 @@ DEBUG = yes
|
||||
Invalid text
|
||||
`)
|
||||
|
||||
tmpfile, err := ioutil.TempFile(".", "miniflux.*.unit_test.conf")
|
||||
tmpfile, err := os.CreateTemp(".", "miniflux.*.unit_test.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1431,3 +1597,74 @@ func TestAuthProxyUserCreationAdmin(t *testing.T) {
|
||||
t.Fatalf(`Unexpected AUTH_PROXY_USER_CREATION value, got %v instead of %v`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchYouTubeWatchTime(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("FETCH_YOUTUBE_WATCH_TIME", "1")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := true
|
||||
result := opts.FetchYouTubeWatchTime()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected FETCH_YOUTUBE_WATCH_TIME value, got %v instead of %v`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYouTubeEmbedUrlOverride(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
||||
|
||||
parser := NewParser()
|
||||
opts, err := parser.ParseEnvironmentVariables()
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
expected := "https://invidious.custom/embed/"
|
||||
result := opts.YouTubeEmbedUrlOverride()
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf(`Unexpected YOUTUBE_EMBED_URL_OVERRIDE value, got %v instead of %v`, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigDumpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
|
||||
wantOpts := NewOptions()
|
||||
wantOpts.adminUsername = "my-username"
|
||||
|
||||
serialized := wantOpts.String()
|
||||
tmpfile, err := os.CreateTemp(".", "miniflux.*.unit_test.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpfile.Write([]byte(serialized)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parsedOpts, err := parser.ParseFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
t.Errorf(`Parsing failure: %v`, err)
|
||||
}
|
||||
|
||||
if parsedOpts.AdminUsername() != wantOpts.AdminUsername() {
|
||||
t.Fatalf(`Unexpected ADMIN_USERNAME value, got %q instead of %q`, parsedOpts.AdminUsername(), wantOpts.AdminUsername())
|
||||
}
|
||||
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.Remove(tmpfile.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2019 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package config handles configuration management for the application.
|
||||
|
||||
*/
|
||||
package config // import "miniflux.app/config"
|
||||
+227
-69
@@ -1,12 +1,14 @@
|
||||
// Copyright 2019 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/config"
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"miniflux.app/version"
|
||||
)
|
||||
@@ -24,24 +26,31 @@ const (
|
||||
defaultBasePath = ""
|
||||
defaultWorkerPoolSize = 5
|
||||
defaultPollingFrequency = 60
|
||||
defaultBatchSize = 10
|
||||
defaultBatchSize = 100
|
||||
defaultPollingScheduler = "round_robin"
|
||||
defaultSchedulerEntryFrequencyMinInterval = 5
|
||||
defaultSchedulerEntryFrequencyMaxInterval = 24 * 60
|
||||
defaultPollingParsingErrorLimit = 3
|
||||
defaultRunMigrations = false
|
||||
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
|
||||
defaultDatabaseMaxConns = 20
|
||||
defaultDatabaseMinConns = 1
|
||||
defaultDatabaseConnectionLifetime = 5
|
||||
defaultListenAddr = "127.0.0.1:8080"
|
||||
defaultCertFile = ""
|
||||
defaultKeyFile = ""
|
||||
defaultCertDomain = ""
|
||||
defaultCertCache = "/tmp/cert_cache"
|
||||
defaultCleanupFrequencyHours = 24
|
||||
defaultCleanupArchiveReadDays = 60
|
||||
defaultCleanupArchiveUnreadDays = 180
|
||||
defaultCleanupArchiveBatchSize = 10000
|
||||
defaultCleanupRemoveSessionsDays = 30
|
||||
defaultProxyImages = "http-only"
|
||||
defaultProxyHTTPClientTimeout = 120
|
||||
defaultProxyOption = "http-only"
|
||||
defaultProxyMediaTypes = "image"
|
||||
defaultProxyUrl = ""
|
||||
defaultFetchYouTubeWatchTime = false
|
||||
defaultYouTubeEmbedUrlOverride = "https://www.youtube-nocookie.com/embed/"
|
||||
defaultCreateAdmin = false
|
||||
defaultAdminUsername = ""
|
||||
defaultAdminPassword = ""
|
||||
@@ -55,6 +64,7 @@ const (
|
||||
defaultHTTPClientTimeout = 20
|
||||
defaultHTTPClientMaxBodySize = 15
|
||||
defaultHTTPClientProxy = ""
|
||||
defaultHTTPServerTimeout = 300
|
||||
defaultAuthProxyHeader = ""
|
||||
defaultAuthProxyUserCreation = false
|
||||
defaultMaintenanceMode = false
|
||||
@@ -62,10 +72,20 @@ const (
|
||||
defaultMetricsCollector = false
|
||||
defaultMetricsRefreshInterval = 60
|
||||
defaultMetricsAllowedNetworks = "127.0.0.1/8"
|
||||
defaultMetricsUsername = ""
|
||||
defaultMetricsPassword = ""
|
||||
defaultWatchdog = true
|
||||
defaultInvidiousInstance = "yewtu.be"
|
||||
)
|
||||
|
||||
var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
|
||||
|
||||
// Option contains a key to value map of a single option. It may be used to output debug strings.
|
||||
type Option struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Options contains configuration options.
|
||||
type Options struct {
|
||||
HTTPS bool
|
||||
@@ -81,26 +101,33 @@ type Options struct {
|
||||
databaseURL string
|
||||
databaseMaxConns int
|
||||
databaseMinConns int
|
||||
databaseConnectionLifetime int
|
||||
runMigrations bool
|
||||
listenAddr string
|
||||
certFile string
|
||||
certDomain string
|
||||
certCache string
|
||||
certKeyFile string
|
||||
cleanupFrequencyHours int
|
||||
cleanupArchiveReadDays int
|
||||
cleanupArchiveUnreadDays int
|
||||
cleanupArchiveBatchSize int
|
||||
cleanupRemoveSessionsDays int
|
||||
pollingFrequency int
|
||||
batchSize int
|
||||
pollingScheduler string
|
||||
schedulerEntryFrequencyMinInterval int
|
||||
schedulerEntryFrequencyMaxInterval int
|
||||
pollingParsingErrorLimit int
|
||||
workerPoolSize int
|
||||
createAdmin bool
|
||||
adminUsername string
|
||||
adminPassword string
|
||||
proxyImages string
|
||||
proxyHTTPClientTimeout int
|
||||
proxyOption string
|
||||
proxyMediaTypes []string
|
||||
proxyUrl string
|
||||
fetchYouTubeWatchTime bool
|
||||
youTubeEmbedUrlOverride string
|
||||
oauth2UserCreationAllowed bool
|
||||
oauth2ClientID string
|
||||
oauth2ClientSecret string
|
||||
@@ -112,6 +139,7 @@ type Options struct {
|
||||
httpClientMaxBodySize int64
|
||||
httpClientProxy string
|
||||
httpClientUserAgent string
|
||||
httpServerTimeout int
|
||||
authProxyHeader string
|
||||
authProxyUserCreation bool
|
||||
maintenanceMode bool
|
||||
@@ -119,10 +147,18 @@ type Options struct {
|
||||
metricsCollector bool
|
||||
metricsRefreshInterval int
|
||||
metricsAllowedNetworks []string
|
||||
metricsUsername string
|
||||
metricsPassword string
|
||||
watchdog bool
|
||||
invidiousInstance string
|
||||
proxyPrivateKey []byte
|
||||
}
|
||||
|
||||
// NewOptions returns Options with default values.
|
||||
func NewOptions() *Options {
|
||||
randomKey := make([]byte, 16)
|
||||
rand.Read(randomKey)
|
||||
|
||||
return &Options{
|
||||
HTTPS: defaultHTTPS,
|
||||
logDateTime: defaultLogDateTime,
|
||||
@@ -137,24 +173,31 @@ func NewOptions() *Options {
|
||||
databaseURL: defaultDatabaseURL,
|
||||
databaseMaxConns: defaultDatabaseMaxConns,
|
||||
databaseMinConns: defaultDatabaseMinConns,
|
||||
databaseConnectionLifetime: defaultDatabaseConnectionLifetime,
|
||||
runMigrations: defaultRunMigrations,
|
||||
listenAddr: defaultListenAddr,
|
||||
certFile: defaultCertFile,
|
||||
certDomain: defaultCertDomain,
|
||||
certCache: defaultCertCache,
|
||||
certKeyFile: defaultKeyFile,
|
||||
cleanupFrequencyHours: defaultCleanupFrequencyHours,
|
||||
cleanupArchiveReadDays: defaultCleanupArchiveReadDays,
|
||||
cleanupArchiveUnreadDays: defaultCleanupArchiveUnreadDays,
|
||||
cleanupArchiveBatchSize: defaultCleanupArchiveBatchSize,
|
||||
cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays,
|
||||
pollingFrequency: defaultPollingFrequency,
|
||||
batchSize: defaultBatchSize,
|
||||
pollingScheduler: defaultPollingScheduler,
|
||||
schedulerEntryFrequencyMinInterval: defaultSchedulerEntryFrequencyMinInterval,
|
||||
schedulerEntryFrequencyMaxInterval: defaultSchedulerEntryFrequencyMaxInterval,
|
||||
pollingParsingErrorLimit: defaultPollingParsingErrorLimit,
|
||||
workerPoolSize: defaultWorkerPoolSize,
|
||||
createAdmin: defaultCreateAdmin,
|
||||
proxyImages: defaultProxyImages,
|
||||
proxyHTTPClientTimeout: defaultProxyHTTPClientTimeout,
|
||||
proxyOption: defaultProxyOption,
|
||||
proxyMediaTypes: []string{defaultProxyMediaTypes},
|
||||
proxyUrl: defaultProxyUrl,
|
||||
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
|
||||
youTubeEmbedUrlOverride: defaultYouTubeEmbedUrlOverride,
|
||||
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
|
||||
oauth2ClientID: defaultOAuth2ClientID,
|
||||
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
||||
@@ -166,6 +209,7 @@ func NewOptions() *Options {
|
||||
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
|
||||
httpClientProxy: defaultHTTPClientProxy,
|
||||
httpClientUserAgent: defaultHTTPClientUserAgent,
|
||||
httpServerTimeout: defaultHTTPServerTimeout,
|
||||
authProxyHeader: defaultAuthProxyHeader,
|
||||
authProxyUserCreation: defaultAuthProxyUserCreation,
|
||||
maintenanceMode: defaultMaintenanceMode,
|
||||
@@ -173,6 +217,11 @@ func NewOptions() *Options {
|
||||
metricsCollector: defaultMetricsCollector,
|
||||
metricsRefreshInterval: defaultMetricsRefreshInterval,
|
||||
metricsAllowedNetworks: []string{defaultMetricsAllowedNetworks},
|
||||
metricsUsername: defaultMetricsUsername,
|
||||
metricsPassword: defaultMetricsPassword,
|
||||
watchdog: defaultWatchdog,
|
||||
invidiousInstance: defaultInvidiousInstance,
|
||||
proxyPrivateKey: randomKey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +285,11 @@ func (o *Options) DatabaseMinConns() int {
|
||||
return o.databaseMinConns
|
||||
}
|
||||
|
||||
// DatabaseConnectionLifetime returns the maximum amount of time a connection may be reused.
|
||||
func (o *Options) DatabaseConnectionLifetime() time.Duration {
|
||||
return time.Duration(o.databaseConnectionLifetime) * time.Minute
|
||||
}
|
||||
|
||||
// ListenAddr returns the listen address for the HTTP server.
|
||||
func (o *Options) ListenAddr() string {
|
||||
return o.listenAddr
|
||||
@@ -256,11 +310,6 @@ func (o *Options) CertDomain() string {
|
||||
return o.certDomain
|
||||
}
|
||||
|
||||
// CertCache returns the directory to use for Let's Encrypt session cache.
|
||||
func (o *Options) CertCache() string {
|
||||
return o.certCache
|
||||
}
|
||||
|
||||
// CleanupFrequencyHours returns the interval in hours for cleanup jobs.
|
||||
func (o *Options) CleanupFrequencyHours() int {
|
||||
return o.cleanupFrequencyHours
|
||||
@@ -276,6 +325,11 @@ func (o *Options) CleanupArchiveUnreadDays() int {
|
||||
return o.cleanupArchiveUnreadDays
|
||||
}
|
||||
|
||||
// CleanupArchiveBatchSize returns the number of entries to archive for each interval.
|
||||
func (o *Options) CleanupArchiveBatchSize() int {
|
||||
return o.cleanupArchiveBatchSize
|
||||
}
|
||||
|
||||
// CleanupRemoveSessionsDays returns the number of days after which to remove sessions.
|
||||
func (o *Options) CleanupRemoveSessionsDays() int {
|
||||
return o.cleanupRemoveSessionsDays
|
||||
@@ -311,6 +365,11 @@ func (o *Options) SchedulerEntryFrequencyMinInterval() int {
|
||||
return o.schedulerEntryFrequencyMinInterval
|
||||
}
|
||||
|
||||
// PollingParsingErrorLimit returns the limit of errors when to stop polling.
|
||||
func (o *Options) PollingParsingErrorLimit() int {
|
||||
return o.pollingParsingErrorLimit
|
||||
}
|
||||
|
||||
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
|
||||
func (o *Options) IsOAuth2UserCreationAllowed() bool {
|
||||
return o.oauth2UserCreationAllowed
|
||||
@@ -366,9 +425,35 @@ func (o *Options) AdminPassword() string {
|
||||
return o.adminPassword
|
||||
}
|
||||
|
||||
// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
|
||||
func (o *Options) ProxyImages() string {
|
||||
return o.proxyImages
|
||||
// FetchYouTubeWatchTime returns true if the YouTube video duration
|
||||
// should be fetched and used as a reading time.
|
||||
func (o *Options) FetchYouTubeWatchTime() bool {
|
||||
return o.fetchYouTubeWatchTime
|
||||
}
|
||||
|
||||
// YouTubeEmbedUrlOverride returns YouTube URL which will be used for embeds
|
||||
func (o *Options) YouTubeEmbedUrlOverride() string {
|
||||
return o.youTubeEmbedUrlOverride
|
||||
}
|
||||
|
||||
// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
|
||||
func (o *Options) ProxyOption() string {
|
||||
return o.proxyOption
|
||||
}
|
||||
|
||||
// ProxyMediaTypes returns a slice of media types to proxy.
|
||||
func (o *Options) ProxyMediaTypes() []string {
|
||||
return o.proxyMediaTypes
|
||||
}
|
||||
|
||||
// ProxyUrl returns a string of a URL to use to proxy image requests
|
||||
func (o *Options) ProxyUrl() string {
|
||||
return o.proxyUrl
|
||||
}
|
||||
|
||||
// ProxyHTTPClientTimeout returns the time limit in seconds before the proxy HTTP client cancel the request.
|
||||
func (o *Options) ProxyHTTPClientTimeout() int {
|
||||
return o.proxyHTTPClientTimeout
|
||||
}
|
||||
|
||||
// HasHTTPService returns true if the HTTP service is enabled.
|
||||
@@ -404,6 +489,11 @@ func (o *Options) HTTPClientProxy() string {
|
||||
return o.httpClientProxy
|
||||
}
|
||||
|
||||
// HTTPServerTimeout returns the time limit in seconds before the HTTP server cancel the request.
|
||||
func (o *Options) HTTPServerTimeout() int {
|
||||
return o.httpServerTimeout
|
||||
}
|
||||
|
||||
// HasHTTPClientProxyConfigured returns true if the HTTP proxy is configured.
|
||||
func (o *Options) HasHTTPClientProxyConfigured() bool {
|
||||
return o.httpClientProxy != ""
|
||||
@@ -436,63 +526,131 @@ func (o *Options) MetricsAllowedNetworks() []string {
|
||||
return o.metricsAllowedNetworks
|
||||
}
|
||||
|
||||
func (o *Options) MetricsUsername() string {
|
||||
return o.metricsUsername
|
||||
}
|
||||
|
||||
func (o *Options) MetricsPassword() string {
|
||||
return o.metricsPassword
|
||||
}
|
||||
|
||||
// HTTPClientUserAgent returns the global User-Agent header for miniflux.
|
||||
func (o *Options) HTTPClientUserAgent() string {
|
||||
return o.httpClientUserAgent
|
||||
}
|
||||
|
||||
// HasWatchdog returns true if the systemd watchdog is enabled.
|
||||
func (o *Options) HasWatchdog() bool {
|
||||
return o.watchdog
|
||||
}
|
||||
|
||||
// InvidiousInstance returns the invidious instance used by miniflux
|
||||
func (o *Options) InvidiousInstance() string {
|
||||
return o.invidiousInstance
|
||||
}
|
||||
|
||||
// ProxyPrivateKey returns the private key used by the media proxy
|
||||
func (o *Options) ProxyPrivateKey() []byte {
|
||||
return o.proxyPrivateKey
|
||||
}
|
||||
|
||||
// SortedOptions returns options as a list of key value pairs, sorted by keys.
|
||||
func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||
var keyValues = map[string]interface{}{
|
||||
"ADMIN_PASSWORD": redactSecretValue(o.adminPassword, redactSecret),
|
||||
"ADMIN_USERNAME": o.adminUsername,
|
||||
"AUTH_PROXY_HEADER": o.authProxyHeader,
|
||||
"AUTH_PROXY_USER_CREATION": o.authProxyUserCreation,
|
||||
"BASE_PATH": o.basePath,
|
||||
"BASE_URL": o.baseURL,
|
||||
"BATCH_SIZE": o.batchSize,
|
||||
"CERT_DOMAIN": o.certDomain,
|
||||
"CERT_FILE": o.certFile,
|
||||
"CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
|
||||
"CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays,
|
||||
"CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays,
|
||||
"CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours,
|
||||
"CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays,
|
||||
"CREATE_ADMIN": o.createAdmin,
|
||||
"DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
|
||||
"DATABASE_MAX_CONNS": o.databaseMaxConns,
|
||||
"DATABASE_MIN_CONNS": o.databaseMinConns,
|
||||
"DATABASE_URL": redactSecretValue(o.databaseURL, redactSecret),
|
||||
"DEBUG": o.debug,
|
||||
"DISABLE_HSTS": !o.hsts,
|
||||
"DISABLE_HTTP_SERVICE": !o.httpService,
|
||||
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
|
||||
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
|
||||
"HTTPS": o.HTTPS,
|
||||
"HTTP_CLIENT_MAX_BODY_SIZE": o.httpClientMaxBodySize,
|
||||
"HTTP_CLIENT_PROXY": o.httpClientProxy,
|
||||
"HTTP_CLIENT_TIMEOUT": o.httpClientTimeout,
|
||||
"HTTP_CLIENT_USER_AGENT": o.httpClientUserAgent,
|
||||
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
|
||||
"HTTP_SERVICE": o.httpService,
|
||||
"INVIDIOUS_INSTANCE": o.invidiousInstance,
|
||||
"KEY_FILE": o.certKeyFile,
|
||||
"LISTEN_ADDR": o.listenAddr,
|
||||
"LOG_DATE_TIME": o.logDateTime,
|
||||
"MAINTENANCE_MESSAGE": o.maintenanceMessage,
|
||||
"MAINTENANCE_MODE": o.maintenanceMode,
|
||||
"METRICS_ALLOWED_NETWORKS": strings.Join(o.metricsAllowedNetworks, ","),
|
||||
"METRICS_COLLECTOR": o.metricsCollector,
|
||||
"METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
|
||||
"METRICS_REFRESH_INTERVAL": o.metricsRefreshInterval,
|
||||
"METRICS_USERNAME": o.metricsUsername,
|
||||
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
|
||||
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
|
||||
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
|
||||
"OAUTH2_PROVIDER": o.oauth2Provider,
|
||||
"OAUTH2_REDIRECT_URL": o.oauth2RedirectURL,
|
||||
"OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed,
|
||||
"POCKET_CONSUMER_KEY": redactSecretValue(o.pocketConsumerKey, redactSecret),
|
||||
"POLLING_FREQUENCY": o.pollingFrequency,
|
||||
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
|
||||
"POLLING_SCHEDULER": o.pollingScheduler,
|
||||
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
|
||||
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
|
||||
"PROXY_OPTION": o.proxyOption,
|
||||
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
|
||||
"PROXY_URL": o.proxyUrl,
|
||||
"ROOT_URL": o.rootURL,
|
||||
"RUN_MIGRATIONS": o.runMigrations,
|
||||
"SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL": o.schedulerEntryFrequencyMaxInterval,
|
||||
"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
|
||||
"SCHEDULER_SERVICE": o.schedulerService,
|
||||
"SERVER_TIMING_HEADER": o.serverTimingHeader,
|
||||
"WATCHDOG": o.watchdog,
|
||||
"WORKER_POOL_SIZE": o.workerPoolSize,
|
||||
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(keyValues))
|
||||
for key := range keyValues {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var sortedOptions []*Option
|
||||
for _, key := range keys {
|
||||
sortedOptions = append(sortedOptions, &Option{Key: key, Value: keyValues[key]})
|
||||
}
|
||||
return sortedOptions
|
||||
}
|
||||
|
||||
func (o *Options) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("LOG_DATE_TIME: %v\n", o.logDateTime))
|
||||
builder.WriteString(fmt.Sprintf("DEBUG: %v\n", o.debug))
|
||||
builder.WriteString(fmt.Sprintf("SERVER_TIMING_HEADER: %v\n", o.serverTimingHeader))
|
||||
builder.WriteString(fmt.Sprintf("HTTP_SERVICE: %v\n", o.httpService))
|
||||
builder.WriteString(fmt.Sprintf("SCHEDULER_SERVICE: %v\n", o.schedulerService))
|
||||
builder.WriteString(fmt.Sprintf("HTTPS: %v\n", o.HTTPS))
|
||||
builder.WriteString(fmt.Sprintf("HSTS: %v\n", o.hsts))
|
||||
builder.WriteString(fmt.Sprintf("BASE_URL: %v\n", o.baseURL))
|
||||
builder.WriteString(fmt.Sprintf("ROOT_URL: %v\n", o.rootURL))
|
||||
builder.WriteString(fmt.Sprintf("BASE_PATH: %v\n", o.basePath))
|
||||
builder.WriteString(fmt.Sprintf("LISTEN_ADDR: %v\n", o.listenAddr))
|
||||
builder.WriteString(fmt.Sprintf("DATABASE_URL: %v\n", o.databaseURL))
|
||||
builder.WriteString(fmt.Sprintf("DATABASE_MAX_CONNS: %v\n", o.databaseMaxConns))
|
||||
builder.WriteString(fmt.Sprintf("DATABASE_MIN_CONNS: %v\n", o.databaseMinConns))
|
||||
builder.WriteString(fmt.Sprintf("RUN_MIGRATIONS: %v\n", o.runMigrations))
|
||||
builder.WriteString(fmt.Sprintf("CERT_FILE: %v\n", o.certFile))
|
||||
builder.WriteString(fmt.Sprintf("KEY_FILE: %v\n", o.certKeyFile))
|
||||
builder.WriteString(fmt.Sprintf("CERT_DOMAIN: %v\n", o.certDomain))
|
||||
builder.WriteString(fmt.Sprintf("CERT_CACHE: %v\n", o.certCache))
|
||||
builder.WriteString(fmt.Sprintf("CLEANUP_FREQUENCY_HOURS: %v\n", o.cleanupFrequencyHours))
|
||||
builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_READ_DAYS: %v\n", o.cleanupArchiveReadDays))
|
||||
builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_UNREAD_DAYS: %v\n", o.cleanupArchiveUnreadDays))
|
||||
builder.WriteString(fmt.Sprintf("CLEANUP_REMOVE_SESSIONS_DAYS: %v\n", o.cleanupRemoveSessionsDays))
|
||||
builder.WriteString(fmt.Sprintf("WORKER_POOL_SIZE: %v\n", o.workerPoolSize))
|
||||
builder.WriteString(fmt.Sprintf("POLLING_FREQUENCY: %v\n", o.pollingFrequency))
|
||||
builder.WriteString(fmt.Sprintf("BATCH_SIZE: %v\n", o.batchSize))
|
||||
builder.WriteString(fmt.Sprintf("POLLING_SCHEDULER: %v\n", o.pollingScheduler))
|
||||
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL: %v\n", o.schedulerEntryFrequencyMaxInterval))
|
||||
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval))
|
||||
builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages))
|
||||
builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin))
|
||||
builder.WriteString(fmt.Sprintf("ADMIN_USERNAME: %v\n", o.adminUsername))
|
||||
builder.WriteString(fmt.Sprintf("ADMIN_PASSWORD: %v\n", o.adminPassword))
|
||||
builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_OIDC_DISCOVERY_ENDPOINT: %v\n", o.oauth2OidcDiscoveryEndpoint))
|
||||
builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider))
|
||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout))
|
||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize))
|
||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_PROXY: %v\n", o.httpClientProxy))
|
||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_USER_AGENT: %v\n", o.httpClientUserAgent))
|
||||
builder.WriteString(fmt.Sprintf("AUTH_PROXY_HEADER: %v\n", o.authProxyHeader))
|
||||
builder.WriteString(fmt.Sprintf("AUTH_PROXY_USER_CREATION: %v\n", o.authProxyUserCreation))
|
||||
builder.WriteString(fmt.Sprintf("MAINTENANCE_MODE: %v\n", o.maintenanceMode))
|
||||
builder.WriteString(fmt.Sprintf("MAINTENANCE_MESSAGE: %v\n", o.maintenanceMessage))
|
||||
builder.WriteString(fmt.Sprintf("METRICS_COLLECTOR: %v\n", o.metricsCollector))
|
||||
builder.WriteString(fmt.Sprintf("METRICS_REFRESH_INTERVAL: %v\n", o.metricsRefreshInterval))
|
||||
builder.WriteString(fmt.Sprintf("METRICS_ALLOWED_NETWORKS: %v\n", o.metricsAllowedNetworks))
|
||||
|
||||
for _, option := range o.SortedOptions(false) {
|
||||
fmt.Fprintf(&builder, "%s=%v\n", option.Key, option.Value)
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func redactSecretValue(value string, redactSecret bool) string {
|
||||
if redactSecret && value != "" {
|
||||
return "<secret>"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
+63
-11
@@ -1,16 +1,15 @@
|
||||
// Copyright 2019 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/config"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
url_parser "net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -96,6 +95,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||
p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
|
||||
case "DATABASE_MIN_CONNS":
|
||||
p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
|
||||
case "DATABASE_CONNECTION_LIFETIME":
|
||||
p.opts.databaseConnectionLifetime = parseInt(value, defaultDatabaseConnectionLifetime)
|
||||
case "RUN_MIGRATIONS":
|
||||
p.opts.runMigrations = parseBool(value, defaultRunMigrations)
|
||||
case "DISABLE_HSTS":
|
||||
@@ -112,14 +113,14 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||
p.opts.certKeyFile = parseString(value, defaultKeyFile)
|
||||
case "CERT_DOMAIN":
|
||||
p.opts.certDomain = parseString(value, defaultCertDomain)
|
||||
case "CERT_CACHE":
|
||||
p.opts.certCache = parseString(value, defaultCertCache)
|
||||
case "CLEANUP_FREQUENCY_HOURS":
|
||||
p.opts.cleanupFrequencyHours = parseInt(value, defaultCleanupFrequencyHours)
|
||||
case "CLEANUP_ARCHIVE_READ_DAYS":
|
||||
p.opts.cleanupArchiveReadDays = parseInt(value, defaultCleanupArchiveReadDays)
|
||||
case "CLEANUP_ARCHIVE_UNREAD_DAYS":
|
||||
p.opts.cleanupArchiveUnreadDays = parseInt(value, defaultCleanupArchiveUnreadDays)
|
||||
case "CLEANUP_ARCHIVE_BATCH_SIZE":
|
||||
p.opts.cleanupArchiveBatchSize = parseInt(value, defaultCleanupArchiveBatchSize)
|
||||
case "CLEANUP_REMOVE_SESSIONS_DAYS":
|
||||
p.opts.cleanupRemoveSessionsDays = parseInt(value, defaultCleanupRemoveSessionsDays)
|
||||
case "WORKER_POOL_SIZE":
|
||||
@@ -134,8 +135,22 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||
p.opts.schedulerEntryFrequencyMaxInterval = parseInt(value, defaultSchedulerEntryFrequencyMaxInterval)
|
||||
case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL":
|
||||
p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
|
||||
case "POLLING_PARSING_ERROR_LIMIT":
|
||||
p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
|
||||
// kept for compatibility purpose
|
||||
case "PROXY_IMAGES":
|
||||
p.opts.proxyImages = parseString(value, defaultProxyImages)
|
||||
p.opts.proxyOption = parseString(value, defaultProxyOption)
|
||||
case "PROXY_HTTP_CLIENT_TIMEOUT":
|
||||
p.opts.proxyHTTPClientTimeout = parseInt(value, defaultProxyHTTPClientTimeout)
|
||||
case "PROXY_OPTION":
|
||||
p.opts.proxyOption = parseString(value, defaultProxyOption)
|
||||
case "PROXY_MEDIA_TYPES":
|
||||
p.opts.proxyMediaTypes = parseStringList(value, []string{defaultProxyMediaTypes})
|
||||
// kept for compatibility purpose
|
||||
case "PROXY_IMAGE_URL":
|
||||
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
|
||||
case "PROXY_URL":
|
||||
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
|
||||
case "CREATE_ADMIN":
|
||||
p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
|
||||
case "ADMIN_USERNAME":
|
||||
@@ -174,6 +189,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||
p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
|
||||
case "HTTP_CLIENT_USER_AGENT":
|
||||
p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
|
||||
case "HTTP_SERVER_TIMEOUT":
|
||||
p.opts.httpServerTimeout = parseInt(value, defaultHTTPServerTimeout)
|
||||
case "AUTH_PROXY_HEADER":
|
||||
p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
|
||||
case "AUTH_PROXY_USER_CREATION":
|
||||
@@ -188,6 +205,26 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||
p.opts.metricsRefreshInterval = parseInt(value, defaultMetricsRefreshInterval)
|
||||
case "METRICS_ALLOWED_NETWORKS":
|
||||
p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks})
|
||||
case "METRICS_USERNAME":
|
||||
p.opts.metricsUsername = parseString(value, defaultMetricsUsername)
|
||||
case "METRICS_USERNAME_FILE":
|
||||
p.opts.metricsUsername = readSecretFile(value, defaultMetricsUsername)
|
||||
case "METRICS_PASSWORD":
|
||||
p.opts.metricsPassword = parseString(value, defaultMetricsPassword)
|
||||
case "METRICS_PASSWORD_FILE":
|
||||
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
|
||||
case "FETCH_YOUTUBE_WATCH_TIME":
|
||||
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
|
||||
case "YOUTUBE_EMBED_URL_OVERRIDE":
|
||||
p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride)
|
||||
case "WATCHDOG":
|
||||
p.opts.watchdog = parseBool(value, defaultWatchdog)
|
||||
case "INVIDIOUS_INSTANCE":
|
||||
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
|
||||
case "PROXY_PRIVATE_KEY":
|
||||
randomKey := make([]byte, 16)
|
||||
rand.Read(randomKey)
|
||||
p.opts.proxyPrivateKey = parseBytes(value, randomKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,12 +245,12 @@ func parseBaseURL(value string) (string, string, string, error) {
|
||||
|
||||
url, err := url_parser.Parse(value)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
|
||||
return "", "", "", fmt.Errorf("config: invalid BASE_URL: %w", err)
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(url.Scheme)
|
||||
if scheme != "https" && scheme != "http" {
|
||||
return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
|
||||
return "", "", "", errors.New("config: invalid BASE_URL: scheme must be http or https")
|
||||
}
|
||||
|
||||
basePath := url.Path
|
||||
@@ -260,16 +297,31 @@ func parseStringList(value string, fallback []string) []string {
|
||||
}
|
||||
|
||||
var strList []string
|
||||
strMap := make(map[string]bool)
|
||||
|
||||
items := strings.Split(value, ",")
|
||||
for _, item := range items {
|
||||
strList = append(strList, strings.TrimSpace(item))
|
||||
itemValue := strings.TrimSpace(item)
|
||||
|
||||
if _, found := strMap[itemValue]; !found {
|
||||
strMap[itemValue] = true
|
||||
strList = append(strList, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return strList
|
||||
}
|
||||
|
||||
func parseBytes(value string, fallback []byte) []byte {
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return []byte(value)
|
||||
}
|
||||
|
||||
func readSecretFile(filename, fallback string) string {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2019 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/config"
|
||||
|
||||
|
||||
@@ -8,6 +8,5 @@ Here are few Docker Compose examples:
|
||||
- `traefik.yml`: Use Traefik as reverse-proxy with automatic HTTPS
|
||||
|
||||
```bash
|
||||
docker-compose -f basic.yml up -d db
|
||||
docker-compose -f basic.yml up
|
||||
docker compose -f basic.yml up -d
|
||||
```
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
miniflux:
|
||||
image: miniflux/miniflux:latest
|
||||
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
|
||||
container_name: miniflux
|
||||
restart: always
|
||||
ports:
|
||||
- "80:8080"
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
|
||||
- RUN_MIGRATIONS=1
|
||||
- CREATE_ADMIN=1
|
||||
- ADMIN_USERNAME=admin
|
||||
- ADMIN_PASSWORD=test123
|
||||
- DEBUG=1
|
||||
# Optional health check:
|
||||
# healthcheck:
|
||||
# test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
|
||||
db:
|
||||
image: postgres:latest
|
||||
image: postgres:15
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_USER=miniflux
|
||||
- POSTGRES_PASSWORD=secret
|
||||
volumes:
|
||||
- miniflux-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "miniflux"]
|
||||
interval: 10s
|
||||
start_period: 30s
|
||||
volumes:
|
||||
miniflux-db:
|
||||
miniflux-db:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2
|
||||
@@ -13,10 +12,11 @@ services:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
miniflux:
|
||||
image: miniflux/miniflux:latest
|
||||
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
|
||||
container_name: miniflux
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
|
||||
- RUN_MIGRATIONS=1
|
||||
@@ -25,13 +25,17 @@ services:
|
||||
- ADMIN_PASSWORD=test123
|
||||
- BASE_URL=https://miniflux.example.org
|
||||
db:
|
||||
image: postgres:latest
|
||||
image: postgres:15
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_USER=miniflux
|
||||
- POSTGRES_PASSWORD=secret
|
||||
volumes:
|
||||
- miniflux-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "miniflux"]
|
||||
interval: 10s
|
||||
start_period: 30s
|
||||
volumes:
|
||||
miniflux-db:
|
||||
caddy_data:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.3"
|
||||
@@ -18,10 +17,11 @@ services:
|
||||
- "./letsencrypt:/letsencrypt"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
miniflux:
|
||||
image: miniflux/miniflux:latest
|
||||
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
|
||||
container_name: miniflux
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
expose:
|
||||
- "8080"
|
||||
environment:
|
||||
@@ -37,12 +37,16 @@ services:
|
||||
- "traefik.http.routers.miniflux.entrypoints=websecure"
|
||||
- "traefik.http.routers.miniflux.tls.certresolver=myresolver"
|
||||
db:
|
||||
image: postgres:latest
|
||||
image: postgres:15
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_USER=miniflux
|
||||
- POSTGRES_PASSWORD=secret
|
||||
volumes:
|
||||
- miniflux-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "miniflux"]
|
||||
interval: 10s
|
||||
start_period: 30s
|
||||
volumes:
|
||||
miniflux-db:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
@@ -104,7 +104,7 @@
|
||||
"type": "bargauge"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
@@ -289,7 +289,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -332,7 +332,7 @@
|
||||
"pluginVersion": "7.1.5",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_sys_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_sys_bytes{job=\"miniflux\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{ instance }} - Memory Used",
|
||||
"refId": "A"
|
||||
@@ -348,7 +348,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
@@ -440,7 +440,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
@@ -529,7 +529,7 @@
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
@@ -546,7 +546,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
@@ -590,7 +590,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_sys_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_sys_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -699,7 +699,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "process_open_fds{app=\"miniflux\"}",
|
||||
"expr": "process_open_fds{job=\"miniflux\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{instance }} - Open File Descriptors",
|
||||
"refId": "A"
|
||||
@@ -748,7 +748,7 @@
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
@@ -816,7 +816,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_alloc_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_alloc_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -826,7 +826,7 @@
|
||||
"step": 4
|
||||
},
|
||||
{
|
||||
"expr": "rate(go_memstats_alloc_bytes_total{app=\"miniflux\"}[30s])",
|
||||
"expr": "rate(go_memstats_alloc_bytes_total{job=\"miniflux\"}[30s])",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -836,7 +836,7 @@
|
||||
"step": 4
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_stack_inuse_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_stack_inuse_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -846,7 +846,7 @@
|
||||
"step": 4
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_heap_inuse_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_inuse_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
@@ -904,7 +904,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
@@ -945,13 +945,13 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_goroutines{app=\"miniflux\"}",
|
||||
"expr": "go_goroutines{job=\"miniflux\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{ instance }} - Goroutines",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "go_threads{app=\"miniflux\"}",
|
||||
"expr": "go_threads{job=\"miniflux\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{ instance }} - OS threads",
|
||||
"refId": "B"
|
||||
@@ -1003,7 +1003,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
@@ -1046,7 +1046,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_stack_inuse_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_stack_inuse_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1054,7 +1054,7 @@
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_stack_sys_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_stack_sys_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1108,7 +1108,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
@@ -1150,7 +1150,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_heap_alloc_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_alloc_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1158,7 +1158,7 @@
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_heap_sys_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_sys_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1166,7 +1166,7 @@
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_heap_idle_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_idle_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1174,7 +1174,7 @@
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_heap_inuse_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_inuse_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1182,7 +1182,7 @@
|
||||
"refId": "D"
|
||||
},
|
||||
{
|
||||
"expr": "go_memstats_heap_released_bytes{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_heap_released_bytes{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1282,7 +1282,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_gc_duration_seconds{app=\"miniflux\"}",
|
||||
"expr": "go_gc_duration_seconds{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -1339,7 +1339,7 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
@@ -1383,7 +1383,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "go_memstats_mallocs_total{app=\"miniflux\"} - go_memstats_frees_total{app=\"miniflux\"}",
|
||||
"expr": "go_memstats_mallocs_total{job=\"miniflux\"} - go_memstats_frees_total{job=\"miniflux\"}",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
@@ -1462,4 +1462,4 @@
|
||||
"title": "Miniflux",
|
||||
"uid": "vSaPgcFMk",
|
||||
"version": 23
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
System-V init for e.g. http://devuan.org
|
||||
|
||||
Assumes an executable `/usr/local/bin/miniflux`.
|
||||
|
||||
Configure in `etc/default/miniflux`
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# sourced by /etc/init.d/miniflux
|
||||
# see cluster port in pg_lsclusters and ls -Al /var/run/postgresql/
|
||||
export DATABASE_URL='host=/var/run/postgresql/ port=5433 user=miniflux password=<my secrect db password> dbname=miniflux sslmode=disable'
|
||||
export LISTEN_ADDR='127.0.0.1:8081'
|
||||
export BASE_URL='https://<my miniflux domain> and path/'
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#! /bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: miniflux
|
||||
# Required-Start: $syslog $network
|
||||
# Required-Stop: $syslog
|
||||
# Should-Start: postgresql
|
||||
# Should-Stop: postgresql
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: A rss reader
|
||||
# Description: A RSS reader
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Danny Boisvert
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Miniflux"
|
||||
NAME=miniflux
|
||||
SERVICEVERBOSE=yes
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
WORKINGDIR=/usr/local/bin
|
||||
DAEMON=$WORKINGDIR/$NAME
|
||||
DAEMON_ARGS=""
|
||||
USER=nobody
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
|
||||
--test --chdir $WORKINGDIR --chuid $USER \\
|
||||
--exec $DAEMON -- $DAEMON_ARGS > /dev/null \\
|
||||
|| return 1"
|
||||
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
|
||||
--background --chdir $WORKINGDIR --chuid $USER \\
|
||||
--exec $DAEMON -- $DAEMON_ARGS \\
|
||||
|| return 2"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/1/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/1/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$SERVICEVERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$SERVICEVERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$SERVICEVERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$SERVICEVERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$SERVICEVERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$SERVICEVERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
+9
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package crypto // import "miniflux.app/crypto"
|
||||
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// HashFromBytes returns a SHA-256 checksum of the input.
|
||||
@@ -42,3 +43,8 @@ func GenerateRandomString(size int) string {
|
||||
func GenerateRandomStringHex(size int) string {
|
||||
return hex.EncodeToString(GenerateRandomBytes(size))
|
||||
}
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package crypto implements helpers related to cryptography.
|
||||
|
||||
*/
|
||||
package crypto // import "miniflux.app/crypto"
|
||||
@@ -1,19 +1,19 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package database // import "miniflux.app/database"
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
// Postgresql driver import
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// NewConnectionPool configures the database connection pool.
|
||||
func NewConnectionPool(dsn string, minConnections, maxConnections int) (*sql.DB, error) {
|
||||
func NewConnectionPool(dsn string, minConnections, maxConnections int, connectionLifetime time.Duration) (*sql.DB, error) {
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -21,6 +21,7 @@ func NewConnectionPool(dsn string, minConnections, maxConnections int) (*sql.DB,
|
||||
|
||||
db.SetMaxOpenConns(maxConnections)
|
||||
db.SetMaxIdleConns(minConnections)
|
||||
db.SetConnMaxLifetime(connectionLifetime)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
+235
-15
@@ -1,6 +1,5 @@
|
||||
// Copyright 2020 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package database // import "miniflux.app/database"
|
||||
|
||||
@@ -10,13 +9,14 @@ import (
|
||||
|
||||
var schemaVersion = len(migrations)
|
||||
|
||||
// Order is important. Add new migrations at the end of the list.
|
||||
var migrations = []func(tx *sql.Tx) error{
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
CREATE TABLE schema_version (
|
||||
version text not null
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE users (
|
||||
id serial not null,
|
||||
username text not null unique,
|
||||
@@ -28,7 +28,7 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
last_login_at timestamp with time zone,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id serial not null,
|
||||
user_id int not null,
|
||||
@@ -40,7 +40,7 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
unique (user_id, token),
|
||||
foreign key (user_id) references users(id) on delete cascade
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE categories (
|
||||
id serial not null,
|
||||
user_id int not null,
|
||||
@@ -49,7 +49,7 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
unique (user_id, title),
|
||||
foreign key (user_id) references users(id) on delete cascade
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE feeds (
|
||||
id bigserial not null,
|
||||
user_id int not null,
|
||||
@@ -67,9 +67,9 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
foreign key (user_id) references users(id) on delete cascade,
|
||||
foreign key (category_id) references categories(id) on delete cascade
|
||||
);
|
||||
|
||||
|
||||
CREATE TYPE entry_status as enum('unread', 'read', 'removed');
|
||||
|
||||
|
||||
CREATE TABLE entries (
|
||||
id bigserial not null,
|
||||
user_id int not null,
|
||||
@@ -86,9 +86,9 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
foreign key (user_id) references users(id) on delete cascade,
|
||||
foreign key (feed_id) references feeds(id) on delete cascade
|
||||
);
|
||||
|
||||
|
||||
CREATE INDEX entries_feed_idx on entries using btree(feed_id);
|
||||
|
||||
|
||||
CREATE TABLE enclosures (
|
||||
id bigserial not null,
|
||||
user_id int not null,
|
||||
@@ -100,7 +100,7 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
foreign key (user_id) references users(id) on delete cascade,
|
||||
foreign key (entry_id) references entries(id) on delete cascade
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE icons (
|
||||
id bigserial not null,
|
||||
hash text not null unique,
|
||||
@@ -108,14 +108,14 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
content bytea not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE feed_icons (
|
||||
feed_id bigint not null,
|
||||
icon_id bigint not null,
|
||||
primary key(feed_id, icon_id),
|
||||
foreign key (feed_id) references feeds(id) on delete cascade,
|
||||
foreign key (icon_id) references icons(id) on delete cascade
|
||||
);
|
||||
);
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
@@ -409,7 +409,7 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE feeds
|
||||
ALTER TABLE feeds
|
||||
ADD COLUMN blocklist_rules text not null default '',
|
||||
ADD COLUMN keeplist_rules text not null default ''
|
||||
`
|
||||
@@ -496,4 +496,224 @@ var migrations = []func(tx *sql.Tx) error{
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
CREATE INDEX entries_feed_url_idx ON entries(feed_id, url);
|
||||
CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
|
||||
CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
CREATE TABLE acme_cache (
|
||||
key varchar(400) not null primary key,
|
||||
data bytea not null,
|
||||
updated_at timestamptz not null
|
||||
);
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
|
||||
ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN telegram_bot_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN telegram_bot_token text default '';
|
||||
ALTER TABLE integrations ADD COLUMN telegram_bot_chat_id text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
|
||||
ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN googlereader_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN googlereader_username text default '';
|
||||
ALTER TABLE integrations ADD COLUMN googlereader_password text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN espial_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN espial_url text default '';
|
||||
ALTER TABLE integrations ADD COLUMN espial_api_key text default '';
|
||||
ALTER TABLE integrations ADD COLUMN espial_tags text default 'miniflux';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN linkding_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN linkding_url text default '';
|
||||
ALTER TABLE integrations ADD COLUMN linkding_api_key text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
|
||||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE users ADD COLUMN default_reading_speed int default 265;
|
||||
ALTER TABLE users ADD COLUMN cjk_reading_speed int default 500;
|
||||
`)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
|
||||
`)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
|
||||
`)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE users ADD COLUMN categories_sorting_order text not null default 'unread_count';
|
||||
`)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN matrix_bot_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN matrix_bot_user text default '';
|
||||
ALTER TABLE integrations ADD COLUMN matrix_bot_password text default '';
|
||||
ALTER TABLE integrations ADD COLUMN matrix_bot_url text default '';
|
||||
ALTER TABLE integrations ADD COLUMN matrix_bot_chat_id text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `ALTER TABLE users ADD COLUMN double_tap boolean default 't'`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
_, err = tx.Exec(`
|
||||
ALTER TABLE entries ADD COLUMN tags text[] default '{}';
|
||||
`)
|
||||
return
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE users RENAME double_tap TO gesture_nav;
|
||||
ALTER TABLE users ALTER COLUMN gesture_nav SET DATA TYPE text using case when gesture_nav = true then 'tap' when gesture_nav = false then 'none' end;
|
||||
ALTER TABLE users ALTER COLUMN gesture_nav SET default 'tap';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN linkding_tags text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE feeds ADD COLUMN no_media_player boolean default 'f';
|
||||
ALTER TABLE enclosures ADD COLUMN media_progression int default 0;
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN linkding_mark_as_unread bool default 'f';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
// Delete duplicated rows
|
||||
sql := `
|
||||
DELETE FROM enclosures a USING enclosures b
|
||||
WHERE a.id < b.id
|
||||
AND a.user_id = b.user_id
|
||||
AND a.entry_id = b.entry_id
|
||||
AND a.url = b.url;
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove previous index
|
||||
_, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create unique index
|
||||
_, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN notion_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN notion_token text default '';
|
||||
ALTER TABLE integrations ADD COLUMN notion_page_id text default '';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Miniflux is a feed reader application.
|
||||
|
||||
*/
|
||||
package main // import "miniflux.app"
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package errors handles localized errors.
|
||||
|
||||
*/
|
||||
package errors // import "miniflux.app/errors"
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package errors // import "miniflux.app/errors"
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package fever implements Fever API endpoints.
|
||||
|
||||
*/
|
||||
package fever // import "miniflux.app/fever"
|
||||
+53
-55
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fever // import "miniflux.app/fever"
|
||||
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"miniflux.app/integration"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/model"
|
||||
"miniflux.app/proxy"
|
||||
"miniflux.app/storage"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
// Serve handles Fever API calls.
|
||||
func Serve(router *mux.Router, store *storage.Storage) {
|
||||
handler := &handler{store}
|
||||
handler := &handler{store, router}
|
||||
|
||||
sr := router.PathPrefix("/fever").Subrouter()
|
||||
sr.Use(newMiddleware(store).serve)
|
||||
@@ -30,7 +30,8 @@ func Serve(router *mux.Router, store *storage.Storage) {
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
store *storage.Storage
|
||||
store *storage.Storage
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
func (h *handler) serve(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -61,13 +62,13 @@ func (h *handler) serve(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
A request with the groups argument will return two additional members:
|
||||
|
||||
groups contains an array of group objects
|
||||
feeds_groups contains an array of feeds_group objects
|
||||
groups contains an array of group objects
|
||||
feeds_groups contains an array of feeds_group objects
|
||||
|
||||
A group object has the following members:
|
||||
|
||||
id (positive integer)
|
||||
title (utf-8 string)
|
||||
id (positive integer)
|
||||
title (utf-8 string)
|
||||
|
||||
The feeds_group object is documented under “Feeds/Groups Relationships.”
|
||||
|
||||
@@ -76,7 +77,6 @@ an is_spark equal to 0.
|
||||
|
||||
The “Sparks” super group is not included in this response and is composed of all feeds with an
|
||||
is_spark equal to 1.
|
||||
|
||||
*/
|
||||
func (h *handler) handleGroups(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
@@ -107,18 +107,18 @@ func (h *handler) handleGroups(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
A request with the feeds argument will return two additional members:
|
||||
|
||||
feeds contains an array of group objects
|
||||
feeds_groups contains an array of feeds_group objects
|
||||
feeds contains an array of group objects
|
||||
feeds_groups contains an array of feeds_group objects
|
||||
|
||||
A feed object has the following members:
|
||||
|
||||
id (positive integer)
|
||||
favicon_id (positive integer)
|
||||
title (utf-8 string)
|
||||
url (utf-8 string)
|
||||
site_url (utf-8 string)
|
||||
is_spark (boolean integer)
|
||||
last_updated_on_time (Unix timestamp/integer)
|
||||
id (positive integer)
|
||||
favicon_id (positive integer)
|
||||
title (utf-8 string)
|
||||
url (utf-8 string)
|
||||
site_url (utf-8 string)
|
||||
is_spark (boolean integer)
|
||||
last_updated_on_time (Unix timestamp/integer)
|
||||
|
||||
The feeds_group object is documented under “Feeds/Groups Relationships.”
|
||||
|
||||
@@ -165,12 +165,12 @@ func (h *handler) handleFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
A request with the favicons argument will return one additional member:
|
||||
|
||||
favicons contains an array of favicon objects
|
||||
favicons contains an array of favicon objects
|
||||
|
||||
A favicon object has the following members:
|
||||
|
||||
id (positive integer)
|
||||
data (base64 encoded image data; prefixed by image type)
|
||||
id (positive integer)
|
||||
data (base64 encoded image data; prefixed by image type)
|
||||
|
||||
An example data value:
|
||||
|
||||
@@ -206,20 +206,20 @@ func (h *handler) handleFavicons(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
A request with the items argument will return two additional members:
|
||||
|
||||
items contains an array of item objects
|
||||
total_items contains the total number of items stored in the database (added in API version 2)
|
||||
items contains an array of item objects
|
||||
total_items contains the total number of items stored in the database (added in API version 2)
|
||||
|
||||
An item object has the following members:
|
||||
|
||||
id (positive integer)
|
||||
feed_id (positive integer)
|
||||
title (utf-8 string)
|
||||
author (utf-8 string)
|
||||
html (utf-8 string)
|
||||
url (utf-8 string)
|
||||
is_saved (boolean integer)
|
||||
is_read (boolean integer)
|
||||
created_on_time (Unix timestamp/integer)
|
||||
id (positive integer)
|
||||
feed_id (positive integer)
|
||||
title (utf-8 string)
|
||||
author (utf-8 string)
|
||||
html (utf-8 string)
|
||||
url (utf-8 string)
|
||||
is_saved (boolean integer)
|
||||
is_read (boolean integer)
|
||||
created_on_time (Unix timestamp/integer)
|
||||
|
||||
Most servers won’t have enough memory allocated to PHP to dump all items at once.
|
||||
Three optional arguments control determine the items included in the response.
|
||||
@@ -232,7 +232,6 @@ Three optional arguments control determine the items included in the response.
|
||||
|
||||
Use the with_ids argument with a comma-separated list of item ids to request (a maximum of 50) specific items.
|
||||
(added in API version 2)
|
||||
|
||||
*/
|
||||
func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
|
||||
var result itemsResponse
|
||||
@@ -242,8 +241,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
|
||||
builder := h.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
builder.WithLimit(50)
|
||||
builder.WithOrder("id")
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
builder.WithSorting("id", model.DefaultSortingDirection)
|
||||
|
||||
switch {
|
||||
case request.HasQueryParam(r, "since_id"):
|
||||
@@ -256,11 +254,11 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
|
||||
maxID := request.QueryInt64Param(r, "max_id", 0)
|
||||
if maxID == 0 {
|
||||
logger.Debug("[Fever] Fetching most recent items for user #%d", userID)
|
||||
builder.WithDirection("desc")
|
||||
builder.WithSorting("id", "DESC")
|
||||
} else if maxID > 0 {
|
||||
logger.Debug("[Fever] Fetching items before #%d for user #%d", maxID, userID)
|
||||
builder.BeforeEntryID(maxID)
|
||||
builder.WithDirection("desc")
|
||||
builder.WithSorting("id", "DESC")
|
||||
}
|
||||
case request.HasQueryParam(r, "with_ids"):
|
||||
csvItemIDs := request.QueryStringParam(r, "with_ids", "")
|
||||
@@ -310,7 +308,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
|
||||
FeedID: entry.FeedID,
|
||||
Title: entry.Title,
|
||||
Author: entry.Author,
|
||||
HTML: entry.Content,
|
||||
HTML: proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content),
|
||||
URL: entry.URL,
|
||||
IsSaved: isSaved,
|
||||
IsRead: isRead,
|
||||
@@ -327,7 +325,8 @@ The unread_item_ids and saved_item_ids arguments can be used to keep your local
|
||||
with the remote Fever installation.
|
||||
|
||||
A request with the unread_item_ids argument will return one additional member:
|
||||
unread_item_ids (string/comma-separated list of positive integers)
|
||||
|
||||
unread_item_ids (string/comma-separated list of positive integers)
|
||||
*/
|
||||
func (h *handler) handleUnreadItems(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
@@ -365,7 +364,7 @@ func (h *handler) handleSavedItems(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debug("[Fever] Fetching saved items for user #%d", userID)
|
||||
|
||||
builder := h.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithStarred()
|
||||
builder.WithStarred(true)
|
||||
|
||||
entryIDs, err := builder.GetEntryIDs()
|
||||
if err != nil {
|
||||
@@ -384,9 +383,9 @@ func (h *handler) handleSavedItems(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/*
|
||||
mark=item
|
||||
as=? where ? is replaced with read, saved or unsaved
|
||||
id=? where ? is replaced with the id of the item to modify
|
||||
mark=item
|
||||
as=? where ? is replaced with read, saved or unsaved
|
||||
id=? where ? is replaced with the id of the item to modify
|
||||
*/
|
||||
func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
@@ -448,10 +447,10 @@ func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/*
|
||||
mark=feed
|
||||
as=read
|
||||
id=? where ? is replaced with the id of the feed or group to modify
|
||||
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
|
||||
mark=feed
|
||||
as=read
|
||||
id=? where ? is replaced with the id of the feed or group to modify
|
||||
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
|
||||
*/
|
||||
func (h *handler) handleWriteFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
@@ -474,10 +473,10 @@ func (h *handler) handleWriteFeeds(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/*
|
||||
mark=group
|
||||
as=read
|
||||
id=? where ? is replaced with the id of the feed or group to modify
|
||||
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
|
||||
mark=group
|
||||
as=read
|
||||
id=? where ? is replaced with the id of the feed or group to modify
|
||||
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
|
||||
*/
|
||||
func (h *handler) handleWriteGroups(w http.ResponseWriter, r *http.Request) {
|
||||
userID := request.UserID(r)
|
||||
@@ -510,9 +509,8 @@ func (h *handler) handleWriteGroups(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
A feeds_group object has the following members:
|
||||
|
||||
group_id (positive integer)
|
||||
feed_ids (string/comma-separated list of positive integers)
|
||||
|
||||
group_id (positive integer)
|
||||
feed_ids (string/comma-separated list of positive integers)
|
||||
*/
|
||||
func (h *handler) buildFeedGroups(feeds model.Feeds) []feedsGroups {
|
||||
feedsGroupedByCategory := make(map[int64][]string)
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fever // import "miniflux.app/fever"
|
||||
|
||||
|
||||
+4
-6
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fever // import "miniflux.app/fever"
|
||||
|
||||
@@ -23,8 +22,8 @@ func (b *baseResponse) SetCommonValues() {
|
||||
/*
|
||||
The default response is a JSON object containing two members:
|
||||
|
||||
api_version contains the version of the API responding (positive integer)
|
||||
auth whether the request was successfully authenticated (boolean integer)
|
||||
api_version contains the version of the API responding (positive integer)
|
||||
auth whether the request was successfully authenticated (boolean integer)
|
||||
|
||||
The API can also return XML by passing xml as the optional value of the api argument like so:
|
||||
|
||||
@@ -37,7 +36,6 @@ at least one additional member:
|
||||
|
||||
last_refreshed_on_time contains the time of the most recently refreshed (not updated)
|
||||
feed (Unix timestamp/integer)
|
||||
|
||||
*/
|
||||
func newBaseResponse() baseResponse {
|
||||
r := baseResponse{}
|
||||
|
||||
-221
@@ -1,221 +0,0 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tdewolff/minify/v2"
|
||||
"github.com/tdewolff/minify/v2/css"
|
||||
"github.com/tdewolff/minify/v2/js"
|
||||
)
|
||||
|
||||
const tpl = `// Code generated by go generate; DO NOT EDIT.
|
||||
|
||||
package {{ .Package }} // import "miniflux.app/{{ .ImportPath }}"
|
||||
|
||||
var {{ .Map }} = map[string]string{
|
||||
{{ range $constant, $content := .Files }}` + "\t" + `"{{ $constant }}": ` + "`{{ $content }}`" + `,
|
||||
{{ end }}}
|
||||
|
||||
var {{ .Map }}Checksums = map[string]string{
|
||||
{{ range $constant, $content := .Checksums }}` + "\t" + `"{{ $constant }}": "{{ $content }}",
|
||||
{{ end }}}
|
||||
`
|
||||
|
||||
var bundleTpl = template.Must(template.New("").Parse(tpl))
|
||||
|
||||
type Bundle struct {
|
||||
Package string
|
||||
Map string
|
||||
ImportPath string
|
||||
Files map[string]string
|
||||
Checksums map[string]string
|
||||
}
|
||||
|
||||
func (b *Bundle) Write(filename string) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bundleTpl.Execute(f, b)
|
||||
}
|
||||
|
||||
func NewBundle(pkg, mapName, importPath string) *Bundle {
|
||||
return &Bundle{
|
||||
Package: pkg,
|
||||
Map: mapName,
|
||||
ImportPath: importPath,
|
||||
Files: make(map[string]string),
|
||||
Checksums: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func readFile(filename string) []byte {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func checksum(data []byte) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256(data))
|
||||
}
|
||||
|
||||
func basename(filename string) string {
|
||||
return path.Base(filename)
|
||||
}
|
||||
|
||||
func stripExtension(filename string) string {
|
||||
filename = strings.TrimSuffix(filename, path.Ext(filename))
|
||||
return strings.Replace(filename, " ", "_", -1)
|
||||
}
|
||||
|
||||
func glob(pattern string) []string {
|
||||
// There is no Glob function in path package, so we have to use filepath and replace in case of Windows
|
||||
files, _ := filepath.Glob(pattern)
|
||||
for i := range files {
|
||||
if strings.Contains(files[i], "\\") {
|
||||
files[i] = strings.Replace(files[i], "\\", "/", -1)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func concat(files []string) string {
|
||||
var b strings.Builder
|
||||
for _, file := range files {
|
||||
b.Write(readFile(file))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func generateJSBundle(bundleFile string, bundleFiles map[string][]string, prefixes, suffixes map[string]string) {
|
||||
bundle := NewBundle("static", "Javascripts", "ui/static")
|
||||
m := minify.New()
|
||||
m.AddFunc("text/javascript", js.Minify)
|
||||
|
||||
for name, srcFiles := range bundleFiles {
|
||||
var b strings.Builder
|
||||
|
||||
if prefix, found := prefixes[name]; found {
|
||||
b.WriteString(prefix)
|
||||
}
|
||||
|
||||
b.WriteString(concat(srcFiles))
|
||||
|
||||
if suffix, found := suffixes[name]; found {
|
||||
b.WriteString(suffix)
|
||||
}
|
||||
|
||||
minifiedData, err := m.String("text/javascript", b.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bundle.Files[name] = minifiedData
|
||||
bundle.Checksums[name] = checksum([]byte(minifiedData))
|
||||
}
|
||||
|
||||
bundle.Write(bundleFile)
|
||||
}
|
||||
|
||||
func generateCSSBundle(bundleFile string, themes map[string][]string) {
|
||||
bundle := NewBundle("static", "Stylesheets", "ui/static")
|
||||
m := minify.New()
|
||||
m.AddFunc("text/css", css.Minify)
|
||||
|
||||
for theme, srcFiles := range themes {
|
||||
data := concat(srcFiles)
|
||||
minifiedData, err := m.String("text/css", data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bundle.Files[theme] = minifiedData
|
||||
bundle.Checksums[theme] = checksum([]byte(minifiedData))
|
||||
}
|
||||
|
||||
bundle.Write(bundleFile)
|
||||
}
|
||||
|
||||
func generateBinaryBundle(bundleFile string, srcFiles []string) {
|
||||
bundle := NewBundle("static", "Binaries", "ui/static")
|
||||
|
||||
for _, srcFile := range srcFiles {
|
||||
data := readFile(srcFile)
|
||||
filename := basename(srcFile)
|
||||
encodedData := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
bundle.Files[filename] = string(encodedData)
|
||||
bundle.Checksums[filename] = checksum(data)
|
||||
}
|
||||
|
||||
bundle.Write(bundleFile)
|
||||
}
|
||||
|
||||
func generateBundle(bundleFile, pkg, mapName string, srcFiles []string) {
|
||||
bundle := NewBundle(pkg, mapName, pkg)
|
||||
|
||||
for _, srcFile := range srcFiles {
|
||||
data := readFile(srcFile)
|
||||
filename := stripExtension(basename(srcFile))
|
||||
|
||||
bundle.Files[filename] = string(data)
|
||||
bundle.Checksums[filename] = checksum(data)
|
||||
}
|
||||
|
||||
bundle.Write(bundleFile)
|
||||
}
|
||||
|
||||
func main() {
|
||||
generateJSBundle("ui/static/js.go", map[string][]string{
|
||||
"app": []string{
|
||||
"ui/static/js/dom_helper.js",
|
||||
"ui/static/js/touch_handler.js",
|
||||
"ui/static/js/keyboard_handler.js",
|
||||
"ui/static/js/request_builder.js",
|
||||
"ui/static/js/modal_handler.js",
|
||||
"ui/static/js/app.js",
|
||||
"ui/static/js/bootstrap.js",
|
||||
},
|
||||
"service-worker": []string{
|
||||
"ui/static/js/service_worker.js",
|
||||
},
|
||||
}, map[string]string{
|
||||
"app": "(function(){'use strict';",
|
||||
"sw": "'use strict';",
|
||||
}, map[string]string{
|
||||
"app": "})();",
|
||||
})
|
||||
|
||||
generateCSSBundle("ui/static/css.go", map[string][]string{
|
||||
"light_serif": []string{"ui/static/css/light.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
|
||||
"light_sans_serif": []string{"ui/static/css/light.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
|
||||
"dark_serif": []string{"ui/static/css/dark.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
|
||||
"dark_sans_serif": []string{"ui/static/css/dark.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
|
||||
"system_serif": []string{"ui/static/css/system.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
|
||||
"system_sans_serif": []string{"ui/static/css/system.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
|
||||
})
|
||||
|
||||
generateBinaryBundle("ui/static/bin.go", glob("ui/static/bin/*"))
|
||||
|
||||
generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
|
||||
generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
|
||||
generateBundle("locale/translations.go", "locale", "translations", glob("locale/translations/*.json"))
|
||||
}
|
||||
@@ -1,27 +1,42 @@
|
||||
module miniflux.app
|
||||
|
||||
// +heroku goVersion go1.11
|
||||
// +heroku goVersion go1.19
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.6.0
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/coreos/go-oidc/v3 v3.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/lib/pq v1.9.0
|
||||
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.9.0
|
||||
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d
|
||||
github.com/stretchr/testify v1.6.1 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.9.10 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
google.golang.org/appengine v1.6.6 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
|
||||
mvdan.cc/xurls/v2 v2.2.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
|
||||
github.com/mccutchen/go-httpbin/v2 v2.10.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0
|
||||
github.com/tdewolff/minify/v2 v2.12.7
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/term v0.10.0
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
go 1.11
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
||||
go 1.19
|
||||
|
||||
@@ -1,585 +1,129 @@
|
||||
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
|
||||
github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/felixge/httpsnoop v1.0.0 h1:gh8fMGz0rlOv/1WmRZm7OgncIOTsAj21iNJot48omJQ=
|
||||
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5 h1:yrv1uUvgXH/tEat+wdvJMRJ4g51GlIydtDpU9pFjaaI=
|
||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
|
||||
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 h1:5vu7C+63KTbsSNnLhrgB98Sqy8MNVSW8FdhkcWA/3Rk=
|
||||
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686/go.mod h1:sam69Hju0uq+5uvLJUMDlsKlQ21Vrs1Kd/1YFPNYdOU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
|
||||
github.com/google/go-dap v0.3.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
|
||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2 h1:9kYRAm87/M5VL+HWegDGIorBWDiErrZrksLKTJBF2IQ=
|
||||
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2/go.mod h1:8mKJVJkyMI20ifXXO0zlV3sh3FrtjvltAeBqz38clBE=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mccutchen/go-httpbin/v2 v2.10.0 h1:8EUgpxSmbXEtmBgfZhtVngCdmMkc0MQnUz2JXfIrT+E=
|
||||
github.com/mccutchen/go-httpbin/v2 v2.10.0/go.mod h1:f4DUXYlU6yH0V81O4lJIwqpmYdTXXmYwzxMnYEimFPk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
|
||||
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
|
||||
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d h1:4Jn2kzSKcpxxwpJdyWxfNWKCYe4Uki2VibkExYmBLiA=
|
||||
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d/go.mod h1:3vfmZI6aJd5Rb9W2TQ0Nmupl+qem21R05+hmCscI0Bk=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0 h1:qSaU9YAEIxk/ozcmY1hiauktAYTpbwYIrPdQ0L2E8UM=
|
||||
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0/go.mod h1:3vfmZI6aJd5Rb9W2TQ0Nmupl+qem21R05+hmCscI0Bk=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tdewolff/minify v1.1.0 h1:nxHQi1ML+g3ZbZHffiZ6eC7vMqNvSRfX3KB5Y5y/kfw=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
||||
github.com/tdewolff/minify/v2 v2.9.10 h1:p+ifTTl+JMFFLDYNAm7nxQ9XuCG10HTW00wlPAZ7aoE=
|
||||
github.com/tdewolff/minify/v2 v2.9.10/go.mod h1:U1Nc+/YBSB0FPEarqcgkYH3Ep4DNyyIbOyl5P4eWMuo=
|
||||
github.com/tdewolff/parse/v2 v2.5.5 h1:b7ICJa4I/54JQGEGgTte8DiyJPKcC5g8V773QMzkeUM=
|
||||
github.com/tdewolff/parse/v2 v2.5.5/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
|
||||
go.starlark.net v0.0.0-20201006213952-227f4aabceb5/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
|
||||
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
|
||||
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
|
||||
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package googlereader // import "miniflux.app/googlereader"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"miniflux.app/http/request"
|
||||
"miniflux.app/http/response"
|
||||
"miniflux.app/http/response/json"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/model"
|
||||
"miniflux.app/storage"
|
||||
)
|
||||
|
||||
type middleware struct {
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
func newMiddleware(s *storage.Storage) *middleware {
|
||||
return &middleware{s}
|
||||
}
|
||||
|
||||
func (m *middleware) clientLogin(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := request.ClientIP(r)
|
||||
var username, password, output string
|
||||
var integration *model.Integration
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not parse form", clientIP)
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
username = r.Form.Get("Email")
|
||||
password = r.Form.Get("Passwd")
|
||||
output = r.Form.Get("output")
|
||||
|
||||
if username == "" || password == "" {
|
||||
logger.Error("[GoogleReader][Login] [ClientIP=%s] Empty username or password", clientIP)
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err = m.store.GoogleReaderUserCheckPassword(username, password); err != nil {
|
||||
logger.Error("[GoogleReader][Login] [ClientIP=%s] Invalid username or password: %s", clientIP, username)
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("[GoogleReader][Login] [ClientIP=%s] User authenticated: %s", clientIP, username)
|
||||
|
||||
if integration, err = m.store.GoogleReaderUserGetIntegration(username); err != nil {
|
||||
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not load integration: %s", clientIP, username)
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
m.store.SetLastLogin(integration.UserID)
|
||||
|
||||
token := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
|
||||
logger.Info("[GoogleReader][Login] [ClientIP=%s] Created token: %s", clientIP, token)
|
||||
result := login{SID: token, LSID: token, Auth: token}
|
||||
if output == "json" {
|
||||
json.OK(w, r, result)
|
||||
return
|
||||
}
|
||||
builder := response.New(w, r)
|
||||
builder.WithHeader("Content-Type", "text/plain; charset=UTF-8")
|
||||
builder.WithBody(result.String())
|
||||
builder.Write()
|
||||
}
|
||||
|
||||
func (m *middleware) token(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := request.ClientIP(r)
|
||||
|
||||
if !request.IsAuthenticated(r) {
|
||||
logger.Error("[GoogleReader][Token] [ClientIP=%s] User is not authenticated", clientIP)
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
token := request.GoolgeReaderToken(r)
|
||||
if token == "" {
|
||||
logger.Error("[GoogleReader][Token] [ClientIP=%s] User does not have token: %s", clientIP, request.UserID(r))
|
||||
json.Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
logger.Info("[GoogleReader][Token] [ClientIP=%s] token: %s", clientIP, token)
|
||||
w.Header().Add("Content-Type", "text/plain; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(token))
|
||||
}
|
||||
|
||||
func (m *middleware) handleCORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *middleware) apiKeyAuth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := request.ClientIP(r)
|
||||
|
||||
var token string
|
||||
if r.Method == http.MethodPost {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not parse form", clientIP)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
token = r.Form.Get("T")
|
||||
if token == "" {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Post-Form T field is empty", clientIP)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
authorization := r.Header.Get("Authorization")
|
||||
|
||||
if authorization == "" {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No token provided", clientIP)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
fields := strings.Fields(authorization)
|
||||
if len(fields) != 2 {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
if fields[0] != "GoogleLogin" {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not begin with GoogleLogin - '%s'", clientIP, authorization)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
auths := strings.Split(fields[1], "=")
|
||||
if len(auths) != 2 {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
if auths[0] != "auth" {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
token = auths[1]
|
||||
}
|
||||
|
||||
parts := strings.Split(token, "/")
|
||||
if len(parts) != 2 {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Auth token does not have the expected structure username/hash - '%s'", clientIP, token)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
var integration *model.Integration
|
||||
var user *model.User
|
||||
var err error
|
||||
if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] token: %s", clientIP, token)
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No user found with the given google reader username: %s", clientIP, parts[0])
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
|
||||
if expectedToken != token {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Token does not match: %s", clientIP, token)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
if user, err = m.store.UserByID(integration.UserID); err != nil {
|
||||
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No user found with the userID: %d", clientIP, integration.UserID)
|
||||
Unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
m.store.SetLastLogin(integration.UserID)
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
|
||||
ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
|
||||
ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
|
||||
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
|
||||
ctx = context.WithValue(ctx, request.GoogleReaderToken, token)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func getAuthToken(username, password string) string {
|
||||
token := hex.EncodeToString(hmac.New(sha1.New, []byte(username+password)).Sum(nil))
|
||||
token = username + "/" + token
|
||||
return token
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package googlereader // import "miniflux.app/googlereader"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"miniflux.app/http/response"
|
||||
"miniflux.app/logger"
|
||||
)
|
||||
|
||||
type login struct {
|
||||
SID string `json:"SID,omitempty"`
|
||||
LSID string `json:"LSID,omitempty"`
|
||||
Auth string `json:"Auth,omitempty"`
|
||||
}
|
||||
|
||||
func (l login) String() string {
|
||||
return fmt.Sprintf("SID=%s\nLSID=%s\nAuth=%s\n", l.SID, l.LSID, l.Auth)
|
||||
}
|
||||
|
||||
type userInfo struct {
|
||||
UserID string `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
UserProfileID string `json:"userProfileId"`
|
||||
UserEmail string `json:"userEmail"`
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Categories []subscriptionCategory `json:"categories"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"htmlUrl"`
|
||||
IconURL string `json:"iconUrl"`
|
||||
}
|
||||
|
||||
type quickAddResponse struct {
|
||||
NumResults int64 `json:"numResults"`
|
||||
Query string `json:"query,omitempty"`
|
||||
StreamID string `json:"streamId,omitempty"`
|
||||
StreamName string `json:"streamName,omitempty"`
|
||||
}
|
||||
|
||||
type subscriptionCategory struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
type subscriptionsResponse struct {
|
||||
Subscriptions []subscription `json:"subscriptions"`
|
||||
}
|
||||
|
||||
type itemRef struct {
|
||||
ID string `json:"id"`
|
||||
DirectStreamIDs string `json:"directStreamIds,omitempty"`
|
||||
TimestampUsec string `json:"timestampUsec,omitempty"`
|
||||
}
|
||||
|
||||
type streamIDResponse struct {
|
||||
ItemRefs []itemRef `json:"itemRefs"`
|
||||
Continuation int `json:"continuation,omitempty,string"`
|
||||
}
|
||||
|
||||
type tagsResponse struct {
|
||||
Tags []subscriptionCategory `json:"tags"`
|
||||
}
|
||||
|
||||
type streamContentItems struct {
|
||||
Direction string `json:"direction"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Self []contentHREF `json:"self"`
|
||||
Alternate []contentHREFType `json:"alternate"`
|
||||
Updated int64 `json:"updated"`
|
||||
Items []contentItem `json:"items"`
|
||||
Author string `json:"author"`
|
||||
}
|
||||
|
||||
type contentItem struct {
|
||||
ID string `json:"id"`
|
||||
Categories []string `json:"categories"`
|
||||
Title string `json:"title"`
|
||||
CrawlTimeMsec string `json:"crawlTimeMsec"`
|
||||
TimestampUsec string `json:"timestampUsec"`
|
||||
Published int64 `json:"published"`
|
||||
Updated int64 `json:"updated"`
|
||||
Author string `json:"author"`
|
||||
Alternate []contentHREFType `json:"alternate"`
|
||||
Summary contentItemContent `json:"summary"`
|
||||
Content contentItemContent `json:"content"`
|
||||
Origin contentItemOrigin `json:"origin"`
|
||||
Enclosure []contentItemEnclosure `json:"enclosure"`
|
||||
Canonical []contentHREF `json:"canonical"`
|
||||
}
|
||||
|
||||
type contentHREFType struct {
|
||||
HREF string `json:"href"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type contentHREF struct {
|
||||
HREF string `json:"href"`
|
||||
}
|
||||
|
||||
type contentItemEnclosure struct {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type contentItemContent struct {
|
||||
Direction string `json:"direction"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type contentItemOrigin struct {
|
||||
StreamID string `json:"streamId"`
|
||||
Title string `json:"title"`
|
||||
HTMLUrl string `json:"htmlUrl"`
|
||||
}
|
||||
|
||||
// Unauthorized sends a not authorized error to the client.
|
||||
func Unauthorized(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Error("[HTTP:Unauthorized] %s", r.URL)
|
||||
|
||||
builder := response.New(w, r)
|
||||
builder.WithStatus(http.StatusUnauthorized)
|
||||
builder.WithHeader("Content-Type", "text/plain")
|
||||
builder.WithHeader("X-Reader-Google-Bad-Token", "true")
|
||||
builder.WithBody("Unauthorized")
|
||||
builder.Write()
|
||||
}
|
||||
|
||||
// OK sends a ok response to the client.
|
||||
func OK(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Info("[HTTP:OK] %s", r.URL)
|
||||
|
||||
builder := response.New(w, r)
|
||||
builder.WithStatus(http.StatusOK)
|
||||
builder.WithHeader("Content-Type", "text/plain")
|
||||
builder.WithBody("OK")
|
||||
builder.Write()
|
||||
}
|
||||
+62
-29
@@ -1,16 +1,15 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/http/client"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
"miniflux.app/errors"
|
||||
"miniflux.app/logger"
|
||||
"miniflux.app/timer"
|
||||
url_helper "miniflux.app/url"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,30 +28,30 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCertificate = "Invalid SSL certificate (original error: %q)"
|
||||
errTemporaryNetworkOperation = "This website is temporarily unreachable (original error: %q)"
|
||||
errPermanentNetworkOperation = "This website is permanently unreachable (original error: %q)"
|
||||
errRequestTimeout = "Website unreachable, the request timed out after %d seconds"
|
||||
errInvalidCertificate = "Invalid SSL certificate (original error: %q)"
|
||||
errNetworkOperation = "This website is unreachable (original error: %q)"
|
||||
errRequestTimeout = "Website unreachable, the request timed out after %d seconds"
|
||||
)
|
||||
|
||||
// Client builds and executes HTTP requests.
|
||||
type Client struct {
|
||||
inputURL string
|
||||
|
||||
requestURL string
|
||||
requestEtagHeader string
|
||||
requestLastModifiedHeader string
|
||||
requestAuthorizationHeader string
|
||||
requestUsername string
|
||||
requestPassword string
|
||||
requestUserAgent string
|
||||
requestCookie string
|
||||
customHeaders map[string]string
|
||||
useProxy bool
|
||||
doNotFollowRedirects bool
|
||||
|
||||
useProxy bool
|
||||
doNotFollowRedirects bool
|
||||
|
||||
ClientTimeout int
|
||||
ClientMaxBodySize int64
|
||||
ClientProxyURL string
|
||||
ClientTimeout int
|
||||
ClientMaxBodySize int64
|
||||
ClientProxyURL string
|
||||
AllowSelfSignedCertificates bool
|
||||
}
|
||||
|
||||
// New initializes a new HTTP client.
|
||||
@@ -88,13 +86,13 @@ func (c *Client) String() string {
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
`InputURL=%q RequestURL=%q ETag=%s LastModified=%s Auth=%v UserAgent=%q`,
|
||||
`InputURL=%q ETag=%s LastMod=%s Auth=%v UserAgent=%q Verify=%v`,
|
||||
c.inputURL,
|
||||
c.requestURL,
|
||||
etagHeader,
|
||||
lastModifiedHeader,
|
||||
c.requestAuthorizationHeader != "" || (c.requestUsername != "" && c.requestPassword != ""),
|
||||
c.requestUserAgent,
|
||||
!c.AllowSelfSignedCertificates,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -113,9 +111,15 @@ func (c *Client) WithAuthorization(authorization string) *Client {
|
||||
return c
|
||||
}
|
||||
|
||||
// WithCustomHeaders defines custom HTTP headers.
|
||||
func (c *Client) WithCustomHeaders(customHeaders map[string]string) *Client {
|
||||
c.customHeaders = customHeaders
|
||||
return c
|
||||
}
|
||||
|
||||
// WithCacheHeaders defines caching headers.
|
||||
func (c *Client) WithCacheHeaders(etagHeader, lastModifiedHeader string) *Client {
|
||||
c.requestLastModifiedHeader = etagHeader
|
||||
c.requestEtagHeader = etagHeader
|
||||
c.requestLastModifiedHeader = lastModifiedHeader
|
||||
return c
|
||||
}
|
||||
@@ -140,6 +144,14 @@ func (c *Client) WithUserAgent(userAgent string) *Client {
|
||||
return c
|
||||
}
|
||||
|
||||
// WithCookie defines the Cookies to use for HTTP requests.
|
||||
func (c *Client) WithCookie(cookie string) *Client {
|
||||
if cookie != "" {
|
||||
c.requestCookie = cookie
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Get performs a GET HTTP request.
|
||||
func (c *Client) Get() (*Response, error) {
|
||||
request, err := c.buildRequest(http.MethodGet, nil)
|
||||
@@ -177,6 +189,22 @@ func (c *Client) PostJSON(data interface{}) (*Response, error) {
|
||||
return c.executeRequest(request)
|
||||
}
|
||||
|
||||
// PatchJSON performs a Patch HTTP request with a JSON payload.
|
||||
func (c *Client) PatchJSON(data interface{}) (*Response, error) {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := c.buildRequest(http.MethodPatch, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
return c.executeRequest(request)
|
||||
}
|
||||
|
||||
func (c *Client) executeRequest(request *http.Request) (*Response, error) {
|
||||
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[HttpClient] inputURL=%s", c.inputURL))
|
||||
|
||||
@@ -197,17 +225,11 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
|
||||
case x509.CertificateInvalidError, x509.HostnameError:
|
||||
err = errors.NewLocalizedError(errInvalidCertificate, uerr.Err)
|
||||
case *net.OpError:
|
||||
if uerr.Err.(*net.OpError).Temporary() {
|
||||
err = errors.NewLocalizedError(errTemporaryNetworkOperation, uerr.Err)
|
||||
} else {
|
||||
err = errors.NewLocalizedError(errPermanentNetworkOperation, uerr.Err)
|
||||
}
|
||||
err = errors.NewLocalizedError(errNetworkOperation, uerr.Err)
|
||||
case net.Error:
|
||||
nerr := uerr.Err.(net.Error)
|
||||
if nerr.Timeout() {
|
||||
err = errors.NewLocalizedError(errRequestTimeout, c.ClientTimeout)
|
||||
} else if nerr.Temporary() {
|
||||
err = errors.NewLocalizedError(errTemporaryNetworkOperation, nerr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +241,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
|
||||
return nil, fmt.Errorf("client: response too large (%d bytes)", resp.ContentLength)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client: error while reading body %v", err)
|
||||
}
|
||||
@@ -252,8 +274,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
|
||||
}
|
||||
|
||||
func (c *Client) buildRequest(method string, body io.Reader) (*http.Request, error) {
|
||||
c.requestURL = url_helper.RequestURI(c.inputURL)
|
||||
request, err := http.NewRequest(method, c.requestURL, body)
|
||||
request, err := http.NewRequest(method, c.inputURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -289,6 +310,10 @@ func (c *Client) buildClient() http.Client {
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
if c.AllowSelfSignedCertificates {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
if c.doNotFollowRedirects {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
@@ -330,6 +355,14 @@ func (c *Client) buildHeaders() http.Header {
|
||||
headers.Add("Authorization", c.requestAuthorizationHeader)
|
||||
}
|
||||
|
||||
if c.requestCookie != "" {
|
||||
headers.Add("Cookie", c.requestCookie)
|
||||
}
|
||||
|
||||
for key, value := range c.customHeaders {
|
||||
headers.Add(key, value)
|
||||
}
|
||||
|
||||
headers.Add("Connection", "close")
|
||||
return headers
|
||||
}
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
// Copyright 2020 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/http/client"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mccutchen/go-httpbin/v2/httpbin"
|
||||
)
|
||||
|
||||
var srv *httptest.Server
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
srv = httptest.NewServer(httpbin.New())
|
||||
exitCode := m.Run()
|
||||
srv.Close()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func MakeClient(path string) *Client {
|
||||
return New(fmt.Sprintf("%s%s", srv.URL, path))
|
||||
}
|
||||
|
||||
func TestClientWithDelay(t *testing.T) {
|
||||
clt := New("http://httpbin.org/delay/5")
|
||||
clt := MakeClient("/delay/5")
|
||||
clt.ClientTimeout = 1
|
||||
_, err := clt.Get()
|
||||
if err == nil {
|
||||
@@ -18,8 +35,8 @@ func TestClientWithDelay(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientWithError(t *testing.T) {
|
||||
clt := New("http://httpbin.org/status/502")
|
||||
clt.ClientTimeout = 1
|
||||
clt := MakeClient("/status/502")
|
||||
clt.ClientTimeout = 5
|
||||
response, err := clt.Get()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -30,12 +47,12 @@ func TestClientWithError(t *testing.T) {
|
||||
}
|
||||
|
||||
if !response.HasServerFailure() {
|
||||
t.Fatal(`A 500 error is considered as server failure`)
|
||||
t.Fatal(`A 502 error is considered as server failure`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientWithResponseTooLarge(t *testing.T) {
|
||||
clt := New("http://httpbin.org/bytes/100")
|
||||
clt := MakeClient("/bytes/100")
|
||||
clt.ClientMaxBodySize = 10
|
||||
_, err := clt.Get()
|
||||
if err == nil {
|
||||
@@ -44,7 +61,7 @@ func TestClientWithResponseTooLarge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientWithBasicAuth(t *testing.T) {
|
||||
clt := New("http://httpbin.org/basic-auth/testuser/testpassword")
|
||||
clt := MakeClient("/basic-auth/testuser/testpassword")
|
||||
clt.WithCredentials("testuser", "testpassword")
|
||||
_, err := clt.Get()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package client provides an HTTP client builder.
|
||||
|
||||
*/
|
||||
package client // import "miniflux.app/http/client"
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/http/client"
|
||||
|
||||
@@ -8,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@@ -43,7 +41,7 @@ func (r *Response) String() string {
|
||||
)
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the resource doesn't exists anymore.
|
||||
// IsNotFound returns true if the resource doesn't exist anymore.
|
||||
func (r *Response) IsNotFound() bool {
|
||||
return r.StatusCode == 404 || r.StatusCode == 410
|
||||
}
|
||||
@@ -87,7 +85,7 @@ func (r *Response) IsModified(etag, lastModified string) bool {
|
||||
// - Feeds with encoding specified only in XML document and not in HTTP header
|
||||
// - Feeds with wrong encoding defined and already in UTF-8
|
||||
func (r *Response) EnsureUnicodeBody() (err error) {
|
||||
buffer, err := ioutil.ReadAll(r.Body)
|
||||
buffer, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,6 +114,6 @@ func (r *Response) EnsureUnicodeBody() (err error) {
|
||||
|
||||
// BodyAsString returns the response body as string.
|
||||
func (r *Response) BodyAsString() string {
|
||||
bytes, _ := ioutil.ReadAll(r.Body)
|
||||
bytes, _ := io.ReadAll(r.Body)
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/http/client"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
@@ -96,7 +95,7 @@ func TestToString(t *testing.T) {
|
||||
r := &Response{Body: strings.NewReader(input)}
|
||||
|
||||
if r.BodyAsString() != input {
|
||||
t.Error(`Unexpected ouput`)
|
||||
t.Error(`Unexpected output`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +128,7 @@ func TestEnsureUnicodeWithHTMLDocuments(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range unicodeTestCases {
|
||||
content, err := ioutil.ReadFile("testdata/" + tc.filename)
|
||||
content, err := os.ReadFile("testdata/" + tc.filename)
|
||||
if err != nil {
|
||||
t.Fatalf(`Unable to read file %q: %v`, tc.filename, err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cookie // import "miniflux.app/http/cookie"
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package cookie provides functions to build cookies.
|
||||
|
||||
*/
|
||||
package cookie // import "miniflux.app/http/cookie"
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
@@ -10,7 +9,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FindClientIP returns client real IP address.
|
||||
// FindClientIP returns the client real IP address based on trusted Reverse-Proxy HTTP headers.
|
||||
func FindClientIP(r *http.Request) string {
|
||||
headers := []string{"X-Forwarded-For", "X-Real-Ip"}
|
||||
for _, header := range headers {
|
||||
@@ -19,6 +18,7 @@ func FindClientIP(r *http.Request) string {
|
||||
if value != "" {
|
||||
addresses := strings.Split(value, ",")
|
||||
address := strings.TrimSpace(addresses[0])
|
||||
address = dropIPv6zone(address)
|
||||
|
||||
if net.ParseIP(address) != nil {
|
||||
return address
|
||||
@@ -27,12 +27,16 @@ func FindClientIP(r *http.Request) string {
|
||||
}
|
||||
|
||||
// Fallback to TCP/IP source IP address.
|
||||
var remoteIP string
|
||||
if strings.ContainsRune(r.RemoteAddr, ':') {
|
||||
remoteIP, _, _ = net.SplitHostPort(r.RemoteAddr)
|
||||
} else {
|
||||
return FindRemoteIP(r)
|
||||
}
|
||||
|
||||
// FindRemoteIP returns remote client IP address.
|
||||
func FindRemoteIP(r *http.Request) string {
|
||||
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
remoteIP = r.RemoteAddr
|
||||
}
|
||||
remoteIP = dropIPv6zone(remoteIP)
|
||||
|
||||
// When listening on a Unix socket, RemoteAddr is empty.
|
||||
if remoteIP == "" {
|
||||
@@ -41,3 +45,11 @@ func FindClientIP(r *http.Request) string {
|
||||
|
||||
return remoteIP
|
||||
}
|
||||
|
||||
func dropIPv6zone(address string) string {
|
||||
i := strings.IndexByte(address, '%')
|
||||
if i != -1 {
|
||||
address = address[:i]
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
@@ -19,6 +18,21 @@ func TestFindClientIPWithoutHeaders(t *testing.T) {
|
||||
if ip := FindClientIP(r); ip != "192.168.0.1" {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
|
||||
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7"}
|
||||
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
|
||||
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7%eth0"}
|
||||
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
|
||||
r = &http.Request{RemoteAddr: "[fe80::14c2:f039:edc7:edc7%eth0]:4242"}
|
||||
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindClientIPWithXFFHeader(t *testing.T) {
|
||||
@@ -40,6 +54,15 @@ func TestFindClientIPWithXFFHeader(t *testing.T) {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
|
||||
// Test with single IPv6 address with zone
|
||||
headers = http.Header{}
|
||||
headers.Set("X-Forwarded-For", "fe80::14c2:f039:edc7:edc7%eth0")
|
||||
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
|
||||
|
||||
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
|
||||
t.Fatalf(`Unexpected result, got: %q`, ip)
|
||||
}
|
||||
|
||||
// Test with single IPv4 address.
|
||||
headers = http.Header{}
|
||||
headers.Set("X-Forwarded-For", "70.41.3.18")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
@@ -25,8 +24,14 @@ const (
|
||||
FlashErrorMessageContextKey
|
||||
PocketRequestTokenContextKey
|
||||
ClientIPContextKey
|
||||
GoogleReaderToken
|
||||
)
|
||||
|
||||
// GoolgeReaderToken returns the google reader token if it exists.
|
||||
func GoolgeReaderToken(r *http.Request) string {
|
||||
return getContextStringValue(r, GoogleReaderToken)
|
||||
}
|
||||
|
||||
// IsAdminUser checks if the logged user is administrator.
|
||||
func IsAdminUser(r *http.Request) bool {
|
||||
return getContextBoolValue(r, IsAdminUserContextKey)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package request contains helper functions to work with the HTTP request.
|
||||
|
||||
*/
|
||||
package request // import "miniflux.app/http/request"
|
||||
+17
-4
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
@@ -72,7 +71,21 @@ func QueryStringParamList(r *http.Request, param string) []string {
|
||||
|
||||
// QueryIntParam returns a query string parameter as integer.
|
||||
func QueryIntParam(r *http.Request, param string, defaultValue int) int {
|
||||
return int(QueryInt64Param(r, param, int64(defaultValue)))
|
||||
value := r.URL.Query().Get(param)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(value, 10, 0)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
if val < 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return int(val)
|
||||
}
|
||||
|
||||
// QueryInt64Param returns a query string parameter as int64.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "miniflux.app/http/request"
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package response // import "miniflux.app/http/response"
|
||||
|
||||
@@ -12,6 +11,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"miniflux.app/logger"
|
||||
)
|
||||
|
||||
const compressionThreshold = 1024
|
||||
@@ -88,7 +89,10 @@ func (b *Builder) Write() {
|
||||
case io.Reader:
|
||||
// Compression not implemented in this case
|
||||
b.writeHeaders()
|
||||
io.Copy(b.w, v)
|
||||
_, err := io.Copy(b.w, v)
|
||||
if err != nil {
|
||||
logger.Error("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +100,7 @@ func (b *Builder) writeHeaders() {
|
||||
b.headers["X-XSS-Protection"] = "1; mode=block"
|
||||
b.headers["X-Content-Type-Options"] = "nosniff"
|
||||
b.headers["X-Frame-Options"] = "DENY"
|
||||
b.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; media-src *; frame-src *"
|
||||
b.headers["Referrer-Policy"] = "no-referrer"
|
||||
|
||||
for key, value := range b.headers {
|
||||
b.w.Header().Set(key, value)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package response // import "miniflux.app/http/response"
|
||||
|
||||
@@ -29,10 +28,9 @@ func TestResponseHasCommonHeaders(t *testing.T) {
|
||||
resp := w.Result()
|
||||
|
||||
headers := map[string]string{
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"Content-Security-Policy": "default-src 'self'; img-src *; media-src *; frame-src *",
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
}
|
||||
|
||||
for header, expected := range headers {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package response contains everything related to HTTP responses.
|
||||
|
||||
*/
|
||||
package response // import "miniflux.app/http/response"
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package html contains HTML response functions.
|
||||
|
||||
*/
|
||||
package html // import "miniflux.app/http/response/html"
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package html // import "miniflux.app/http/response/html"
|
||||
|
||||
@@ -26,6 +25,7 @@ func ServerError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
|
||||
builder := response.New(w, r)
|
||||
builder.WithStatus(http.StatusInternalServerError)
|
||||
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
|
||||
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
|
||||
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
|
||||
builder.WithBody(err)
|
||||
@@ -38,6 +38,7 @@ func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
|
||||
|
||||
builder := response.New(w, r)
|
||||
builder.WithStatus(http.StatusBadRequest)
|
||||
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
|
||||
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
|
||||
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
|
||||
builder.WithBody(err)
|
||||
@@ -72,3 +73,16 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
|
||||
http.Redirect(w, r, uri, http.StatusFound)
|
||||
}
|
||||
|
||||
// RequestedRangeNotSatisfiable sends a range not satisfiable error to the client.
|
||||
func RequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, contentRange string) {
|
||||
logger.Error("[HTTP:Range Not Satisfiable] %s", r.URL)
|
||||
|
||||
builder := response.New(w, r)
|
||||
builder.WithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
|
||||
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
|
||||
builder.WithHeader("Content-Range", contentRange)
|
||||
builder.WithBody("Range Not Satisfiable")
|
||||
builder.Write()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package html // import "miniflux.app/http/response/html"
|
||||
|
||||
@@ -210,3 +209,32 @@ func TestRedirectResponse(t *testing.T) {
|
||||
t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestedRangeNotSatisfiable(t *testing.T) {
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
RequestedRangeNotSatisfiable(w, r, "bytes */12777")
|
||||
})
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
|
||||
resp := w.Result()
|
||||
defer resp.Body.Close()
|
||||
|
||||
expectedStatusCode := http.StatusRequestedRangeNotSatisfiable
|
||||
if resp.StatusCode != expectedStatusCode {
|
||||
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
|
||||
}
|
||||
|
||||
expectedContentRangeHeader := "bytes */12777"
|
||||
actualContentRangeHeader := resp.Header.Get("Content-Range")
|
||||
if actualContentRangeHeader != expectedContentRangeHeader {
|
||||
t.Fatalf(`Unexpected content range header, got %q instead of %q`, actualContentRangeHeader, expectedContentRangeHeader)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user