mirror of https://github.com/miniflux/v2.git
Compare commits
No commits in common. "main" and "2.0.1" have entirely different histories.
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"name": "Miniflux",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
"remoteUser": "vscode",
|
||||
"forwardPorts": [
|
||||
8080
|
||||
],
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"golang.go",
|
||||
"rangav.vscode-thunder-client",
|
||||
"GitHub.codespaces",
|
||||
"GitHub.copilot",
|
||||
"GitHub.copilot-chat"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
image: mcr.microsoft.com/devcontainers/go:1.22
|
||||
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
|
||||
apprise:
|
||||
image: caronc/apprise:1.0
|
||||
restart: unless-stopped
|
||||
hostname: apprise
|
||||
volumes:
|
||||
postgres-data: null
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
blank_issues_enabled: false
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
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
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: Feed Problems
|
||||
about: Problems with a feed or a website
|
||||
title: ''
|
||||
labels: feed problems, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/packaging/docker/distroless"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "packaging/debian"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "packaging/rpm"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "fguillot"
|
||||
assignees:
|
||||
- "fguillot"
|
|
@ -1,7 +0,0 @@
|
|||
Do you follow the guidelines?
|
||||
|
||||
- [ ] I have tested my changes
|
||||
- [ ] There is no breaking changes
|
||||
- [ ] I really tested my changes and there is no regression
|
||||
- [ ] Ideally, my commit messages use the same convention as the Go project: https://go.dev/doc/contribute#commit_messages
|
||||
- [ ] I read this document: https://miniflux.app/faq.html#pull-request
|
|
@ -1,29 +0,0 @@
|
|||
name: Build Binaries
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
check-latest: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Compile binaries
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
run: make build
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
path: miniflux-*
|
||||
if-no-files-found: error
|
||||
retention-days: 5
|
|
@ -1,43 +0,0 @@
|
|||
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@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
|
@ -1,82 +0,0 @@
|
|||
name: Debian Packages
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
test-packages:
|
||||
if: github.event.pull_request
|
||||
name: Test Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
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
|
||||
build-packages-manually:
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'push'
|
||||
name: Build Packages Manually
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
id: buildx
|
||||
with:
|
||||
install: true
|
||||
- name: Available Docker Platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Build Debian Packages
|
||||
run: make debian-packages
|
||||
- name: Upload package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: "*.deb"
|
||||
if-no-files-found: error
|
||||
retention-days: 3
|
||||
publish-packages:
|
||||
if: github.event_name == 'push'
|
||||
name: Publish Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
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
|
|
@ -1,95 +0,0 @@
|
|||
name: Docker
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
docker-images:
|
||||
name: Docker Images
|
||||
permissions:
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate Alpine Docker tags
|
||||
id: docker_alpine_tags
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
docker.io/${{ github.repository_owner }}/miniflux
|
||||
ghcr.io/${{ github.repository_owner }}/miniflux
|
||||
quay.io/${{ github.repository_owner }}/miniflux
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=schedule,pattern=nightly
|
||||
type=semver,pattern={{raw}}
|
||||
|
||||
- name: Generate Distroless Docker tags
|
||||
id: docker_distroless_tags
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
docker.io/${{ github.repository_owner }}/miniflux
|
||||
ghcr.io/${{ github.repository_owner }}/miniflux
|
||||
quay.io/${{ github.repository_owner }}/miniflux
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=schedule,pattern=nightly
|
||||
type=semver,pattern={{raw}}
|
||||
flavor: |
|
||||
suffix=-distroless,onlatest=true
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Quay Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_TOKEN }}
|
||||
|
||||
- name: Build and Push Alpine images
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/alpine/Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_alpine_tags.outputs.tags }}
|
||||
|
||||
- name: Build and Push Distroless images
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./packaging/docker/distroless/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_distroless_tags.outputs.tags }}
|
|
@ -1,39 +0,0 @@
|
|||
name: Linters
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
jshint:
|
||||
name: Javascript Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install linters
|
||||
run: |
|
||||
sudo npm install -g jshint@2.13.6 eslint@8.57.0
|
||||
- name: Run jshint
|
||||
run: jshint internal/ui/static/js/*.js
|
||||
- name: Run ESLint
|
||||
run: eslint internal/ui/static/js/*.js
|
||||
|
||||
golangci:
|
||||
name: Golang Linters
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
- run: "go vet ./..."
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
args: --timeout 10m --skip-dirs tests --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace --enable gocritic
|
||||
- uses: dominikh/staticcheck-action@v1.3.1
|
||||
with:
|
||||
version: "2023.1.7"
|
||||
install-go: false
|
|
@ -1,55 +0,0 @@
|
|||
name: RPM Packages
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
test-package:
|
||||
if: github.event.pull_request
|
||||
name: Test Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build RPM Package
|
||||
run: make rpm
|
||||
- name: List generated files
|
||||
run: ls -l *.rpm
|
||||
build-package-manually:
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'push'
|
||||
name: Build Packages Manually
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build RPM Package
|
||||
run: make rpm
|
||||
- name: Upload package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: "*.rpm"
|
||||
if-no-files-found: error
|
||||
retention-days: 3
|
||||
publish-package:
|
||||
if: github.event_name == 'push'
|
||||
name: Publish Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
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
|
|
@ -1,56 +0,0 @@
|
|||
name: Tests
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
go-version: ["1.22.x"]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Run unit tests
|
||||
run: make test
|
||||
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:9.5
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Postgres client
|
||||
run: sudo apt update && sudo apt install -y postgresql-client
|
||||
- name: Run integration tests
|
||||
run: make integration-test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PGHOST: 127.0.0.1
|
||||
PGPASSWORD: postgres
|
|
@ -1,6 +1,4 @@
|
|||
miniflux-*
|
||||
./miniflux
|
||||
*.rpm
|
||||
*.deb
|
||||
.idea
|
||||
.vscode
|
||||
miniflux-linux-amd64
|
||||
miniflux-linux-arm*
|
||||
miniflux-darwin-amd64
|
||||
miniflux-test
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
notifications:
|
||||
email: false
|
||||
services:
|
||||
- postgresql
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
language: go
|
||||
go:
|
||||
- 1.9
|
||||
before_install:
|
||||
- npm install -g jshint
|
||||
- go get -u github.com/golang/lint/golint
|
||||
script:
|
||||
- jshint ui/static/js/app.js
|
||||
- make lint
|
||||
- make test
|
||||
- make integration-test
|
|
@ -0,0 +1,105 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
packages = ["."]
|
||||
revision = "e1271ee34c6a305e38566ecd27ae374944907ee9"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/andybalholm/cascadia"
|
||||
packages = ["."]
|
||||
revision = "349dd0209470eabd9514242c688c403c0926d266"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
revision = "7f08801859139f86dfafd1c296e2cba9a80d292e"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [".","hstore","oid"]
|
||||
revision = "83612a56d3dd153a94a629cd64925371c9adad78"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/miniflux/miniflux-go"
|
||||
packages = ["."]
|
||||
revision = "887ba3b062946784f0e64edb1734f435beb204f9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tdewolff/minify"
|
||||
packages = [".","css","js"]
|
||||
revision = "222672169d634c440a73abc47685074e1a9daa60"
|
||||
version = "v2.3.4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tdewolff/parse"
|
||||
packages = [".","buffer","css","js","strconv"]
|
||||
revision = "639f6272aec6b52094db77b9ec488214b0b4b1a1"
|
||||
version = "v2.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/tomasen/realip"
|
||||
packages = ["."]
|
||||
revision = "b5850897b7b539a1c9f22cdaa3b547d1bd453db8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["acme","acme/autocert","bcrypt","blowfish","ssh/terminal"]
|
||||
revision = "94eea52f7b742c7cbe0b03b22f0c4c8631ece122"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp","html","html/atom","html/charset"]
|
||||
revision = "d866cfc389cec985d6fda2859936a575a55a3ab6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [".","internal"]
|
||||
revision = "462316686f20eb6df426961c1c131bdaa5dfa68e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix","windows"]
|
||||
revision = "571f7bbbe08da2a8955aed9d4db316e78630e9a3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/utf8internal","language","runes","transform","unicode/cldr"]
|
||||
revision = "d5a9226ed7dd70cade6ccae9d37517fe14dd9fee"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "338222e5111416c46b2b8bde149443abc542b386dd02aff2a0dd6e13334bcf28"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.6.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/miniflux/miniflux-go"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tdewolff/minify"
|
||||
version = "2.3.3"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/tomasen/realip"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
182
Makefile
182
Makefile
|
@ -1,177 +1,53 @@
|
|||
APP := miniflux
|
||||
DOCKER_IMAGE := miniflux/miniflux
|
||||
VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null)
|
||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
||||
BUILD_DATE := `date +%FT%T%z`
|
||||
LD_FLAGS := "-s -w -X 'miniflux.app/v2/internal/version.Version=$(VERSION)' -X 'miniflux.app/v2/internal/version.Commit=$(COMMIT)' -X 'miniflux.app/v2/internal/version.BuildDate=$(BUILD_DATE)'"
|
||||
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
|
||||
DB_URL := postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
|
||||
DOCKER_PLATFORM := amd64
|
||||
APP := miniflux
|
||||
VERSION=$(shell git rev-parse --short HEAD)
|
||||
BUILD_DATE=`date +%FT%T%z`
|
||||
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
|
||||
DB_URL := postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
|
||||
|
||||
export PGPASSWORD := postgres
|
||||
.PHONY: linux linux-arm darwin build run clean test lint integration-test clean-integration-test
|
||||
|
||||
.PHONY: \
|
||||
miniflux \
|
||||
miniflux-no-pie \
|
||||
linux-amd64 \
|
||||
linux-arm64 \
|
||||
linux-armv7 \
|
||||
linux-armv6 \
|
||||
linux-armv5 \
|
||||
linux-x86 \
|
||||
darwin-amd64 \
|
||||
darwin-arm64 \
|
||||
freebsd-amd64 \
|
||||
freebsd-x86 \
|
||||
openbsd-amd64 \
|
||||
openbsd-x86 \
|
||||
netbsd-x86 \
|
||||
netbsd-amd64 \
|
||||
windows-amd64 \
|
||||
windows-x86 \
|
||||
build \
|
||||
run \
|
||||
clean \
|
||||
test \
|
||||
lint \
|
||||
integration-test \
|
||||
clean-integration-test \
|
||||
docker-image \
|
||||
docker-image-distroless \
|
||||
docker-images \
|
||||
rpm \
|
||||
debian \
|
||||
debian-packages
|
||||
linux:
|
||||
@ go generate
|
||||
@ GOOS=linux GOARCH=amd64 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-linux-amd64 main.go
|
||||
|
||||
miniflux:
|
||||
@ go build -buildmode=pie -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
||||
linux-arm:
|
||||
@ go generate
|
||||
@ GOOS=linux GOARCH=arm64 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-linux-armv8 main.go
|
||||
@ GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-linux-armv7 main.go
|
||||
@ GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-linux-armv6 main.go
|
||||
@ GOOS=linux GOARCH=arm GOARM=5 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-linux-armv5 main.go
|
||||
|
||||
miniflux-no-pie:
|
||||
@ go build -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
||||
darwin:
|
||||
@ go generate
|
||||
@ GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'github.com/miniflux/miniflux/version.Version=$(VERSION)' -X 'github.com/miniflux/miniflux/version.BuildDate=$(BUILD_DATE)'" -o $(APP)-darwin-amd64 main.go
|
||||
|
||||
linux-amd64:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-arm64:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv7:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv6:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-armv5:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
darwin-amd64:
|
||||
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
darwin-arm64:
|
||||
@ GOOS=darwin GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
freebsd-amd64:
|
||||
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
openbsd-amd64:
|
||||
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
windows-amd64:
|
||||
@ GOOS=windows GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@.exe main.go
|
||||
|
||||
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:
|
||||
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
linux-x86:
|
||||
@ CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
freebsd-x86:
|
||||
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
netbsd-x86:
|
||||
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
openbsd-x86:
|
||||
@ GOOS=openbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
|
||||
|
||||
windows-x86:
|
||||
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@.exe main.go
|
||||
build: linux linux-arm darwin
|
||||
|
||||
run:
|
||||
@ LOG_DATE_TIME=1 LOG_LEVEL=debug RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go
|
||||
@ go generate
|
||||
@ go run main.go
|
||||
|
||||
clean:
|
||||
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe
|
||||
@ rm -f $(APP)-*
|
||||
|
||||
test:
|
||||
go test -cover -race -count=1 ./...
|
||||
go test -cover -race ./...
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
staticcheck ./...
|
||||
golangci-lint run --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace
|
||||
@ golint -set_exit_status ${PKG_LIST}
|
||||
|
||||
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) \
|
||||
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
|
||||
|
||||
TEST_MINIFLUX_BASE_URL=http://127.0.0.1:8080 \
|
||||
TEST_MINIFLUX_ADMIN_USERNAME=admin \
|
||||
TEST_MINIFLUX_ADMIN_PASSWORD=test123 \
|
||||
go test -v -count=1 ./internal/api
|
||||
DATABASE_URL=$(DB_URL) ./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||
while ! echo exit | nc localhost 8080; do sleep 1; done >/dev/null
|
||||
go test -v -tags=integration || cat /tmp/miniflux.log
|
||||
|
||||
clean-integration-test:
|
||||
@ kill -9 `cat /tmp/miniflux.pid`
|
||||
@ rm -f /tmp/miniflux.pid /tmp/miniflux.log
|
||||
@ rm miniflux-test
|
||||
@ psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||
|
||||
docker-image:
|
||||
docker build --pull -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/alpine/Dockerfile \
|
||||
--tag $(DOCKER_IMAGE):$(VERSION) \
|
||||
--push .
|
||||
|
||||
rpm: clean
|
||||
@ docker build \
|
||||
-t miniflux-rpm-builder \
|
||||
-f packaging/rpm/Dockerfile \
|
||||
.
|
||||
@ docker run --rm \
|
||||
-v ${PWD}:/root/rpmbuild/RPMS/x86_64 miniflux-rpm-builder \
|
||||
rpmbuild -bb --define "_miniflux_version $(VERSION)" /root/rpmbuild/SPECS/miniflux.spec
|
||||
|
||||
debian:
|
||||
@ docker buildx build --load \
|
||||
--platform linux/$(DOCKER_PLATFORM) \
|
||||
-t miniflux-deb-builder \
|
||||
-f packaging/debian/Dockerfile \
|
||||
.
|
||||
@ docker run --rm --platform linux/$(DOCKER_PLATFORM) \
|
||||
-v ${PWD}:/pkg miniflux-deb-builder
|
||||
|
||||
debian-packages: clean
|
||||
$(MAKE) debian DOCKER_PLATFORM=amd64
|
||||
$(MAKE) debian DOCKER_PLATFORM=arm64
|
||||
$(MAKE) debian DOCKER_PLATFORM=arm/v7
|
||||
|
|
42
README.md
42
README.md
|
@ -1,5 +1,8 @@
|
|||
Miniflux 2
|
||||
==========
|
||||
[![Build Status](https://travis-ci.org/miniflux/miniflux.svg?branch=master)](https://travis-ci.org/miniflux/miniflux)
|
||||
[![GoDoc](https://godoc.org/github.com/miniflux/miniflux?status.svg)](https://godoc.org/github.com/miniflux/miniflux)
|
||||
[![Documentation Status](https://readthedocs.org/projects/miniflux/badge/?version=latest)](https://docs.miniflux.net/)
|
||||
|
||||
Miniflux is a minimalist and opinionated feed reader:
|
||||
|
||||
|
@ -8,47 +11,26 @@ Miniflux is a minimalist and opinionated feed reader:
|
|||
- Doesn't use any ORM
|
||||
- Doesn't use any complicated framework
|
||||
- Use only modern vanilla Javascript (ES6 and Fetch API)
|
||||
- Single binary compiled statically without dependency
|
||||
- The number of features is voluntarily limited
|
||||
|
||||
It's simple, fast, lightweight and super easy to install.
|
||||
|
||||
Official website: <https://miniflux.app>
|
||||
Miniflux 2 is a rewrite of [Miniflux 1.x](https://github.com/miniflux/miniflux-legacy) in Golang.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The Miniflux documentation is available here: <https://miniflux.app/docs/> ([Man page](https://miniflux.app/miniflux.1.html))
|
||||
The Miniflux documentation is available here: <https://docs.miniflux.net/>
|
||||
|
||||
- [Opinionated?](https://miniflux.app/opinionated.html)
|
||||
- [Features](https://miniflux.app/features.html)
|
||||
- [Requirements](https://miniflux.app/docs/requirements.html)
|
||||
- [Installation Instructions](https://miniflux.app/docs/installation.html)
|
||||
- [Upgrading to a New Version](https://miniflux.app/docs/upgrade.html)
|
||||
- [Configuration](https://miniflux.app/docs/configuration.html)
|
||||
- [Command Line Usage](https://miniflux.app/docs/cli.html)
|
||||
- [User Interface Usage](https://miniflux.app/docs/ui.html)
|
||||
- [Keyboard Shortcuts](https://miniflux.app/docs/keyboard_shortcuts.html)
|
||||
- [Integration with External Services](https://miniflux.app/docs/services.html)
|
||||
- [Rewrite and Scraper Rules](https://miniflux.app/docs/rules.html)
|
||||
- [API Reference](https://miniflux.app/docs/api.html)
|
||||
- [Development](https://miniflux.app/docs/development.html)
|
||||
- [Internationalization](https://miniflux.app/docs/i18n.html)
|
||||
- [Frequently Asked Questions](https://miniflux.app/faq.html)
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
Default theme:
|
||||
|
||||
![Default theme](https://miniflux.app/images/overview.png)
|
||||
|
||||
Dark theme when using keyboard navigation:
|
||||
|
||||
![Dark theme](https://miniflux.app/images/item-selection-black-theme.png)
|
||||
- [Opinionated?](https://docs.miniflux.net/en/latest/opinionated.html)
|
||||
- [Features](https://docs.miniflux.net/en/latest/features.html)
|
||||
- [Requirements](https://docs.miniflux.net/en/latest/requirements.html)
|
||||
- [Installation](https://docs.miniflux.net/en/latest/installation.html)
|
||||
- [Upgrading to a new version](https://docs.miniflux.net/en/latest/upgrade.html)
|
||||
- [Configuration](https://docs.miniflux.net/en/latest/configuration.html)
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
- Authors: Frédéric Guillot - [List of contributors](https://github.com/miniflux/v2/graphs/contributors)
|
||||
- Author: Frédéric Guillot
|
||||
- Distributed under Apache 2.0 License
|
||||
|
|
11
SECURITY.md
11
SECURITY.md
|
@ -1,11 +0,0 @@
|
|||
# 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.
|
|
@ -0,0 +1,103 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
)
|
||||
|
||||
// CreateCategory is the API handler to create a new category.
|
||||
func (c *Controller) CreateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
category, err := decodeCategoryPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
category.UserID = userID
|
||||
if err := category.ValidateCategoryCreation(); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if c, err := c.store.CategoryByTitle(userID, category.Title); err != nil || c != nil {
|
||||
response.JSON().BadRequest(errors.New("This category already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.CreateCategory(category)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to create this category"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Created(category)
|
||||
}
|
||||
|
||||
// UpdateCategory is the API handler to update a category.
|
||||
func (c *Controller) UpdateCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
categoryID, err := request.IntegerParam("categoryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
category, err := decodeCategoryPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
category.UserID = ctx.UserID()
|
||||
category.ID = categoryID
|
||||
if err := category.ValidateCategoryModification(); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.UpdateCategory(category)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to update this category"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Created(category)
|
||||
}
|
||||
|
||||
// GetCategories is the API handler to get a list of categories for a given user.
|
||||
func (c *Controller) GetCategories(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
categories, err := c.store.Categories(ctx.UserID())
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch categories"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(categories)
|
||||
}
|
||||
|
||||
// RemoveCategory is the API handler to remove a category.
|
||||
func (c *Controller) RemoveCategory(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
categoryID, err := request.IntegerParam("categoryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.store.CategoryExists(userID, categoryID) {
|
||||
response.JSON().NotFound(errors.New("Category not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.RemoveCategory(userID, categoryID); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to remove this category"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux/reader/feed"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
)
|
||||
|
||||
// Controller holds all handlers for the API.
|
||||
type Controller struct {
|
||||
store *storage.Storage
|
||||
feedHandler *feed.Handler
|
||||
}
|
||||
|
||||
// NewController creates a new controller.
|
||||
func NewController(store *storage.Storage, feedHandler *feed.Handler) *Controller {
|
||||
return &Controller{store: store, feedHandler: feedHandler}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package api implements API endpoints for Miniflux application.
|
||||
|
||||
*/
|
||||
package api
|
|
@ -0,0 +1,223 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
)
|
||||
|
||||
// GetFeedEntry is the API handler to get a single feed entry.
|
||||
func (c *Controller) GetFeedEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this entry from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.JSON().NotFound(errors.New("Entry not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(entry)
|
||||
}
|
||||
|
||||
// GetEntry is the API handler to get a single entry.
|
||||
func (c *Controller) GetEntry(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this entry from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.JSON().NotFound(errors.New("Entry not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(entry)
|
||||
}
|
||||
|
||||
// GetFeedEntries is the API handler to get all feed entries.
|
||||
func (c *Controller) GetFeedEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
status := request.QueryStringParam("status", "")
|
||||
if status != "" {
|
||||
if err := model.ValidateEntryStatus(status); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
order := request.QueryStringParam("order", model.DefaultSortingOrder)
|
||||
if err := model.ValidateEntryOrder(order); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
direction := request.QueryStringParam("direction", model.DefaultSortingDirection)
|
||||
if err := model.ValidateDirection(direction); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
limit := request.QueryIntegerParam("limit", 100)
|
||||
offset := request.QueryIntegerParam("offset", 0)
|
||||
if err := model.ValidateRange(offset, limit); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithStatus(status)
|
||||
builder.WithOrder(order)
|
||||
builder.WithDirection(direction)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(limit)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch the list of entries"))
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to count the number of entries"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(&entriesResponse{Total: count, Entries: entries})
|
||||
}
|
||||
|
||||
// GetEntries is the API handler to fetch entries.
|
||||
func (c *Controller) GetEntries(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
|
||||
status := request.QueryStringParam("status", "")
|
||||
if status != "" {
|
||||
if err := model.ValidateEntryStatus(status); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
order := request.QueryStringParam("order", model.DefaultSortingOrder)
|
||||
if err := model.ValidateEntryOrder(order); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
direction := request.QueryStringParam("direction", model.DefaultSortingDirection)
|
||||
if err := model.ValidateDirection(direction); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
limit := request.QueryIntegerParam("limit", 100)
|
||||
offset := request.QueryIntegerParam("offset", 0)
|
||||
if err := model.ValidateRange(offset, limit); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.NewEntryQueryBuilder(userID)
|
||||
builder.WithStatus(status)
|
||||
builder.WithOrder(order)
|
||||
builder.WithDirection(direction)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(limit)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch the list of entries"))
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to count the number of entries"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(&entriesResponse{Total: count, Entries: entries})
|
||||
}
|
||||
|
||||
// SetEntryStatus is the API handler to change the status of entries.
|
||||
func (c *Controller) SetEntryStatus(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
|
||||
entryIDs, status, err := decodeEntryStatusPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(errors.New("Invalid JSON payload"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.ValidateEntryStatus(status); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.SetEntriesStatus(userID, entryIDs, status); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to change entries status"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
||||
|
||||
// ToggleBookmark is the API handler to toggle bookmark status.
|
||||
func (c *Controller) ToggleBookmark(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.ToggleBookmark(userID, entryID); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to toggle bookmark value"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miniflux/miniflux/reader/opml"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
)
|
||||
|
||||
// CreateFeed is the API handler to create a new feed.
|
||||
func (c *Controller) CreateFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedURL, categoryID, err := decodeFeedCreationPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if feedURL == "" {
|
||||
response.JSON().BadRequest(errors.New("The feed_url is required"))
|
||||
return
|
||||
}
|
||||
|
||||
if categoryID <= 0 {
|
||||
response.JSON().BadRequest(errors.New("The category_id is required"))
|
||||
return
|
||||
}
|
||||
|
||||
if c.store.FeedURLExists(userID, feedURL) {
|
||||
response.JSON().BadRequest(errors.New("This feed_url already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
if !c.store.CategoryExists(userID, categoryID) {
|
||||
response.JSON().BadRequest(errors.New("This category_id doesn't exists or doesn't belongs to this user"))
|
||||
return
|
||||
}
|
||||
|
||||
feed, err := c.feedHandler.CreateFeed(userID, categoryID, feedURL, false)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to create this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
type result struct {
|
||||
FeedID int64 `json:"feed_id"`
|
||||
}
|
||||
|
||||
response.JSON().Created(&result{FeedID: feed.ID})
|
||||
}
|
||||
|
||||
// RefreshFeed is the API handler to refresh a feed.
|
||||
func (c *Controller) RefreshFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.store.FeedExists(userID, feedID) {
|
||||
response.JSON().NotFound(errors.New("Unable to find this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.feedHandler.RefreshFeed(userID, feedID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to refresh this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
||||
|
||||
// UpdateFeed is the API handler that is used to update a feed.
|
||||
func (c *Controller) UpdateFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
newFeed, err := decodeFeedModificationPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if newFeed.Category != nil && newFeed.Category.ID != 0 && !c.store.CategoryExists(userID, newFeed.Category.ID) {
|
||||
response.JSON().BadRequest(errors.New("This category_id doesn't exists or doesn't belongs to this user"))
|
||||
return
|
||||
}
|
||||
|
||||
originalFeed, err := c.store.FeedByID(userID, feedID)
|
||||
if err != nil {
|
||||
response.JSON().NotFound(errors.New("Unable to find this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
if originalFeed == nil {
|
||||
response.JSON().NotFound(errors.New("Feed not found"))
|
||||
return
|
||||
}
|
||||
|
||||
originalFeed.Merge(newFeed)
|
||||
if err := c.store.UpdateFeed(originalFeed); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to update this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
originalFeed, err = c.store.FeedByID(userID, feedID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Created(originalFeed)
|
||||
}
|
||||
|
||||
// GetFeeds is the API handler that get all feeds that belongs to the given user.
|
||||
func (c *Controller) GetFeeds(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
feeds, err := c.store.Feeds(ctx.UserID())
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch feeds from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(feeds)
|
||||
}
|
||||
|
||||
// Export is the API handler that incoves an OPML export.
|
||||
func (c *Controller) Export(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
opmlHandler := opml.NewHandler(c.store)
|
||||
|
||||
opml, err := opmlHandler.Export(ctx.LoggedUser().ID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("unable to export feeds to OPML"))
|
||||
}
|
||||
|
||||
response.XML().Serve(opml)
|
||||
}
|
||||
|
||||
// GetFeed is the API handler to get a feed.
|
||||
func (c *Controller) GetFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
feed, err := c.store.FeedByID(userID, feedID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
if feed == nil {
|
||||
response.JSON().NotFound(errors.New("Feed not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(feed)
|
||||
}
|
||||
|
||||
// RemoveFeed is the API handler to remove a feed.
|
||||
func (c *Controller) RemoveFeed(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.store.FeedExists(userID, feedID) {
|
||||
response.JSON().NotFound(errors.New("Feed not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.RemoveFeed(userID, feedID); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to remove this feed"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
)
|
||||
|
||||
// FeedIcon returns a feed icon.
|
||||
func (c *Controller) FeedIcon(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
userID := ctx.UserID()
|
||||
feedID, err := request.IntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.store.HasIcon(feedID) {
|
||||
response.JSON().NotFound(errors.New("This feed doesn't have any icon"))
|
||||
return
|
||||
}
|
||||
|
||||
icon, err := c.store.IconByFeedID(userID, feedID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch feed icon"))
|
||||
return
|
||||
}
|
||||
|
||||
if icon == nil {
|
||||
response.JSON().NotFound(errors.New("This feed doesn't have any icon"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(&feedIcon{
|
||||
ID: icon.ID,
|
||||
MimeType: icon.MimeType,
|
||||
Data: icon.DataURL(),
|
||||
})
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/miniflux/miniflux/model"
|
||||
)
|
||||
|
||||
type feedIcon struct {
|
||||
ID int64 `json:"id"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type entriesResponse struct {
|
||||
Total int `json:"total"`
|
||||
Entries model.Entries `json:"entries"`
|
||||
}
|
||||
|
||||
func decodeUserPayload(data io.Reader) (*model.User, error) {
|
||||
var user model.User
|
||||
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&user); err != nil {
|
||||
return nil, fmt.Errorf("Unable to decode user JSON object: %v", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func decodeURLPayload(data io.Reader) (string, error) {
|
||||
type payload struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
var p payload
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&p); err != nil {
|
||||
return "", fmt.Errorf("invalid JSON payload: %v", err)
|
||||
}
|
||||
|
||||
return p.URL, nil
|
||||
}
|
||||
|
||||
func decodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
|
||||
type payload struct {
|
||||
EntryIDs []int64 `json:"entry_ids"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
var p payload
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&p); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid JSON payload: %v", err)
|
||||
}
|
||||
|
||||
return p.EntryIDs, p.Status, nil
|
||||
}
|
||||
|
||||
func decodeFeedCreationPayload(data io.Reader) (string, int64, error) {
|
||||
type payload struct {
|
||||
FeedURL string `json:"feed_url"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
}
|
||||
|
||||
var p payload
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&p); err != nil {
|
||||
return "", 0, fmt.Errorf("invalid JSON payload: %v", err)
|
||||
}
|
||||
|
||||
return p.FeedURL, p.CategoryID, nil
|
||||
}
|
||||
|
||||
func decodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
|
||||
var feed model.Feed
|
||||
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&feed); err != nil {
|
||||
return nil, fmt.Errorf("Unable to decode feed JSON object: %v", err)
|
||||
}
|
||||
|
||||
return &feed, nil
|
||||
}
|
||||
|
||||
func decodeCategoryPayload(data io.Reader) (*model.Category, error) {
|
||||
var category model.Category
|
||||
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&category); err != nil {
|
||||
return nil, fmt.Errorf("Unable to decode category JSON object: %v", err)
|
||||
}
|
||||
|
||||
return &category, nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
"github.com/miniflux/miniflux/reader/subscription"
|
||||
)
|
||||
|
||||
// GetSubscriptions is the API handler to find subscriptions.
|
||||
func (c *Controller) GetSubscriptions(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
websiteURL, err := decodeURLPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptions, err := subscription.FindSubscriptions(websiteURL)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to discover subscriptions"))
|
||||
return
|
||||
}
|
||||
|
||||
if subscriptions == nil {
|
||||
response.JSON().NotFound(fmt.Errorf("No subscription found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(subscriptions)
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
// 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.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miniflux/miniflux/http/handler"
|
||||
)
|
||||
|
||||
// CreateUser is the API handler to create a new user.
|
||||
func (c *Controller) CreateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
user, err := decodeUserPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.ValidateUserCreation(); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.store.UserExists(user.Username) {
|
||||
response.JSON().BadRequest(errors.New("This user already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.CreateUser(user)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to create this user"))
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = ""
|
||||
response.JSON().Created(user)
|
||||
}
|
||||
|
||||
// UpdateUser is the API handler to update the given user.
|
||||
func (c *Controller) UpdateUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := request.IntegerParam("userID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := decodeUserPayload(request.Body())
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.ValidateUserModification(); err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
originalUser, err := c.store.UserByID(userID)
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(errors.New("Unable to fetch this user from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if originalUser == nil {
|
||||
response.JSON().NotFound(errors.New("User not found"))
|
||||
return
|
||||
}
|
||||
|
||||
originalUser.Merge(user)
|
||||
if err = c.store.UpdateUser(originalUser); err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to update this user"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Created(originalUser)
|
||||
}
|
||||
|
||||
// Users is the API handler to get the list of users.
|
||||
func (c *Controller) Users(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
users, err := c.store.Users()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch the list of users"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(users)
|
||||
}
|
||||
|
||||
// UserByID is the API handler to fetch the given user by the ID.
|
||||
func (c *Controller) UserByID(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := request.IntegerParam("userID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.store.UserByID(userID)
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(errors.New("Unable to fetch this user from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
response.JSON().NotFound(errors.New("User not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(user)
|
||||
}
|
||||
|
||||
// UserByUsername is the API handler to fetch the given user by the username.
|
||||
func (c *Controller) UserByUsername(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
username := request.StringParam("username", "")
|
||||
user, err := c.store.UserByUsername(username)
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(errors.New("Unable to fetch this user from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
response.JSON().NotFound(errors.New("User not found"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().Standard(user)
|
||||
}
|
||||
|
||||
// RemoveUser is the API handler to remove an existing user.
|
||||
func (c *Controller) RemoveUser(ctx *handler.Context, request *handler.Request, response *handler.Response) {
|
||||
if !ctx.IsAdminUser() {
|
||||
response.JSON().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := request.IntegerParam("userID")
|
||||
if err != nil {
|
||||
response.JSON().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.store.UserByID(userID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(errors.New("Unable to fetch this user from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
response.JSON().NotFound(errors.New("User not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.RemoveUser(user.ID); err != nil {
|
||||
response.JSON().BadRequest(errors.New("Unable to remove this user from the database"))
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON().NoContent()
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func askCredentials() (string, string) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Print("Enter Username: ")
|
||||
username, _ := reader.ReadString('\n')
|
||||
|
||||
fmt.Print("Enter Password: ")
|
||||
bytePassword, _ := terminal.ReadPassword(0)
|
||||
|
||||
fmt.Printf("\n")
|
||||
return strings.TrimSpace(username), strings.TrimSpace(string(bytePassword))
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/miniflux/miniflux/config"
|
||||
"github.com/miniflux/miniflux/daemon"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
"github.com/miniflux/miniflux/version"
|
||||
)
|
||||
|
||||
// Parse parses command line arguments.
|
||||
func Parse() {
|
||||
flagInfo := flag.Bool("info", false, "Show application information")
|
||||
flagVersion := flag.Bool("version", false, "Show application version")
|
||||
flagMigrate := flag.Bool("migrate", false, "Migrate database schema")
|
||||
flagFlushSessions := flag.Bool("flush-sessions", false, "Flush all sessions (disconnect users)")
|
||||
flagCreateAdmin := flag.Bool("create-admin", false, "Create admin user")
|
||||
flagResetPassword := flag.Bool("reset-password", false, "Reset user password")
|
||||
flag.Parse()
|
||||
|
||||
cfg := config.NewConfig()
|
||||
store := storage.NewStorage(
|
||||
cfg.DatabaseURL(),
|
||||
cfg.DatabaseMaxConnections(),
|
||||
)
|
||||
|
||||
if *flagInfo {
|
||||
info()
|
||||
return
|
||||
}
|
||||
|
||||
if *flagVersion {
|
||||
fmt.Println(version.Version)
|
||||
return
|
||||
}
|
||||
|
||||
if *flagMigrate {
|
||||
store.Migrate()
|
||||
return
|
||||
}
|
||||
|
||||
if *flagFlushSessions {
|
||||
flushSessions(store)
|
||||
return
|
||||
}
|
||||
|
||||
if *flagCreateAdmin {
|
||||
createAdmin(store)
|
||||
return
|
||||
}
|
||||
|
||||
if *flagResetPassword {
|
||||
resetPassword(store)
|
||||
return
|
||||
}
|
||||
|
||||
daemon.Run(cfg, store)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
)
|
||||
|
||||
func createAdmin(store *storage.Storage) {
|
||||
user := model.NewUser()
|
||||
user.Username = os.Getenv("ADMIN_USERNAME")
|
||||
user.Password = os.Getenv("ADMIN_PASSWORD")
|
||||
user.IsAdmin = true
|
||||
|
||||
if user.Username == "" || user.Password == "" {
|
||||
user.Username, user.Password = askCredentials()
|
||||
}
|
||||
|
||||
if err := user.ValidateUserCreation(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := store.CreateUser(user); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package cli implements command line arguments for Miniflux application.
|
||||
|
||||
*/
|
||||
package cli
|
|
@ -0,0 +1,20 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
)
|
||||
|
||||
func flushSessions(store *storage.Storage) {
|
||||
fmt.Println("Flushing all sessions (disconnect users)")
|
||||
if err := store.FlushAllSessions(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/miniflux/miniflux/version"
|
||||
)
|
||||
|
||||
func info() {
|
||||
fmt.Println("Version:", version.Version)
|
||||
fmt.Println("Build Date:", version.BuildDate)
|
||||
fmt.Println("Go Version:", runtime.Version())
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
)
|
||||
|
||||
func resetPassword(store *storage.Storage) {
|
||||
username, password := askCredentials()
|
||||
user, err := store.UserByUsername(username)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
fmt.Println("User not found!")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
user.Password = password
|
||||
if err := user.ValidatePassword(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := store.UpdateUser(user); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Password changed!")
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
Miniflux API Client
|
||||
===================
|
||||
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/miniflux.app/v2/client)](https://pkg.go.dev/miniflux.app/v2/client)
|
||||
|
||||
Client library for Miniflux REST API.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```bash
|
||||
go get -u miniflux.app/v2/client
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
miniflux "miniflux.app/v2/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Authentication with username/password:
|
||||
client := miniflux.New("https://api.example.org", "admin", "secret")
|
||||
|
||||
// Authentication with an API Key:
|
||||
client := miniflux.New("https://api.example.org", "my-secret-token")
|
||||
|
||||
// Fetch all feeds.
|
||||
feeds, err := client.Feeds()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(feeds)
|
||||
|
||||
// Backup your feeds to an OPML file.
|
||||
opml, err := client.Export()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile("opml.xml", opml, 0644)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
696
client/client.go
696
client/client.go
|
@ -1,696 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/v2/client"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Client holds API procedure calls.
|
||||
type Client struct {
|
||||
request *request
|
||||
}
|
||||
|
||||
// New returns a new Miniflux client.
|
||||
// Deprecated: use NewClient instead.
|
||||
func New(endpoint string, credentials ...string) *Client {
|
||||
return NewClient(endpoint, credentials...)
|
||||
}
|
||||
|
||||
// NewClient returns a new Miniflux client.
|
||||
func NewClient(endpoint string, credentials ...string) *Client {
|
||||
// Trim trailing slashes and /v1 from the endpoint.
|
||||
endpoint = strings.TrimSuffix(endpoint, "/")
|
||||
endpoint = strings.TrimSuffix(endpoint, "/v1")
|
||||
switch len(credentials) {
|
||||
case 2:
|
||||
return &Client{request: &request{endpoint: endpoint, username: credentials[0], password: credentials[1]}}
|
||||
case 1:
|
||||
return &Client{request: &request{endpoint: endpoint, apiKey: credentials[0]}}
|
||||
default:
|
||||
return &Client{request: &request{endpoint: endpoint}}
|
||||
}
|
||||
}
|
||||
|
||||
// Healthcheck checks if the application is up and running.
|
||||
func (c *Client) Healthcheck() error {
|
||||
body, err := c.request.Get("/healthcheck")
|
||||
if err != nil {
|
||||
return fmt.Errorf("miniflux: unable to perform healthcheck: %w", err)
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
responseBodyContent, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("miniflux: unable to read healthcheck response: %w", err)
|
||||
}
|
||||
|
||||
if string(responseBodyContent) != "OK" {
|
||||
return fmt.Errorf("miniflux: invalid healthcheck response: %q", responseBodyContent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version returns the version of the Miniflux instance.
|
||||
func (c *Client) Version() (*VersionResponse, error) {
|
||||
body, err := c.request.Get("/v1/version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var versionResponse *VersionResponse
|
||||
if err := json.NewDecoder(body).Decode(&versionResponse); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: json error (%v)", err)
|
||||
}
|
||||
|
||||
return versionResponse, nil
|
||||
}
|
||||
|
||||
// Me returns the logged user information.
|
||||
func (c *Client) Me() (*User, error) {
|
||||
body, err := c.request.Get("/v1/me")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var user *User
|
||||
if err := json.NewDecoder(body).Decode(&user); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: json error (%v)", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Users returns all users.
|
||||
func (c *Client) Users() (Users, error) {
|
||||
body, err := c.request.Get("/v1/users")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var users Users
|
||||
if err := json.NewDecoder(body).Decode(&users); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// UserByID returns a single user.
|
||||
func (c *Client) UserByID(userID int64) (*User, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/users/%d", userID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var user User
|
||||
if err := json.NewDecoder(body).Decode(&user); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UserByUsername returns a single user.
|
||||
func (c *Client) UserByUsername(username string) (*User, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/users/%s", username))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var user User
|
||||
if err := json.NewDecoder(body).Decode(&user); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// CreateUser creates a new user in the system.
|
||||
func (c *Client) CreateUser(username, password string, isAdmin bool) (*User, error) {
|
||||
body, err := c.request.Post("/v1/users", &UserCreationRequest{
|
||||
Username: username,
|
||||
Password: password,
|
||||
IsAdmin: isAdmin,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var user *User
|
||||
if err := json.NewDecoder(body).Decode(&user); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser updates a user in the system.
|
||||
func (c *Client) UpdateUser(userID int64, userChanges *UserModificationRequest) (*User, error) {
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/users/%d", userID), userChanges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var u *User
|
||||
if err := json.NewDecoder(body).Decode(&u); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// DeleteUser removes a user from the system.
|
||||
func (c *Client) DeleteUser(userID int64) error {
|
||||
return c.request.Delete(fmt.Sprintf("/v1/users/%d", userID))
|
||||
}
|
||||
|
||||
// MarkAllAsRead marks all unread entries as read for a given user.
|
||||
func (c *Client) MarkAllAsRead(userID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/users/%d/mark-all-as-read", userID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Discover try to find subscriptions from a website.
|
||||
func (c *Client) Discover(url string) (Subscriptions, error) {
|
||||
body, err := c.request.Post("/v1/discover", map[string]string{"url": url})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var subscriptions Subscriptions
|
||||
if err := json.NewDecoder(body).Decode(&subscriptions); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Categories gets the list of categories.
|
||||
func (c *Client) Categories() (Categories, error) {
|
||||
body, err := c.request.Get("/v1/categories")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var categories Categories
|
||||
if err := json.NewDecoder(body).Decode(&categories); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
// CreateCategory creates a new category.
|
||||
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
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var category *Category
|
||||
if err := json.NewDecoder(body).Decode(&category); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return category, nil
|
||||
}
|
||||
|
||||
// UpdateCategory updates a category.
|
||||
func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
|
||||
"title": title,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var category *Category
|
||||
if err := json.NewDecoder(body).Decode(&category); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return category, nil
|
||||
}
|
||||
|
||||
// MarkCategoryAsRead marks all unread entries in a category as read.
|
||||
func (c *Client) MarkCategoryAsRead(categoryID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
|
||||
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
|
||||
if err := json.NewDecoder(body).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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var feeds Feeds
|
||||
if err := json.NewDecoder(body).Decode(&feeds); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return feeds, nil
|
||||
}
|
||||
|
||||
// Export creates OPML file.
|
||||
func (c *Client) Export() ([]byte, error) {
|
||||
body, err := c.request.Get("/v1/export")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
opml, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return opml, nil
|
||||
}
|
||||
|
||||
// Import imports an OPML file.
|
||||
func (c *Client) Import(f io.ReadCloser) error {
|
||||
_, err := c.request.PostFile("/v1/import", f)
|
||||
return err
|
||||
}
|
||||
|
||||
// Feed gets a feed.
|
||||
func (c *Client) Feed(feedID int64) (*Feed, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var feed *Feed
|
||||
if err := json.NewDecoder(body).Decode(&feed); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
// CreateFeed creates a new feed.
|
||||
func (c *Client) CreateFeed(feedCreationRequest *FeedCreationRequest) (int64, error) {
|
||||
body, err := c.request.Post("/v1/feeds", feedCreationRequest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
type result struct {
|
||||
FeedID int64 `json:"feed_id"`
|
||||
}
|
||||
|
||||
var r result
|
||||
if err := json.NewDecoder(body).Decode(&r); err != nil {
|
||||
return 0, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return r.FeedID, nil
|
||||
}
|
||||
|
||||
// UpdateFeed updates a feed.
|
||||
func (c *Client) UpdateFeed(feedID int64, feedChanges *FeedModificationRequest) (*Feed, error) {
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d", feedID), feedChanges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var f *Feed
|
||||
if err := json.NewDecoder(body).Decode(&f); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// MarkFeedAsRead marks all unread entries of the feed as read.
|
||||
func (c *Client) MarkFeedAsRead(feedID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/mark-all-as-read", feedID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RefreshAllFeeds refreshes all feeds.
|
||||
func (c *Client) RefreshAllFeeds() error {
|
||||
_, err := c.request.Put("/v1/feeds/refresh", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RefreshFeed refreshes a feed.
|
||||
func (c *Client) RefreshFeed(feedID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/refresh", feedID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFeed removes a feed.
|
||||
func (c *Client) DeleteFeed(feedID int64) error {
|
||||
return c.request.Delete(fmt.Sprintf("/v1/feeds/%d", feedID))
|
||||
}
|
||||
|
||||
// FeedIcon gets a feed icon.
|
||||
func (c *Client) FeedIcon(feedID int64) (*FeedIcon, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/icon", feedID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var feedIcon *FeedIcon
|
||||
if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return feedIcon, nil
|
||||
}
|
||||
|
||||
// FeedEntry gets a single feed entry.
|
||||
func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var entry *Entry
|
||||
if err := json.NewDecoder(body).Decode(&entry); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
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
|
||||
if err := json.NewDecoder(body).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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var entry *Entry
|
||||
if err := json.NewDecoder(body).Decode(&entry); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Entries fetch entries.
|
||||
func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
|
||||
path := buildFilterQueryString("/v1/entries", filter)
|
||||
|
||||
body, err := c.request.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var result EntryResultSet
|
||||
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// FeedEntries fetch feed entries.
|
||||
func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
|
||||
path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
|
||||
|
||||
body, err := c.request.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var result EntryResultSet
|
||||
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", 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
|
||||
if err := json.NewDecoder(body).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 {
|
||||
EntryIDs []int64 `json:"entry_ids"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
_, err := c.request.Put("/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateEntry updates an entry.
|
||||
func (c *Client) UpdateEntry(entryID int64, entryChanges *EntryModificationRequest) (*Entry, error) {
|
||||
body, err := c.request.Put(fmt.Sprintf("/v1/entries/%d", entryID), entryChanges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var entry *Entry
|
||||
if err := json.NewDecoder(body).Decode(&entry); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// ToggleBookmark toggles entry bookmark value.
|
||||
func (c *Client) ToggleBookmark(entryID int64) error {
|
||||
_, err := c.request.Put(fmt.Sprintf("/v1/entries/%d/bookmark", entryID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveEntry sends an entry to a third-party service.
|
||||
func (c *Client) SaveEntry(entryID int64) error {
|
||||
_, err := c.request.Post(fmt.Sprintf("/v1/entries/%d/save", entryID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchEntryOriginalContent fetches the original content of an entry using the scraper.
|
||||
func (c *Client) FetchEntryOriginalContent(entryID int64) (string, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d/fetch-content", entryID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var response struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(body).Decode(&response); err != nil {
|
||||
return "", fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return response.Content, nil
|
||||
}
|
||||
|
||||
// FetchCounters fetches feed counters.
|
||||
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
|
||||
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// FlushHistory changes all entries with the status "read" to "removed".
|
||||
func (c *Client) FlushHistory() error {
|
||||
_, err := c.request.Put("/v1/flush-history", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Icon fetches a feed icon.
|
||||
func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
|
||||
body, err := c.request.Get(fmt.Sprintf("/v1/icons/%d", iconID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var feedIcon *FeedIcon
|
||||
if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
|
||||
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||
}
|
||||
|
||||
return feedIcon, nil
|
||||
}
|
||||
|
||||
func buildFilterQueryString(path string, filter *Filter) string {
|
||||
if filter != nil {
|
||||
values := url.Values{}
|
||||
|
||||
if filter.Status != "" {
|
||||
values.Set("status", filter.Status)
|
||||
}
|
||||
|
||||
if filter.Direction != "" {
|
||||
values.Set("direction", filter.Direction)
|
||||
}
|
||||
|
||||
if filter.Order != "" {
|
||||
values.Set("order", filter.Order)
|
||||
}
|
||||
|
||||
if filter.Limit >= 0 {
|
||||
values.Set("limit", strconv.Itoa(filter.Limit))
|
||||
}
|
||||
|
||||
if filter.Offset >= 0 {
|
||||
values.Set("offset", strconv.Itoa(filter.Offset))
|
||||
}
|
||||
|
||||
if filter.After > 0 {
|
||||
values.Set("after", strconv.FormatInt(filter.After, 10))
|
||||
}
|
||||
|
||||
if filter.Before > 0 {
|
||||
values.Set("before", strconv.FormatInt(filter.Before, 10))
|
||||
}
|
||||
|
||||
if filter.PublishedAfter > 0 {
|
||||
values.Set("published_after", strconv.FormatInt(filter.PublishedAfter, 10))
|
||||
}
|
||||
|
||||
if filter.PublishedBefore > 0 {
|
||||
values.Set("published_before", strconv.FormatInt(filter.PublishedBefore, 10))
|
||||
}
|
||||
|
||||
if filter.ChangedAfter > 0 {
|
||||
values.Set("changed_after", strconv.FormatInt(filter.ChangedAfter, 10))
|
||||
}
|
||||
|
||||
if filter.ChangedBefore > 0 {
|
||||
values.Set("changed_before", strconv.FormatInt(filter.ChangedBefore, 10))
|
||||
}
|
||||
|
||||
if filter.AfterEntryID > 0 {
|
||||
values.Set("after_entry_id", strconv.FormatInt(filter.AfterEntryID, 10))
|
||||
}
|
||||
|
||||
if filter.BeforeEntryID > 0 {
|
||||
values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
|
||||
}
|
||||
|
||||
if filter.Starred != "" {
|
||||
values.Set("starred", filter.Starred)
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
values.Set("search", filter.Search)
|
||||
}
|
||||
|
||||
if filter.CategoryID > 0 {
|
||||
values.Set("category_id", strconv.FormatInt(filter.CategoryID, 10))
|
||||
}
|
||||
|
||||
if filter.FeedID > 0 {
|
||||
values.Set("feed_id", strconv.FormatInt(filter.FeedID, 10))
|
||||
}
|
||||
|
||||
for _, status := range filter.Statuses {
|
||||
values.Add("status", status)
|
||||
}
|
||||
|
||||
path = fmt.Sprintf("%s?%s", path, values.Encode())
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// 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
|
||||
|
||||
This code snippet fetch the list of users:
|
||||
|
||||
import (
|
||||
miniflux "miniflux.app/v2/client"
|
||||
)
|
||||
|
||||
client := miniflux.NewClient("https://api.example.org", "admin", "secret")
|
||||
users, err := client.Users()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(users, err)
|
||||
|
||||
This one discover subscriptions on a website:
|
||||
|
||||
subscriptions, err := client.Discover("https://example.org/")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(subscriptions)
|
||||
*/
|
||||
package client // import "miniflux.app/v2/client"
|
298
client/model.go
298
client/model.go
|
@ -1,298 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/v2/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"`
|
||||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||
}
|
||||
|
||||
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"`
|
||||
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||
}
|
||||
|
||||
// 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=%q, URL=%q, Type=%q`, 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"`
|
||||
DisableHTTP2 bool `json:"disable_http2"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
DisableHTTP2 bool `json:"disable_http2"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
DisableHTTP2 *bool `json:"disable_http2"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Date time.Time `json:"published_at"`
|
||||
ChangedAt time.Time `json:"changed_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Feed *Feed `json:"feed,omitempty"`
|
||||
Hash string `json:"hash"`
|
||||
URL string `json:"url"`
|
||||
CommentsURL string `json:"comments_url"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
ShareCode string `json:"share_code"`
|
||||
Enclosures Enclosures `json:"enclosures,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
ReadingTime int `json:"reading_time"`
|
||||
UserID int64 `json:"user_id"`
|
||||
FeedID int64 `json:"feed_id"`
|
||||
Starred bool `json:"starred"`
|
||||
}
|
||||
|
||||
// EntryModificationRequest represents a request to modify an entry.
|
||||
type EntryModificationRequest struct {
|
||||
Title *string `json:"title"`
|
||||
Content *string `json:"content"`
|
||||
}
|
||||
|
||||
// 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
|
||||
PublishedBefore int64
|
||||
PublishedAfter int64
|
||||
ChangedBefore int64
|
||||
ChangedAfter 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"`
|
||||
}
|
||||
|
||||
// VersionResponse represents the version and the build information of the Miniflux instance.
|
||||
type VersionResponse struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
BuildDate string `json:"build_date"`
|
||||
GoVersion string `json:"go_version"`
|
||||
Compiler string `json:"compiler"`
|
||||
Arch string `json:"arch"`
|
||||
OS string `json:"os"`
|
||||
}
|
||||
|
||||
func SetOptionalField[T any](value T) *T {
|
||||
return &value
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package client // import "miniflux.app/v2/client"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "Miniflux Client Library"
|
||||
defaultTimeout = 80
|
||||
)
|
||||
|
||||
// List of exposed errors.
|
||||
var (
|
||||
ErrNotAuthorized = errors.New("miniflux: unauthorized (bad credentials)")
|
||||
ErrForbidden = errors.New("miniflux: access forbidden")
|
||||
ErrServerError = errors.New("miniflux: internal server error")
|
||||
ErrNotFound = errors.New("miniflux: resource not found")
|
||||
ErrBadRequest = errors.New("miniflux: bad request")
|
||||
)
|
||||
|
||||
type errorResponse struct {
|
||||
ErrorMessage string `json:"error_message"`
|
||||
}
|
||||
|
||||
type request struct {
|
||||
endpoint string
|
||||
username string
|
||||
password string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func (r *request) Get(path string) (io.ReadCloser, error) {
|
||||
return r.execute(http.MethodGet, path, nil)
|
||||
}
|
||||
|
||||
func (r *request) Post(path string, data interface{}) (io.ReadCloser, error) {
|
||||
return r.execute(http.MethodPost, path, data)
|
||||
}
|
||||
|
||||
func (r *request) PostFile(path string, f io.ReadCloser) (io.ReadCloser, error) {
|
||||
return r.execute(http.MethodPost, path, f)
|
||||
}
|
||||
|
||||
func (r *request) Put(path string, data interface{}) (io.ReadCloser, error) {
|
||||
return r.execute(http.MethodPut, path, data)
|
||||
}
|
||||
|
||||
func (r *request) Delete(path string) error {
|
||||
_, err := r.execute(http.MethodDelete, path, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *request) execute(method, path string, data interface{}) (io.ReadCloser, error) {
|
||||
if r.endpoint[len(r.endpoint)-1:] == "/" {
|
||||
r.endpoint = r.endpoint[:len(r.endpoint)-1]
|
||||
}
|
||||
|
||||
u, err := url.Parse(r.endpoint + path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &http.Request{
|
||||
URL: u,
|
||||
Method: method,
|
||||
Header: r.buildHeaders(),
|
||||
}
|
||||
|
||||
if r.username != "" && r.password != "" {
|
||||
request.SetBasicAuth(r.username, r.password)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
switch data := data.(type) {
|
||||
case io.ReadCloser:
|
||||
request.Body = data
|
||||
default:
|
||||
request.Body = io.NopCloser(bytes.NewBuffer(r.toJSON(data)))
|
||||
}
|
||||
}
|
||||
|
||||
client := r.buildClient()
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch response.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
response.Body.Close()
|
||||
return nil, ErrNotAuthorized
|
||||
case http.StatusForbidden:
|
||||
response.Body.Close()
|
||||
return nil, ErrForbidden
|
||||
case http.StatusInternalServerError:
|
||||
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
|
||||
case http.StatusNoContent:
|
||||
response.Body.Close()
|
||||
return nil, nil
|
||||
case http.StatusBadRequest:
|
||||
defer response.Body.Close()
|
||||
|
||||
var resp errorResponse
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(&resp); err != nil {
|
||||
return nil, fmt.Errorf("%w (%v)", ErrBadRequest, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w (%s)", ErrBadRequest, resp.ErrorMessage)
|
||||
}
|
||||
|
||||
if response.StatusCode > 400 {
|
||||
response.Body.Close()
|
||||
return nil, fmt.Errorf("miniflux: status code=%d", response.StatusCode)
|
||||
}
|
||||
|
||||
return response.Body, nil
|
||||
}
|
||||
|
||||
func (r *request) buildClient() http.Client {
|
||||
return http.Client{
|
||||
Timeout: time.Duration(defaultTimeout * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *request) buildHeaders() http.Header {
|
||||
headers := make(http.Header)
|
||||
headers.Add("User-Agent", userAgent)
|
||||
headers.Add("Content-Type", "application/json")
|
||||
headers.Add("Accept", "application/json")
|
||||
if r.apiKey != "" {
|
||||
headers.Add("X-Auth-Token", r.apiKey)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (r *request) toJSON(v interface{}) []byte {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Println("Unable to convert interface to JSON:", err)
|
||||
return []byte("")
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "http://localhost"
|
||||
defaultDatabaseURL = "postgres://postgres:postgres@localhost/miniflux2?sslmode=disable"
|
||||
defaultWorkerPoolSize = 5
|
||||
defaultPollingFrequency = 60
|
||||
defaultBatchSize = 10
|
||||
defaultDatabaseMaxConns = 20
|
||||
defaultListenAddr = "127.0.0.1:8080"
|
||||
defaultCertFile = ""
|
||||
defaultKeyFile = ""
|
||||
defaultCertDomain = ""
|
||||
defaultCertCache = "/tmp/cert_cache"
|
||||
defaultSessionCleanupFrequency = 24
|
||||
)
|
||||
|
||||
// Config manages configuration parameters.
|
||||
type Config struct {
|
||||
IsHTTPS bool
|
||||
}
|
||||
|
||||
func (c *Config) get(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (c *Config) getInt(key string, fallback int) int {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
v, _ := strconv.Atoi(value)
|
||||
return v
|
||||
}
|
||||
|
||||
// BaseURL returns the application base URL.
|
||||
func (c *Config) BaseURL() string {
|
||||
return c.get("BASE_URL", defaultBaseURL)
|
||||
}
|
||||
|
||||
// DatabaseURL returns the database URL.
|
||||
func (c *Config) DatabaseURL() string {
|
||||
return c.get("DATABASE_URL", defaultDatabaseURL)
|
||||
}
|
||||
|
||||
// DatabaseMaxConnections returns the number of maximum database connections.
|
||||
func (c *Config) DatabaseMaxConnections() int {
|
||||
return c.getInt("DATABASE_MAX_CONNS", defaultDatabaseMaxConns)
|
||||
}
|
||||
|
||||
// ListenAddr returns the listen address for the HTTP server.
|
||||
func (c *Config) ListenAddr() string {
|
||||
return c.get("LISTEN_ADDR", defaultListenAddr)
|
||||
}
|
||||
|
||||
// CertFile returns the SSL certificate filename if any.
|
||||
func (c *Config) CertFile() string {
|
||||
return c.get("CERT_FILE", defaultCertFile)
|
||||
}
|
||||
|
||||
// KeyFile returns the private key filename for custom SSL certificate.
|
||||
func (c *Config) KeyFile() string {
|
||||
return c.get("KEY_FILE", defaultKeyFile)
|
||||
}
|
||||
|
||||
// CertDomain returns the domain to use for Let's Encrypt certificate.
|
||||
func (c *Config) CertDomain() string {
|
||||
return c.get("CERT_DOMAIN", defaultCertDomain)
|
||||
}
|
||||
|
||||
// CertCache returns the directory to use for Let's Encrypt session cache.
|
||||
func (c *Config) CertCache() string {
|
||||
return c.get("CERT_CACHE", defaultCertCache)
|
||||
}
|
||||
|
||||
// SessionCleanupFrequency returns the interval for session cleanup.
|
||||
func (c *Config) SessionCleanupFrequency() int {
|
||||
return c.getInt("SESSION_CLEANUP_FREQUENCY", defaultSessionCleanupFrequency)
|
||||
}
|
||||
|
||||
// WorkerPoolSize returns the number of background worker.
|
||||
func (c *Config) WorkerPoolSize() int {
|
||||
return c.getInt("WORKER_POOL_SIZE", defaultWorkerPoolSize)
|
||||
}
|
||||
|
||||
// PollingFrequency returns the interval to refresh feeds in the background.
|
||||
func (c *Config) PollingFrequency() int {
|
||||
return c.getInt("POLLING_FREQUENCY", defaultPollingFrequency)
|
||||
}
|
||||
|
||||
// BatchSize returns the number of feeds to send for background processing.
|
||||
func (c *Config) BatchSize() int {
|
||||
return c.getInt("BATCH_SIZE", defaultBatchSize)
|
||||
}
|
||||
|
||||
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
|
||||
func (c *Config) IsOAuth2UserCreationAllowed() bool {
|
||||
return c.getInt("OAUTH2_USER_CREATION", 0) == 1
|
||||
}
|
||||
|
||||
// OAuth2ClientID returns the OAuth2 Client ID.
|
||||
func (c *Config) OAuth2ClientID() string {
|
||||
return c.get("OAUTH2_CLIENT_ID", "")
|
||||
}
|
||||
|
||||
// OAuth2ClientSecret returns the OAuth2 client secret.
|
||||
func (c *Config) OAuth2ClientSecret() string {
|
||||
return c.get("OAUTH2_CLIENT_SECRET", "")
|
||||
}
|
||||
|
||||
// OAuth2RedirectURL returns the OAuth2 redirect URL.
|
||||
func (c *Config) OAuth2RedirectURL() string {
|
||||
return c.get("OAUTH2_REDIRECT_URL", "")
|
||||
}
|
||||
|
||||
// OAuth2Provider returns the name of the OAuth2 provider configured.
|
||||
func (c *Config) OAuth2Provider() string {
|
||||
return c.get("OAUTH2_PROVIDER", "")
|
||||
}
|
||||
|
||||
// NewConfig returns a new Config.
|
||||
func NewConfig() *Config {
|
||||
return &Config{IsHTTPS: os.Getenv("HTTPS") != ""}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package config handles configuration values for Miniflux application.
|
||||
|
||||
*/
|
||||
package config
|
|
@ -1,4 +0,0 @@
|
|||
The contrib directory contains various useful things contributed by the community.
|
||||
|
||||
Community contributions are not officially supported by the maintainers.
|
||||
There is no guarantee whatsoever that anything in this folder works.
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
miniflux_linux_user: miniflux
|
||||
miniflux_db_user_name: miniflux_db_user
|
||||
miniflux_db_user_password: miniflux_db_user_password
|
||||
miniflux_db: miniflux_db
|
||||
miniflux_admin_name: admin
|
||||
miniflux_admin_passwort: miniflux_admin_password
|
||||
miniflux_port: 8080
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
- hosts: miniflux
|
||||
roles:
|
||||
- { role: mgrote.miniflux, tags: "miniflux" }
|
|
@ -1,23 +0,0 @@
|
|||
## mgrote.miniflux
|
||||
|
||||
### Details
|
||||
Installs and configures Miniflux v2 with ansible
|
||||
|
||||
### Works on...
|
||||
- [x] Ubuntu (>=18.04)
|
||||
|
||||
### Variables and Defaults
|
||||
##### Linux User
|
||||
miniflux_linux_user: miniflux
|
||||
##### DB User
|
||||
miniflux_db_user_name: miniflux_db_user
|
||||
##### DB Password
|
||||
miniflux_db_user_password: qqqqqqqqqqqqq
|
||||
##### Database
|
||||
miniflux_db: miniflux_db
|
||||
##### Username Miniflux Admin
|
||||
miniflux_admin_name: admin
|
||||
##### Password Miniflux Admin
|
||||
miniflux_admin_passwort: hallowelt
|
||||
##### Port for Miniflux Frontend
|
||||
miniflux_port: 8080
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
- name: start_miniflux.service
|
||||
become: yes
|
||||
systemd:
|
||||
name: miniflux
|
||||
state: restarted
|
||||
enabled: yes
|
||||
# wait 15 seconds(for systemd)
|
||||
- name: miniflux_wait
|
||||
wait_for:
|
||||
timeout: 15
|
|
@ -1,43 +0,0 @@
|
|||
- name: add Apt-key for miniflux-repo
|
||||
become: yes
|
||||
apt_key:
|
||||
url: https://apt.miniflux.app/KEY.gpg
|
||||
state: present
|
||||
|
||||
- name: add miniflux-repo
|
||||
become: yes
|
||||
apt_repository:
|
||||
repo: 'deb https://apt.miniflux.app/ /'
|
||||
state: present
|
||||
filename: miniflux_repo
|
||||
update_cache: yes
|
||||
|
||||
- name: install miniflux
|
||||
become: yes
|
||||
apt:
|
||||
name: miniflux
|
||||
state: present
|
||||
|
||||
- name: add miniflux linux_user
|
||||
become: yes
|
||||
user:
|
||||
name: "{{ miniflux_linux_user }}"
|
||||
home: "/var/empty"
|
||||
create_home: "no"
|
||||
system: "yes"
|
||||
shell: "/bin/false"
|
||||
|
||||
- name: create directory "/etc/miniflux.d"
|
||||
become: yes
|
||||
file:
|
||||
path: /etc/miniflux.d
|
||||
state: directory
|
||||
|
||||
- name: copy miniflux.conf
|
||||
become: yes
|
||||
template:
|
||||
src: "miniflux.conf"
|
||||
dest: "/etc/miniflux.conf"
|
||||
notify:
|
||||
- start_miniflux.service
|
||||
- miniflux_wait
|
|
@ -1,18 +0,0 @@
|
|||
# See https://docs.miniflux.app/
|
||||
|
||||
LISTEN_ADDR=0.0.0.0:{{ miniflux_port }}
|
||||
DATABASE_URL=user={{ miniflux_db_user_name }} password={{ miniflux_db_user_password }} dbname={{ miniflux_db }} sslmode=disable
|
||||
|
||||
POLLING_FREQUENCY=15
|
||||
PROXY_IMAGES=http-only
|
||||
|
||||
# Run SQL migrations automatically:
|
||||
RUN_MIGRATIONS=1
|
||||
|
||||
CREATE_ADMIN=1
|
||||
ADMIN_USERNAME={{ miniflux_admin_name }}
|
||||
ADMIN_PASSWORD={{ miniflux_admin_passwort }}
|
||||
|
||||
POLLING_FREQUENCY=10
|
||||
|
||||
# Options: https://miniflux.app/miniflux.1.html
|
|
@ -1,6 +0,0 @@
|
|||
This folder contains Miniflux API collection for [Bruno](https://www.usebruno.com).
|
||||
|
||||
Bruno is a lightweight alternative to Postman/Insomnia.
|
||||
|
||||
- https://www.usebruno.com
|
||||
- https://github.com/usebruno/bruno
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Bookmark an entry
|
||||
type: http
|
||||
seq: 37
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/entries/{{entryID}}/bookmark
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
entryID: 1698
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Create a feed
|
||||
type: http
|
||||
seq: 19
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/feeds
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Create a new category
|
||||
type: http
|
||||
seq: 10
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/categories
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
meta {
|
||||
name: Create a new user
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/users
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"username": "foobar",
|
||||
"password": "secret123"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Delete a category
|
||||
type: http
|
||||
seq: 12
|
||||
}
|
||||
|
||||
delete {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 1
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Delete a feed
|
||||
type: http
|
||||
seq: 26
|
||||
}
|
||||
|
||||
delete {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 18
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Delete a user
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
delete {
|
||||
url: {{minifluxBaseURL}}/v1/users/{{userID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"language": "fr_FR"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
userID: 2
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Discover feeds
|
||||
type: http
|
||||
seq: 18
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/discover
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"url": "https://miniflux.app"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Fetch entry website content
|
||||
type: http
|
||||
seq: 39
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/entries/{{entryID}}/fetch-content
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
entryID: 1698
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Flush history
|
||||
type: http
|
||||
seq: 40
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/flush-history
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"url": "https://miniflux.app"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get a single entry
|
||||
type: http
|
||||
seq: 36
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/entries/{{entryID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
entryID: 1698
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
meta {
|
||||
name: Get a single feed entry
|
||||
type: http
|
||||
seq: 33
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}/entries/{{entryID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
entryID: 1698
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get a single feed
|
||||
type: http
|
||||
seq: 24
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 18
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
meta {
|
||||
name: Get a single user by ID
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/users/{{userID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
userID: 1
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
meta {
|
||||
name: Get a single user by username
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/users/{{username}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
username: admin
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
meta {
|
||||
name: Get all categories
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/categories
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Get all entries
|
||||
type: http
|
||||
seq: 34
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/entries
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Get all feeds
|
||||
type: http
|
||||
seq: 20
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
meta {
|
||||
name: Get all users
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/users
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get category entries
|
||||
type: http
|
||||
seq: 16
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}/entries
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 2
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
meta {
|
||||
name: Get category entry
|
||||
type: http
|
||||
seq: 17
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}/entries/{{entryID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 2
|
||||
entryID: 1
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get category feeds
|
||||
type: http
|
||||
seq: 14
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}/feeds
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 2
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
meta {
|
||||
name: Get current user
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/me
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Get feed counters
|
||||
type: http
|
||||
seq: 21
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/counters
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get feed entries
|
||||
type: http
|
||||
seq: 32
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}/entries
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get feed icon by feed ID
|
||||
type: http
|
||||
seq: 27
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}/icon
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Get feed icon by icon ID
|
||||
type: http
|
||||
seq: 28
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/icons/{{iconID}}
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
iconID: 11
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
meta {
|
||||
name: Get version and build information
|
||||
type: http
|
||||
seq: 42
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/version
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Mark all category entries as read
|
||||
type: http
|
||||
seq: 13
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}/mark-all-as-read
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 2
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Mark all user entries as read
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/users/{{userID}}/mark-all-as-read
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
userID: 1
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Mark feed as read
|
||||
type: http
|
||||
seq: 29
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}/mark-all-as-read
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: OPML Export
|
||||
type: http
|
||||
seq: 30
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{minifluxBaseURL}}/v1/export
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
meta {
|
||||
name: OPML Import
|
||||
type: http
|
||||
seq: 31
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/import
|
||||
body: xml
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
body:xml {
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="2.0">
|
||||
<head>
|
||||
<title>Miniflux</title>
|
||||
</head>
|
||||
<body>
|
||||
<outline text="My category">
|
||||
<outline title="Miniflux" text="Miniflux" xmlUrl="https://miniflux.app/feed.xml" htmlUrl="https://miniflux.app"></outline>
|
||||
</outline>
|
||||
</body>
|
||||
</opml>
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 19
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Refresh a single feed
|
||||
type: http
|
||||
seq: 23
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}/refresh
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 18
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
meta {
|
||||
name: Refresh all feeds
|
||||
type: http
|
||||
seq: 22
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/refresh
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Refresh category feeds
|
||||
type: http
|
||||
seq: 15
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}/refresh
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 2
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Save an entry
|
||||
type: http
|
||||
seq: 38
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{minifluxBaseURL}}/v1/entries/{{entryID}}/save
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"feed_url": "https://miniflux.app/feed.xml"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
entryID: 1698
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Update a category
|
||||
type: http
|
||||
seq: 11
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/categories/{{categoryID}}
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "Test Update"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
categoryID: 1
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Update a feed
|
||||
type: http
|
||||
seq: 25
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/feeds/{{feedID}}
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"user_agent": "My user agent"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
feedID: 18
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
meta {
|
||||
name: Update a user
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/users/{{userID}}
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"language": "fr_FR"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
userID: 1
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
meta {
|
||||
name: Update entries status
|
||||
type: http
|
||||
seq: 35
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/entries
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"entry_ids": [1698, 1699],
|
||||
"status": "read"
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
meta {
|
||||
name: Update entry
|
||||
type: http
|
||||
seq: 41
|
||||
}
|
||||
|
||||
put {
|
||||
url: {{minifluxBaseURL}}/v1/entries/{{entryID}}
|
||||
body: json
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: {{minifluxUsername}}
|
||||
password: {{minifluxPassword}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"title": "New title",
|
||||
"content": "Some text"
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
entryID: 1789
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"version": "1",
|
||||
"name": "Miniflux",
|
||||
"type": "collection"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
vars {
|
||||
minifluxBaseURL: http://127.0.0.1:8080
|
||||
minifluxUsername: admin
|
||||
}
|
||||
vars:secret [
|
||||
minifluxPassword
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue