tasks: add /tasks/jellyseerr, document

now live in tasks.go and have actual API documentation.
/tasks/jellyseerr triggers account import.
This commit is contained in:
Harvey Tindall
2025-11-28 17:26:27 +00:00
parent f36a32773a
commit 0ecacc6064
7 changed files with 90 additions and 31 deletions

View File

@@ -1,13 +1,14 @@
package common
type SectionMeta struct {
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
Description string `json:"description" yaml:"description"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
Description string `json:"description" yaml:"description"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
}
type Option [2]string
@@ -40,6 +41,7 @@ type Setting struct {
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
}
type Section struct {

View File

@@ -1368,6 +1368,10 @@ sections:
and telegram). A template must be added to a User Profile for accounts to be
created.
wiki_link: https://wiki.jfa-go.com/docs/external-services/jellyseerr/
aliases:
- Jellyseerr
- Overseerr
- Seerr
settings:
- setting: enabled
name: Enabled

View File

@@ -32,7 +32,6 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
if strings.Contains(err.Error(), "INVALID_EMAIL") {
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err.Error()+"\""+email.Addr+"\"")
} else {
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
}
} else {

36
main.go
View File

@@ -112,19 +112,19 @@ type appContext struct {
adminUsers []User
invalidTokens []string
// Keeping jf name because I can't think of a better one
jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser
ombi *OmbiWrapper
js *JellyseerrWrapper
thirdPartyServices []ThirdPartyService
storage *Storage
validator Validator
email *Emailer
telegram *TelegramDaemon
discord *DiscordDaemon
matrix *MatrixDaemon
housekeepingDaemon, userDaemon *GenericDaemon
contactMethods []ContactMethodLinker
jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser
ombi *OmbiWrapper
js *JellyseerrWrapper
thirdPartyServices []ThirdPartyService
storage *Storage
validator Validator
email *Emailer
telegram *TelegramDaemon
discord *DiscordDaemon
matrix *MatrixDaemon
housekeepingDaemon, userDaemon, jellyseerrDaemon *GenericDaemon
contactMethods []ContactMethodLinker
LoggerSet
host string
port int
@@ -516,12 +516,11 @@ func start(asDaemon, firstCall bool) {
go app.userDaemon.run()
defer app.userDaemon.Shutdown()
var jellyseerrDaemon *GenericDaemon
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) && app.config.Section("jellyseerr").Key("import_existing").MustBool(false) {
// jellyseerrDaemon = newJellyseerrDaemon(time.Duration(30*time.Second), app)
jellyseerrDaemon = newJellyseerrDaemon(time.Duration(10*time.Minute), app)
go jellyseerrDaemon.run()
defer jellyseerrDaemon.Shutdown()
app.jellyseerrDaemon = newJellyseerrDaemon(time.Duration(10*time.Minute), app)
go app.jellyseerrDaemon.run()
defer app.jellyseerrDaemon.Shutdown()
}
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
@@ -762,6 +761,9 @@ func flagPassed(name string) (found bool) {
// @tag.name Statistics
// @tag.description Routes that expose useful info/stats.
// @tag.name Tasks
// @tag.description Manual triggers for background tasks.
func printVersion() {
tray := ""
if TRAY {

View File

@@ -244,8 +244,11 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.POST(p+"/config", app.ModifyConfig)
api.POST(p+"/restart", app.restart)
api.GET(p+"/logs", app.GetLog)
api.POST(p+"/tasks/housekeeping", func(gc *gin.Context) { app.housekeepingDaemon.Trigger(); gc.Status(http.StatusNoContent) })
api.POST(p+"/tasks/users", func(gc *gin.Context) { app.userDaemon.Trigger(); gc.Status(http.StatusNoContent) })
api.POST(p+"/tasks/housekeeping", app.TaskHousekeeping)
api.POST(p+"/tasks/users", app.TaskUserCleanup)
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
api.POST(p+"/tasks/jellyseerr", app.TaskJellyseerrImport)
}
api.POST(p+"/backups", app.CreateBackup)
api.GET(p+"/backups/:fname", app.GetBackup)
api.GET(p+"/backups", app.GetBackups)

42
tasks.go Normal file
View File

@@ -0,0 +1,42 @@
// Routes for triggering background tasks manually.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// @Summary Triggers general housekeeping tasks: Clearing expired invites, activities, unused contact details, captchas, etc.
// @Success 204
// @Router /tasks/housekeeping [post]
// @Security Bearer
// @tags Tasks
func (app *appContext) TaskHousekeeping(gc *gin.Context) {
app.housekeepingDaemon.Trigger()
gc.Status(http.StatusNoContent)
}
// @Summary Triggers check for account expiry.
// @Success 204
// @Router /tasks/users [post]
// @Security Bearer
// @tags Tasks
func (app *appContext) TaskUserCleanup(gc *gin.Context) {
app.userDaemon.Trigger()
gc.Status(http.StatusNoContent)
}
// @Summary Triggers sync of user details with Jellyseerr. Not usually needed after one run, details are synced on change anyway.
// @Success 204
// @Router /tasks/jellyseerr [post]
// @Security Bearer
// @tags Tasks
func (app *appContext) TaskJellyseerrImport(gc *gin.Context) {
if app.jellyseerrDaemon != nil {
app.jellyseerrDaemon.Trigger()
} else {
app.SynchronizeJellyseerrUsers()
}
gc.Status(http.StatusNoContent)
}

View File

@@ -50,6 +50,7 @@ interface Meta {
depends_true?: string;
depends_false?: string;
wiki_link?: string;
aliases?: string[];
}
interface Setting {
@@ -65,6 +66,7 @@ interface Setting {
depends_false?: string;
wiki_link?: string;
deprecated?: boolean;
aliases?: string[];
asElement: () => HTMLElement;
update: (s: Setting) => void;
@@ -170,6 +172,8 @@ class DOMSetting {
this._registerDependencies();
}
get aliases(): string[] { return this._s.aliases; }
protected _registerDependencies() {
// Doesn't re-register dependencies, but that isn't important in this application
if (!(this._s.depends_true || this._s.depends_false)) return;
@@ -1422,6 +1426,7 @@ export class settingsList {
section.section.toLowerCase().includes(query) ||
section.meta.name.toLowerCase().includes(query) ||
section.meta.description.toLowerCase().includes(query);
if (section.meta.aliases) section.meta.aliases.forEach((term: string) => matchedSection ||= term.toLowerCase().includes(query));
matchedSection &&= ((section.meta.advanced && this._advanced) || !(section.meta.advanced));
if (matchedSection) {
@@ -1447,10 +1452,12 @@ export class settingsList {
// element.classList.remove("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low");
element.classList.add("opacity-50", "pointer-events-none");
element.setAttribute("aria-disabled", "true");
if (setting.setting.toLowerCase().includes(query) ||
setting.name.toLowerCase().includes(query) ||
setting.description.toLowerCase().includes(query) ||
String(setting.value).toLowerCase().includes(query)) {
let matchedSetting = setting.setting.toLowerCase().includes(query) ||
setting.name.toLowerCase().includes(query) ||
setting.description.toLowerCase().includes(query) ||
String(setting.value).toLowerCase().includes(query);
if (setting.aliases) setting.aliases.forEach((term: string) => matchedSetting ||= term.toLowerCase().includes(query));
if (matchedSetting) {
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
show();
firstVisibleSection = firstVisibleSection || section.section;