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-linux-amd64
|
||||||
./miniflux
|
miniflux-linux-arm*
|
||||||
*.rpm
|
miniflux-darwin-amd64
|
||||||
*.deb
|
miniflux-test
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
|
|
|
@ -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
|
APP := miniflux
|
||||||
DOCKER_IMAGE := miniflux/miniflux
|
VERSION=$(shell git rev-parse --short HEAD)
|
||||||
VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null)
|
BUILD_DATE=`date +%FT%T%z`
|
||||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
|
||||||
BUILD_DATE := `date +%FT%T%z`
|
DB_URL := postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
|
||||||
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
|
|
||||||
|
|
||||||
export PGPASSWORD := postgres
|
.PHONY: linux linux-arm darwin build run clean test lint integration-test clean-integration-test
|
||||||
|
|
||||||
.PHONY: \
|
linux:
|
||||||
miniflux \
|
@ go generate
|
||||||
miniflux-no-pie \
|
@ 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
|
||||||
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
|
|
||||||
|
|
||||||
miniflux:
|
linux-arm:
|
||||||
@ go build -buildmode=pie -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
@ 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:
|
darwin:
|
||||||
@ go build -ldflags=$(LD_FLAGS) -o $(APP) main.go
|
@ 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:
|
build: linux linux-arm darwin
|
||||||
@ 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
|
|
||||||
|
|
||||||
run:
|
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:
|
clean:
|
||||||
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe
|
@ rm -f $(APP)-*
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -cover -race -count=1 ./...
|
go test -cover -race ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
go vet ./...
|
@ golint -set_exit_status ${PKG_LIST}
|
||||||
staticcheck ./...
|
|
||||||
golangci-lint run --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace
|
|
||||||
|
|
||||||
integration-test:
|
integration-test:
|
||||||
psql -U postgres -c 'drop database if exists miniflux_test;'
|
psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||||
psql -U postgres -c 'create database 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
|
go build -o miniflux-test main.go
|
||||||
|
DATABASE_URL=$(DB_URL) ./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||||
DATABASE_URL=$(DB_URL) \
|
while ! echo exit | nc localhost 8080; do sleep 1; done >/dev/null
|
||||||
ADMIN_USERNAME=admin \
|
go test -v -tags=integration || cat /tmp/miniflux.log
|
||||||
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
|
|
||||||
|
|
||||||
clean-integration-test:
|
clean-integration-test:
|
||||||
@ kill -9 `cat /tmp/miniflux.pid`
|
@ kill -9 `cat /tmp/miniflux.pid`
|
||||||
@ rm -f /tmp/miniflux.pid /tmp/miniflux.log
|
@ rm -f /tmp/miniflux.pid /tmp/miniflux.log
|
||||||
@ rm miniflux-test
|
@ rm miniflux-test
|
||||||
@ psql -U postgres -c 'drop database if exists 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
|
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:
|
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 ORM
|
||||||
- Doesn't use any complicated framework
|
- Doesn't use any complicated framework
|
||||||
- Use only modern vanilla Javascript (ES6 and Fetch API)
|
- Use only modern vanilla Javascript (ES6 and Fetch API)
|
||||||
- Single binary compiled statically without dependency
|
|
||||||
- The number of features is voluntarily limited
|
- The number of features is voluntarily limited
|
||||||
|
|
||||||
It's simple, fast, lightweight and super easy to install.
|
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
|
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)
|
- [Opinionated?](https://docs.miniflux.net/en/latest/opinionated.html)
|
||||||
- [Features](https://miniflux.app/features.html)
|
- [Features](https://docs.miniflux.net/en/latest/features.html)
|
||||||
- [Requirements](https://miniflux.app/docs/requirements.html)
|
- [Requirements](https://docs.miniflux.net/en/latest/requirements.html)
|
||||||
- [Installation Instructions](https://miniflux.app/docs/installation.html)
|
- [Installation](https://docs.miniflux.net/en/latest/installation.html)
|
||||||
- [Upgrading to a New Version](https://miniflux.app/docs/upgrade.html)
|
- [Upgrading to a new version](https://docs.miniflux.net/en/latest/upgrade.html)
|
||||||
- [Configuration](https://miniflux.app/docs/configuration.html)
|
- [Configuration](https://docs.miniflux.net/en/latest/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)
|
|
||||||
|
|
||||||
Credits
|
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
|
- 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