From b6537cef65a8bb32c56d0e3e25a29751739e4e56 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Thu, 24 Sep 2020 17:51:13 +0100 Subject: [PATCH] Add basic swagger documentation accessible by running with -swagger. Accessible at /swagger/index.html. Currently doesn't have authentication setup, so no requests will work. --- Makefile | 4 + api.go | 450 +++++++++++++------ auth.go | 11 - docs/docs.go | 1062 +++++++++++++++++++++++++++++++++++++++++++++ docs/go.mod | 3 + docs/swagger.json | 999 ++++++++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 678 +++++++++++++++++++++++++++++ go.mod | 15 +- go.sum | 138 ++++++ main.go | 18 + pwval.go | 11 +- ts/settings.ts | 9 +- 12 files changed, 3254 insertions(+), 144 deletions(-) create mode 100644 docs/docs.go create mode 100644 docs/go.mod create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml diff --git a/Makefile b/Makefile index 159e91d..9bf2686 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,9 @@ ts-debug: -npx tsc -p ts/ --sourceMap cp -r ts data/static/ +swagger: + swag init -g main.go + version: python3 version.py auto version.go @@ -52,6 +55,7 @@ compress: copy: $(info Copying data) cp -r data build/ + cp docs/swagger.json build/data/static/ install: cp -r build $(DESTDIR)/jfa-go diff --git a/api.go b/api.go index 775b0be..f388d09 100644 --- a/api.go +++ b/api.go @@ -13,6 +13,38 @@ import ( "gopkg.in/ini.v1" ) +func respond(code int, message string, gc *gin.Context) { + resp := stringResponse{} + if code == 200 || code == 204 { + resp.Response = message + } else { + resp.Error = message + } + gc.JSON(code, resp) + gc.Abort() +} + +type stringResponse struct { + Response string `json:"response" example:"message"` + Error string `json:"error" example:"errorDescription"` +} + +type boolResponse struct { + Success bool `json:"success" example:"false"` + Error bool `json:"error" example:"true"` +} + +func respondBool(code int, val bool, gc *gin.Context) { + resp := boolResponse{} + if !val { + resp.Error = true + } else { + resp.Success = true + } + gc.JSON(code, resp) + gc.Abort() +} + func (app *appContext) loadStrftime() { app.datePattern = app.config.Section("email").Key("date_format").String() app.timePattern = `%H:%M` @@ -174,15 +206,20 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool // Routes from now on! -type newUserReq struct { - Username string `json:"username"` - Password string `json:"password"` - Email string `json:"email"` - Code string `json:"code"` +type newUserDTO struct { + Username string `json:"username" example:"jeff" binding:"required"` // User's username + Password string `json:"password" example:"guest" binding:"required"` // User's password + Email string `json:"email" example:"jeff@jellyf.in"` // User's email address + Code string `json:"code" example:"abc0933jncjkcjj"` // Invite code (required on /newUser) } +// @Summary Creates a new Jellyfin user without an invite. +// @Produce json +// @Param newUserDTO body newUserDTO true "New user request object" +// @Success 200 +// @Router /users [post] func (app *appContext) NewUserAdmin(gc *gin.Context) { - var req newUserReq + var req newUserDTO gc.BindJSON(&req) existingUser, _, _ := app.jf.userByName(req.Username, false) if existingUser != nil { @@ -234,14 +271,19 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { app.jf.cacheExpiry = time.Now() } +// @Summary Creates a new Jellyfin user via invite code +// @Produce json +// @Param newUserDTO body newUserDTO true "New user request object" +// @Success 200 {object} PasswordValidation +// @Failure 400 {object} PasswordValidation +// @Router /newUser [post] func (app *appContext) NewUser(gc *gin.Context) { - var req newUserReq + var req newUserDTO gc.BindJSON(&req) app.debug.Printf("%s: New user attempt", req.Code) if !app.checkInvite(req.Code, false, "") { app.info.Printf("%s New user failed: invalid code", req.Code) - gc.JSON(401, map[string]bool{"success": false}) - gc.Abort() + respondBool(401, false, gc) return } validation := app.validator.validate(req.Password) @@ -335,17 +377,30 @@ func (app *appContext) NewUser(gc *gin.Context) { } } } - gc.JSON(200, validation) + code := 200 + for _, val := range validation { + if !val { + code = 400 + } + } + gc.JSON(code, validation) } -type deleteUserReq struct { - Users []string `json:"users"` - Notify bool `json:"notify"` - Reason string `json:"reason"` +type deleteUserDTO struct { + Users []string `json:"users" binding:"required"` // List of usernames to delete + Notify bool `json:"notify"` // Whether to notify users of deletion + Reason string `json:"reason"` // Account deletion reason (for notification) } +// @Summary Delete a list of users, optionally notifying them why. +// @Produce json +// @Param deleteUserDTO body deleteUserDTO true "User deletion request object" +// @Success 200 {object} boolResponse +// @Failure 400 {object} stringResponse +// @Failure 500 {object} errorListDTO "List of errors" +// @Router /users [delete] func (app *appContext) DeleteUser(gc *gin.Context) { - var req deleteUserReq + var req deleteUserDTO gc.BindJSON(&req) errors := map[string]string{} for _, userID := range req.Users { @@ -373,29 +428,34 @@ func (app *appContext) DeleteUser(gc *gin.Context) { } app.jf.cacheExpiry = time.Now() if len(errors) == len(req.Users) { - respond(500, "Failed", gc) + respondBool(500, false, gc) app.err.Printf("Account deletion failed: %s", errors[req.Users[0]]) return } else if len(errors) != 0 { gc.JSON(500, errors) return } - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } -type generateInviteReq struct { - Days int `json:"days"` - Hours int `json:"hours"` - Minutes int `json:"minutes"` - Email string `json:"email"` - MultipleUses bool `json:"multiple-uses"` - NoLimit bool `json:"no-limit"` - RemainingUses int `json:"remaining-uses"` - Profile string `json:"profile"` +type generateInviteDTO struct { + Days int `json:"days" example:"1"` // Number of days + Hours int `json:"hours" example:"2"` // Number of hours + Minutes int `json:"minutes" example:"3"` // Number of minutes + Email string `json:"email" example:"jeff@jellyf.in"` // Send invite to this address + MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses + NoLimit bool `json:"no-limit" example:"false"` // No invite use limit + RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses + Profile string `json:"profile" example:"DefaultProfile"` // Name of profile to apply on this invite } +// @Summary Create a new invite. +// @Produce json +// @Param generateInviteDTO body generateInviteDTO true "New invite request object" +// @Success 200 {object} boolResponse +// @Router /invites [post] func (app *appContext) GenerateInvite(gc *gin.Context) { - var req generateInviteReq + var req generateInviteDTO app.debug.Println("Generating new invite") app.storage.loadInvites() gc.BindJSON(&req) @@ -446,16 +506,22 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { } app.storage.invites[invite_code] = invite app.storage.storeInvites() - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } -type profileReq struct { - Invite string `json:"invite"` - Profile string `json:"profile"` +type inviteProfileDTO struct { + Invite string `json:"invite" example:"slakdaslkdl2342"` // Invite to apply to + Profile string `json:"profile" example:"DefaultProfile"` // Profile to use } +// @Summary Set profile for an invite +// @Produce json +// @Param inviteProfileDTO body inviteProfileDTO true "Invite profile object" +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /invites/profile [post] func (app *appContext) SetProfile(gc *gin.Context) { - var req profileReq + var req inviteProfileDTO gc.BindJSON(&req) app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile) // "" means "Don't apply profile" @@ -468,56 +534,87 @@ func (app *appContext) SetProfile(gc *gin.Context) { inv.Profile = req.Profile app.storage.invites[req.Invite] = inv app.storage.storeInvites() - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } +type profileDTO struct { + Admin bool `json:"admin" example:"false"` // Whether profile has admin rights or not + LibraryAccess string `json:"libraries" example:"all"` // Number of libraries profile has access to + FromUser string `json:"fromUser" example:"jeff"` // The user the profile is based on +} + +type getProfilesDTO struct { + Profiles map[string]profileDTO `json:"profiles"` + DefaultProfile string `json:"default_profile"` +} + +// @Summary Get a list of profiles +// @Produce json +// @Success 200 {object} getProfilesDTO +// @Router /profiles [get] func (app *appContext) GetProfiles(gc *gin.Context) { app.storage.loadProfiles() app.debug.Println("Profiles requested") - out := map[string]interface{}{ - "default_profile": app.storage.defaultProfile, + out := getProfilesDTO{ + DefaultProfile: app.storage.defaultProfile, + Profiles: map[string]profileDTO{}, } for name, p := range app.storage.profiles { - out[name] = map[string]interface{}{ - "admin": p.Admin, - "libraries": p.LibraryAccess, - "fromUser": p.FromUser, + out.Profiles[name] = profileDTO{ + Admin: p.Admin, + LibraryAccess: p.LibraryAccess, + FromUser: p.FromUser, } } - fmt.Println(out) gc.JSON(200, out) } +type profileChangeDTO struct { + Name string `json:"name" example:"DefaultProfile" binding:"required"` // Name of the profile +} + +// @Summary Set the default profile to use. +// @Produce json +// @Param profileChangeDTO body profileChangeDTO true "Default profile object" +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /profiles/default [post] func (app *appContext) SetDefaultProfile(gc *gin.Context) { - req := map[string]string{} + req := profileChangeDTO{} gc.BindJSON(&req) - app.info.Printf("Setting default profile to \"%s\"", req["name"]) - if _, ok := app.storage.profiles[req["name"]]; !ok { - app.err.Printf("Profile not found: \"%s\"", req["name"]) + app.info.Printf("Setting default profile to \"%s\"", req.Name) + if _, ok := app.storage.profiles[req.Name]; !ok { + app.err.Printf("Profile not found: \"%s\"", req.Name) respond(500, "Profile not found", gc) return } for name, profile := range app.storage.profiles { - if name == req["name"] { + if name == req.Name { profile.Admin = true app.storage.profiles[name] = profile } else { profile.Admin = false } } - app.storage.defaultProfile = req["name"] - gc.JSON(200, map[string]bool{"success": true}) + app.storage.defaultProfile = req.Name + respondBool(200, true, gc) } -type newProfileReq struct { - Name string `json:"name"` - ID string `json:"id"` - Homescreen bool `json:"homescreen"` +type newProfileDTO struct { + Name string `json:"name" example:"DefaultProfile" binding:"required"` // Name of the profile + ID string `json:"id" example:"kasdjlaskjd342342" binding:"required"` // ID of user to source settings from + Homescreen bool `json:"homescreen" example:"true"` // Whether to store homescreen layout or not } +// @Summary Create a profile based on a Jellyfin user's settings. +// @Produce json +// @Param newProfileDTO body newProfileDTO true "New profile object" +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /profiles [post] func (app *appContext) CreateProfile(gc *gin.Context) { fmt.Println("Profile creation requested") - var req newProfileReq + var req newProfileDTO gc.BindJSON(&req) user, status, err := app.jf.userById(req.ID, false) if !(status == 200 || status == 204) || err != nil { @@ -545,48 +642,75 @@ func (app *appContext) CreateProfile(gc *gin.Context) { app.storage.profiles[req.Name] = profile app.storage.storeProfiles() app.storage.loadProfiles() - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } +// @Summary Delete an existing profile +// @Produce json +// @Param profileChangeDTO body profileChangeDTO true "Delete profile object" +// @Success 200 {object} boolResponse +// @Router /profiles [delete] func (app *appContext) DeleteProfile(gc *gin.Context) { - req := map[string]string{} + req := profileChangeDTO{} gc.BindJSON(&req) - name := req["name"] + name := req.Name if _, ok := app.storage.profiles[name]; ok { delete(app.storage.profiles, name) } app.storage.storeProfiles() - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } +type inviteDTO struct { + Code string `json:"code" example:"sajdlj23423j23"` // Invite code + Days int `json:"days" example:"1"` // Number of days till expiry + Hours int `json:"hours" example:"2"` // Number of hours till expiry + Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry + Created string `json:"created" example:"01/01/20 12:00"` // Date of creation + Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite + UsedBy [][]string `json:"used-by,omitempty"` // Users who have used this invite + NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times + RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable) + Email string `json:"email,omitempty"` // Email the invite was sent to (if applicable) + NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not + NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not +} + +type getInvitesDTO struct { + Profiles []string `json:"profiles"` // List of profiles (name only) + Invites []inviteDTO `json:"invites"` // List of invites +} + +// @Summary Get invites. +// @Produce json +// @Success 200 {object} getInvitesDTO +// @Router /invites [get] func (app *appContext) GetInvites(gc *gin.Context) { app.debug.Println("Invites requested") current_time := time.Now() app.storage.loadInvites() app.checkInvites() - var invites []map[string]interface{} + var invites []inviteDTO for code, inv := range app.storage.invites { _, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, current_time) - invite := map[string]interface{}{ - "code": code, - "days": days, - "hours": hours, - "minutes": minutes, - "created": app.formatDatetime(inv.Created), - "profile": inv.Profile, + invite := inviteDTO{ + Code: code, + Days: days, + Hours: hours, + Minutes: minutes, + Created: app.formatDatetime(inv.Created), + Profile: inv.Profile, + NoLimit: inv.NoLimit, } if len(inv.UsedBy) != 0 { - invite["used-by"] = inv.UsedBy + invite.UsedBy = inv.UsedBy } - if inv.NoLimit { - invite["no-limit"] = true - } - invite["remaining-uses"] = 1 + invite.RemainingUses = 1 if inv.RemainingUses != 0 { - invite["remaining-uses"] = inv.RemainingUses + invite.RemainingUses = inv.RemainingUses } if inv.Email != "" { - invite["email"] = inv.Email + invite.Email = inv.Email } if len(inv.Notify) != 0 { var address string @@ -599,10 +723,11 @@ func (app *appContext) GetInvites(gc *gin.Context) { address = app.config.Section("ui").Key("email").String() } if _, ok := inv.Notify[address]; ok { - for _, notifyType := range []string{"notify-expiry", "notify-creation"} { - if _, ok = inv.Notify[address][notifyType]; ok { - invite[notifyType] = inv.Notify[address][notifyType] - } + if _, ok = inv.Notify[address]["notify-expiry"]; ok { + invite.NotifyExpiry = inv.Notify[address]["notify-expiry"] + } + if _, ok = inv.Notify[address]["notify-creation"]; ok { + invite.NotifyCreation = inv.Notify[address]["notify-creation"] } } } @@ -621,13 +746,28 @@ func (app *appContext) GetInvites(gc *gin.Context) { } } } - resp := map[string]interface{}{ - "profiles": profiles, - "invites": invites, + resp := getInvitesDTO{ + Profiles: profiles, + Invites: invites, } gc.JSON(200, resp) } +// fake DTO, if i actually used this the code would be a lot longer +type setNotifyValues map[string]struct { + NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not + NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not +} + +type setNotifyDTO map[string]setNotifyValues + +// @Summary Set notification preferences for an invite. +// @Produce json +// @Param setNotifyDTO body setNotifyDTO true "Map of invite codes to notification settings objects" +// @Success 200 +// @Failure 400 {object} stringResponse +// @Failure 500 {object} stringResponse +// @Router /invites/notify [post] func (app *appContext) SetNotify(gc *gin.Context) { var req map[string]map[string]bool gc.BindJSON(&req) @@ -639,8 +779,7 @@ func (app *appContext) SetNotify(gc *gin.Context) { invite, ok := app.storage.invites[code] if !ok { app.err.Printf("%s Notification setting change failed: Invalid code", code) - gc.JSON(400, map[string]string{"error": "Invalid invite code"}) - gc.Abort() + respond(400, "Invalid invite code", gc) return } var address string @@ -650,8 +789,7 @@ func (app *appContext) SetNotify(gc *gin.Context) { if !ok { app.err.Printf("%s: Couldn't find email address. Make sure it's set", code) app.debug.Printf("%s: User ID \"%s\"", code, gc.GetString("jfId")) - gc.JSON(500, map[string]string{"error": "Missing user email"}) - gc.Abort() + respond(500, "Missing user email", gc) return } } else { @@ -684,12 +822,18 @@ func (app *appContext) SetNotify(gc *gin.Context) { } } -type deleteReq struct { - Code string `json:"code"` +type deleteInviteDTO struct { + Code string `json:"code" example:"skjadajd43234s"` // Code of invite to delete } +// @Summary Delete an invite. +// @Produce json +// @Param deleteInviteDTO body deleteInviteDTO true "Delete invite object" +// @Success 200 {object} boolResponse +// @Failure 400 {object} stringResponse +// @Router /invites [delete] func (app *appContext) DeleteInvite(gc *gin.Context) { - var req deleteReq + var req deleteInviteDTO gc.BindJSON(&req) app.debug.Printf("%s: Deletion requested", req.Code) var ok bool @@ -698,11 +842,11 @@ func (app *appContext) DeleteInvite(gc *gin.Context) { delete(app.storage.invites, req.Code) app.storage.storeInvites() app.info.Printf("%s: Invite deleted", req.Code) - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) return } app.err.Printf("%s: Deletion failed: Invalid code", req.Code) - respond(401, "Code doesn't exist", gc) + respond(400, "Code doesn't exist", gc) } type dateToParse struct { @@ -718,21 +862,25 @@ func parseDt(date string) time.Time { } type respUser struct { - ID string `json:"id"` - Name string `json:"name"` - Email string `json:"email,omitempty"` - // this magically parses a string to time.time - LastActive string `json:"last_active"` - Admin bool `json:"admin"` + ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user + Name string `json:"name" example:"jeff"` // Username of user + Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available) + LastActive string `json:"last_active"` // Time of last activity on Jellyfin + Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator } -type userResp struct { +type getUsersDTO struct { UserList []respUser `json:"users"` } +// @Summary Get a list of Jellyfin users. +// @Produce json +// @Success 200 {object} getUsersDTO +// @Failure 500 {object} stringResponse +// @Router /users [get] func (app *appContext) GetUsers(gc *gin.Context) { app.debug.Println("Users requested") - var resp userResp + var resp getUsersDTO resp.UserList = []respUser{} users, status, err := app.jf.getUsers(false) if !(status == 200 || status == 204) || err != nil { @@ -761,10 +909,19 @@ func (app *appContext) GetUsers(gc *gin.Context) { } type ombiUser struct { - Name string `json:"name,omitempty"` - ID string `json:"id"` + Name string `json:"name,omitempty" example:"jeff"` // Name of Ombi user + ID string `json:"id" example:"djgkjdg7dkjfsj8"` // userID of Ombi user } +type ombiUsersDTO struct { + Users []ombiUser `json:"users"` +} + +// @Summary Get a list of Ombi users. +// @Produce json +// @Success 200 {object} ombiUsersDTO +// @Failure 500 {object} stringResponse +// @Router /ombi/users [get] func (app *appContext) OmbiUsers(gc *gin.Context) { app.debug.Println("Ombi users requested") users, status, err := app.ombi.getUsers() @@ -781,11 +938,40 @@ func (app *appContext) OmbiUsers(gc *gin.Context) { ID: data["id"].(string), } } - gc.JSON(200, map[string][]ombiUser{"users": userlist}) + gc.JSON(200, ombiUsersDTO{Users: userlist}) } +// @Summary Set new user defaults for Ombi accounts. +// @Produce json +// @Param ombiUser body ombiUser true "User to source settings from" +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /ombi/defaults [post] +func (app *appContext) SetOmbiDefaults(gc *gin.Context) { + var req ombiUser + gc.BindJSON(&req) + template, code, err := app.ombi.templateByID(req.ID) + if err != nil || code != 200 || len(template) == 0 { + app.err.Printf("Couldn't get user from Ombi: %d %s", code, err) + respond(500, "Couldn't get user", gc) + return + } + app.storage.ombi_template = template + fmt.Println(app.storage.ombi_path) + app.storage.storeOmbiTemplate() + respondBool(200, true, gc) +} + +type modifyEmailsDTO map[string]string + +// @Summary Modify user's email addresses. +// @Produce json +// @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to email addresses" +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /users/emails [post] func (app *appContext) ModifyEmails(gc *gin.Context) { - var req map[string]string + var req modifyEmailsDTO gc.BindJSON(&req) fmt.Println(req) app.debug.Println("Email modification requested") @@ -803,30 +989,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { } app.storage.storeEmails() app.info.Println("Email list modified") - gc.JSON(200, map[string]bool{"success": true}) -} - -func (app *appContext) SetOmbiDefaults(gc *gin.Context) { - var req ombiUser - gc.BindJSON(&req) - template, code, err := app.ombi.templateByID(req.ID) - if err != nil || code != 200 || len(template) == 0 { - app.err.Printf("Couldn't get user from Ombi: %d %s", code, err) - respond(500, "Couldn't get user", gc) - return - } - app.storage.ombi_template = template - fmt.Println(app.storage.ombi_path) - app.storage.storeOmbiTemplate() - gc.JSON(200, map[string]bool{"success": true}) -} - -type defaultsReq struct { - From string `json:"from"` - Profile string `json:"profile"` - ApplyTo []string `json:"apply_to"` - ID string `json:"id"` - Homescreen bool `json:"homescreen"` + respondBool(200, true, gc) } /*func (app *appContext) SetDefaults(gc *gin.Context) { @@ -865,9 +1028,25 @@ type defaultsReq struct { gc.JSON(200, map[string]bool{"success": true}) }*/ +type userSettingsDTO struct { + From string `json:"from"` // Whether to apply from "user" or "profile" + Profile string `json:"profile"` // Name of profile (if from = "profile") + ApplyTo []string `json:"apply_to"` // Users to apply settings to + ID string `json:"id"` // ID of user (if from = "user") + Homescreen bool `json:"homescreen"` // Whether to apply homescreen layout or not +} + +type errorListDTO map[string]map[string]string + +// @Summary Apply settings to a list of users, either from a profile or from another user. +// @Produce json +// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings" +// @Success 200 {object} errorListDTO +// @Failure 500 {object} errorListDTO "Lists of errors that occured while applying settings" +// @Router /users/settings [post] func (app *appContext) ApplySettings(gc *gin.Context) { app.info.Println("User settings change requested") - var req defaultsReq + var req userSettingsDTO gc.BindJSON(&req) applyingFrom := "profile" var policy, configuration, displayprefs map[string]interface{} @@ -911,7 +1090,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) { } } app.info.Printf("Applying settings to %d user(s) from %s", len(req.ApplyTo), applyingFrom) - errors := map[string]map[string]string{ + errors := errorListDTO{ "policy": map[string]string{}, "homescreen": map[string]string{}, } @@ -943,6 +1122,10 @@ func (app *appContext) ApplySettings(gc *gin.Context) { gc.JSON(code, errors) } +// @Summary Get jfa-go configuration. +// @Produce json +// @Success 200 {object} configDTO "Uses the same format as config-base.json" +// @Router /config [get] func (app *appContext) GetConfig(gc *gin.Context) { app.info.Println("Config requested") resp := map[string]interface{}{} @@ -976,9 +1159,17 @@ func (app *appContext) GetConfig(gc *gin.Context) { gc.JSON(200, resp) } +// @Summary Modify app config. +// @Produce json +// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings." +// @Success 200 {object} boolResponse +// @Router /config [post] + +type configDTO map[string]interface{} + func (app *appContext) ModifyConfig(gc *gin.Context) { app.info.Println("Config modification requested") - var req map[string]interface{} + var req configDTO gc.BindJSON(&req) tempConfig, _ := ini.Load(app.config_path) for section, settings := range req { @@ -1022,6 +1213,11 @@ func (app *appContext) ModifyConfig(gc *gin.Context) { } } +// @Summary Logout by deleting refresh token from cookies. +// @Produce json +// @Success 200 {object} boolResponse +// @Failure 500 {object} stringResponse +// @Router /logout [post] func (app *appContext) Logout(gc *gin.Context) { cookie, err := gc.Cookie("refresh") if err != nil { @@ -1031,7 +1227,7 @@ func (app *appContext) Logout(gc *gin.Context) { } app.invalidTokens = append(app.invalidTokens, cookie) gc.SetCookie("refresh", "invalid", -1, "/", gc.Request.URL.Hostname(), true, true) - gc.JSON(200, map[string]bool{"success": true}) + respondBool(200, true, gc) } // func Restart() error { diff --git a/auth.go b/auth.go index 77d36e6..e30ae4d 100644 --- a/auth.go +++ b/auth.go @@ -43,17 +43,6 @@ func CreateToken(userId, jfId string) (string, string, error) { return token, refresh, nil } -func respond(code int, message string, gc *gin.Context) { - resp := map[string]string{} - if code == 200 || code == 204 { - resp["response"] = message - } else { - resp["error"] = message - } - gc.JSON(code, resp) - gc.Abort() -} - // Check header for token func (app *appContext) authenticate(gc *gin.Context) { header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2) diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..3c53b26 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,1062 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Harvey Tindall", + "email": "hrfee@protonmail.ch" + }, + "license": { + "name": "MIT", + "url": "https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/config": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get jfa-go configuration.", + "responses": { + "200": { + "description": "Uses the same format as config-base.json", + "schema": { + "$ref": "#/definitions/main.configDTO" + } + } + } + } + }, + "/invites": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get invites.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getInvitesDTO" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Create a new invite.", + "parameters": [ + { + "description": "New invite request object", + "name": "generateInviteDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.generateInviteDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete an invite.", + "parameters": [ + { + "description": "Delete invite object", + "name": "deleteInviteDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.deleteInviteDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/invites/notify": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set notification preferences for an invite.", + "parameters": [ + { + "description": "Map of invite codes to notification settings objects", + "name": "setNotifyDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.setNotifyDTO" + } + } + ], + "responses": { + "200": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/invites/profile": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set profile for an invite", + "parameters": [ + { + "description": "Invite profile object", + "name": "inviteProfileDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.inviteProfileDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/logout": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Logout by deleting refresh token from cookies.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/newUser": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Creates a new Jellyfin user via invite code", + "parameters": [ + { + "description": "New user request object", + "name": "newUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newUserDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.PasswordValidation" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.PasswordValidation" + } + } + } + } + }, + "/ombi/defaults": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set new user defaults for Ombi accounts.", + "parameters": [ + { + "description": "User to source settings from", + "name": "ombiUser", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.ombiUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/ombi/users": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of Ombi users.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.ombiUsersDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/profiles": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of profiles", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getProfilesDTO" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Create a profile based on a Jellyfin user's settings.", + "parameters": [ + { + "description": "New profile object", + "name": "newProfileDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newProfileDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete an existing profile", + "parameters": [ + { + "description": "Delete profile object", + "name": "profileChangeDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.profileChangeDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + } + } + } + }, + "/profiles/default": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set the default profile to use.", + "parameters": [ + { + "description": "Default profile object", + "name": "profileChangeDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.profileChangeDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/users": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of Jellyfin users.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getUsersDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Creates a new Jellyfin user without an invite.", + "parameters": [ + { + "description": "New user request object", + "name": "newUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newUserDTO" + } + } + ], + "responses": { + "200": {} + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete a list of users, optionally notifying them why.", + "parameters": [ + { + "description": "User deletion request object", + "name": "deleteUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.deleteUserDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + }, + "500": { + "description": "List of errors", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + } + } + } + }, + "/users/emails": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Modify user's email addresses.", + "parameters": [ + { + "description": "Map of userIDs to email addresses", + "name": "modifyEmailsDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.modifyEmailsDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/users/settings": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Apply settings to a list of users, either from a profile or from another user.", + "parameters": [ + { + "description": "Parameters for applying settings", + "name": "userSettingsDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.userSettingsDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + }, + "500": { + "description": "Lists of errors that occured while applying settings", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + } + } + } + } + }, + "definitions": { + "main.PasswordValidation": { + "type": "object", + "properties": { + "characters": { + "description": "Number of characters", + "type": "boolean" + }, + "lowercase characters": { + "description": "Number of lowercase characters", + "type": "boolean" + }, + "numbers": { + "description": "Number of numbers", + "type": "boolean" + }, + "special characters": { + "description": "Number of special characters", + "type": "boolean" + }, + "uppercase characters": { + "description": "Number of uppercase characters", + "type": "boolean" + } + } + }, + "main.boolResponse": { + "type": "object", + "properties": { + "error": { + "type": "boolean", + "example": true + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "main.configDTO": { + "type": "object", + "additionalProperties": true + }, + "main.deleteInviteDTO": { + "type": "object", + "properties": { + "code": { + "description": "Code of invite to delete", + "type": "string", + "example": "skjadajd43234s" + } + } + }, + "main.deleteUserDTO": { + "type": "object", + "required": [ + "users" + ], + "properties": { + "notify": { + "description": "Whether to notify users of deletion", + "type": "boolean" + }, + "reason": { + "description": "Account deletion reason (for notification)", + "type": "string" + }, + "users": { + "description": "List of usernames to delete", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "main.errorListDTO": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "main.generateInviteDTO": { + "type": "object", + "properties": { + "days": { + "description": "Number of days", + "type": "integer", + "example": 1 + }, + "email": { + "description": "Send invite to this address", + "type": "string", + "example": "jeff@jellyf.in" + }, + "hours": { + "description": "Number of hours", + "type": "integer", + "example": 2 + }, + "minutes": { + "description": "Number of minutes", + "type": "integer", + "example": 3 + }, + "multiple-uses": { + "description": "Allow multiple uses", + "type": "boolean", + "example": true + }, + "no-limit": { + "description": "No invite use limit", + "type": "boolean", + "example": false + }, + "profile": { + "description": "Name of profile to apply on this invite", + "type": "string", + "example": "DefaultProfile" + }, + "remaining-uses": { + "description": "Remaining invite uses", + "type": "integer", + "example": 5 + } + } + }, + "main.getInvitesDTO": { + "type": "object", + "properties": { + "invites": { + "description": "List of invites", + "type": "array", + "items": { + "$ref": "#/definitions/main.inviteDTO" + } + }, + "profiles": { + "description": "List of profiles (name only)", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "main.getProfilesDTO": { + "type": "object", + "properties": { + "default_profile": { + "type": "string" + }, + "profiles": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/main.profileDTO" + } + } + } + }, + "main.getUsersDTO": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/main.respUser" + } + } + } + }, + "main.inviteDTO": { + "type": "object", + "properties": { + "code": { + "description": "Invite code", + "type": "string", + "example": "sajdlj23423j23" + }, + "created": { + "description": "Date of creation", + "type": "string", + "example": "01/01/20 12:00" + }, + "days": { + "description": "Number of days till expiry", + "type": "integer", + "example": 1 + }, + "email": { + "description": "Email the invite was sent to (if applicable)", + "type": "string" + }, + "hours": { + "description": "Number of hours till expiry", + "type": "integer", + "example": 2 + }, + "minutes": { + "description": "Number of minutes till expiry", + "type": "integer", + "example": 3 + }, + "no-limit": { + "description": "If true, invite can be used any number of times", + "type": "boolean" + }, + "notify-creation": { + "description": "Whether to notify the requesting user of account creation or not", + "type": "boolean" + }, + "notify-expiry": { + "description": "Whether to notify the requesting user of expiry or not", + "type": "boolean" + }, + "profile": { + "description": "Profile used on this invite", + "type": "string", + "example": "DefaultProfile" + }, + "remaining-uses": { + "description": "Remaining number of uses (if applicable)", + "type": "integer" + }, + "used-by": { + "description": "Users who have used this invite", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "main.inviteProfileDTO": { + "type": "object", + "properties": { + "invite": { + "description": "Invite to apply to", + "type": "string", + "example": "slakdaslkdl2342" + }, + "profile": { + "description": "Profile to use", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.modifyEmailsDTO": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "main.newProfileDTO": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "homescreen": { + "description": "Whether to store homescreen layout or not", + "type": "boolean", + "example": true + }, + "id": { + "description": "ID of user to source settings from", + "type": "string", + "example": "kasdjlaskjd342342" + }, + "name": { + "description": "Name of the profile", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.newUserDTO": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "code": { + "description": "Invite code (required on /newUser)", + "type": "string", + "example": "abc0933jncjkcjj" + }, + "email": { + "description": "User's email address", + "type": "string", + "example": "jeff@jellyf.in" + }, + "password": { + "description": "User's password", + "type": "string", + "example": "guest" + }, + "username": { + "description": "User's username", + "type": "string", + "example": "jeff" + } + } + }, + "main.ombiUser": { + "type": "object", + "properties": { + "id": { + "description": "userID of Ombi user", + "type": "string", + "example": "djgkjdg7dkjfsj8" + }, + "name": { + "description": "Name of Ombi user", + "type": "string", + "example": "jeff" + } + } + }, + "main.ombiUsersDTO": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/main.ombiUser" + } + } + } + }, + "main.profileChangeDTO": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Name of the profile", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.profileDTO": { + "type": "object", + "properties": { + "admin": { + "description": "Whether profile has admin rights or not", + "type": "boolean", + "example": false + }, + "fromUser": { + "description": "The user the profile is based on", + "type": "string", + "example": "jeff" + }, + "libraries": { + "description": "Number of libraries profile has access to", + "type": "string", + "example": "all" + } + } + }, + "main.respUser": { + "type": "object", + "properties": { + "admin": { + "description": "Whether or not the user is Administrator", + "type": "boolean", + "example": false + }, + "email": { + "description": "Email address of user (if available)", + "type": "string", + "example": "jeff@jellyf.in" + }, + "id": { + "description": "userID of user", + "type": "string", + "example": "fdgsdfg45534fa" + }, + "last_active": { + "description": "Time of last activity on Jellyfin", + "type": "string" + }, + "name": { + "description": "Username of user", + "type": "string", + "example": "jeff" + } + } + }, + "main.setNotifyDTO": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/main.setNotifyValues" + } + }, + "main.setNotifyValues": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "notify-creation": { + "description": "Whether to notify the requesting user of account creation or not", + "type": "boolean" + }, + "notify-expiry": { + "description": "Whether to notify the requesting user of expiry or not", + "type": "boolean" + } + } + } + }, + "main.stringResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "errorDescription" + }, + "response": { + "type": "string", + "example": "message" + } + } + }, + "main.userSettingsDTO": { + "type": "object", + "properties": { + "apply_to": { + "description": "Users to apply settings to", + "type": "array", + "items": { + "type": "string" + } + }, + "from": { + "description": "Whether to apply from \"user\" or \"profile\"", + "type": "string" + }, + "homescreen": { + "description": "Whether to apply homescreen layout or not", + "type": "boolean" + }, + "id": { + "description": "ID of user (if from = \"user\")", + "type": "string" + }, + "profile": { + "description": "Name of profile (if from = \"profile\")", + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "0.2.0", + Host: "", + BasePath: "/", + Schemes: []string{}, + Title: "jfa-go internal API", + Description: "API for the jfa-go frontend", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/docs/go.mod b/docs/go.mod new file mode 100644 index 0000000..6ec1d14 --- /dev/null +++ b/docs/go.mod @@ -0,0 +1,3 @@ +module github.com/hrfee/jfa-go/docs + +go 1.15 diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..7b1e87e --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,999 @@ +{ + "swagger": "2.0", + "info": { + "description": "API for the jfa-go frontend", + "title": "jfa-go internal API", + "contact": { + "name": "Harvey Tindall", + "email": "hrfee@protonmail.ch" + }, + "license": { + "name": "MIT", + "url": "https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE" + }, + "version": "0.2.0" + }, + "basePath": "/", + "paths": { + "/config": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get jfa-go configuration.", + "responses": { + "200": { + "description": "Uses the same format as config-base.json", + "schema": { + "$ref": "#/definitions/main.configDTO" + } + } + } + } + }, + "/invites": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get invites.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getInvitesDTO" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Create a new invite.", + "parameters": [ + { + "description": "New invite request object", + "name": "generateInviteDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.generateInviteDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete an invite.", + "parameters": [ + { + "description": "Delete invite object", + "name": "deleteInviteDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.deleteInviteDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/invites/notify": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set notification preferences for an invite.", + "parameters": [ + { + "description": "Map of invite codes to notification settings objects", + "name": "setNotifyDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.setNotifyDTO" + } + } + ], + "responses": { + "200": {}, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/invites/profile": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set profile for an invite", + "parameters": [ + { + "description": "Invite profile object", + "name": "inviteProfileDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.inviteProfileDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/logout": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Logout by deleting refresh token from cookies.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/newUser": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Creates a new Jellyfin user via invite code", + "parameters": [ + { + "description": "New user request object", + "name": "newUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newUserDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.PasswordValidation" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.PasswordValidation" + } + } + } + } + }, + "/ombi/defaults": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set new user defaults for Ombi accounts.", + "parameters": [ + { + "description": "User to source settings from", + "name": "ombiUser", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.ombiUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/ombi/users": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of Ombi users.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.ombiUsersDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/profiles": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of profiles", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getProfilesDTO" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Create a profile based on a Jellyfin user's settings.", + "parameters": [ + { + "description": "New profile object", + "name": "newProfileDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newProfileDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete an existing profile", + "parameters": [ + { + "description": "Delete profile object", + "name": "profileChangeDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.profileChangeDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + } + } + } + }, + "/profiles/default": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Set the default profile to use.", + "parameters": [ + { + "description": "Default profile object", + "name": "profileChangeDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.profileChangeDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/users": { + "get": { + "produces": [ + "application/json" + ], + "summary": "Get a list of Jellyfin users.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.getUsersDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "summary": "Creates a new Jellyfin user without an invite.", + "parameters": [ + { + "description": "New user request object", + "name": "newUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.newUserDTO" + } + } + ], + "responses": { + "200": {} + } + }, + "delete": { + "produces": [ + "application/json" + ], + "summary": "Delete a list of users, optionally notifying them why.", + "parameters": [ + { + "description": "User deletion request object", + "name": "deleteUserDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.deleteUserDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + }, + "500": { + "description": "List of errors", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + } + } + } + }, + "/users/emails": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Modify user's email addresses.", + "parameters": [ + { + "description": "Map of userIDs to email addresses", + "name": "modifyEmailsDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.modifyEmailsDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.boolResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/main.stringResponse" + } + } + } + } + }, + "/users/settings": { + "post": { + "produces": [ + "application/json" + ], + "summary": "Apply settings to a list of users, either from a profile or from another user.", + "parameters": [ + { + "description": "Parameters for applying settings", + "name": "userSettingsDTO", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.userSettingsDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + }, + "500": { + "description": "Lists of errors that occured while applying settings", + "schema": { + "$ref": "#/definitions/main.errorListDTO" + } + } + } + } + } + }, + "definitions": { + "main.PasswordValidation": { + "type": "object", + "properties": { + "characters": { + "description": "Number of characters", + "type": "boolean" + }, + "lowercase characters": { + "description": "Number of lowercase characters", + "type": "boolean" + }, + "numbers": { + "description": "Number of numbers", + "type": "boolean" + }, + "special characters": { + "description": "Number of special characters", + "type": "boolean" + }, + "uppercase characters": { + "description": "Number of uppercase characters", + "type": "boolean" + } + } + }, + "main.boolResponse": { + "type": "object", + "properties": { + "error": { + "type": "boolean", + "example": true + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "main.configDTO": { + "type": "object", + "additionalProperties": true + }, + "main.deleteInviteDTO": { + "type": "object", + "properties": { + "code": { + "description": "Code of invite to delete", + "type": "string", + "example": "skjadajd43234s" + } + } + }, + "main.deleteUserDTO": { + "type": "object", + "required": [ + "users" + ], + "properties": { + "notify": { + "description": "Whether to notify users of deletion", + "type": "boolean" + }, + "reason": { + "description": "Account deletion reason (for notification)", + "type": "string" + }, + "users": { + "description": "List of usernames to delete", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "main.errorListDTO": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "main.generateInviteDTO": { + "type": "object", + "properties": { + "days": { + "description": "Number of days", + "type": "integer", + "example": 1 + }, + "email": { + "description": "Send invite to this address", + "type": "string", + "example": "jeff@jellyf.in" + }, + "hours": { + "description": "Number of hours", + "type": "integer", + "example": 2 + }, + "minutes": { + "description": "Number of minutes", + "type": "integer", + "example": 3 + }, + "multiple-uses": { + "description": "Allow multiple uses", + "type": "boolean", + "example": true + }, + "no-limit": { + "description": "No invite use limit", + "type": "boolean", + "example": false + }, + "profile": { + "description": "Name of profile to apply on this invite", + "type": "string", + "example": "DefaultProfile" + }, + "remaining-uses": { + "description": "Remaining invite uses", + "type": "integer", + "example": 5 + } + } + }, + "main.getInvitesDTO": { + "type": "object", + "properties": { + "invites": { + "description": "List of invites", + "type": "array", + "items": { + "$ref": "#/definitions/main.inviteDTO" + } + }, + "profiles": { + "description": "List of profiles (name only)", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "main.getProfilesDTO": { + "type": "object", + "properties": { + "default_profile": { + "type": "string" + }, + "profiles": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/main.profileDTO" + } + } + } + }, + "main.getUsersDTO": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/main.respUser" + } + } + } + }, + "main.inviteDTO": { + "type": "object", + "properties": { + "code": { + "description": "Invite code", + "type": "string", + "example": "sajdlj23423j23" + }, + "created": { + "description": "Date of creation", + "type": "string", + "example": "01/01/20 12:00" + }, + "days": { + "description": "Number of days till expiry", + "type": "integer", + "example": 1 + }, + "email": { + "description": "Email the invite was sent to (if applicable)", + "type": "string" + }, + "hours": { + "description": "Number of hours till expiry", + "type": "integer", + "example": 2 + }, + "minutes": { + "description": "Number of minutes till expiry", + "type": "integer", + "example": 3 + }, + "no-limit": { + "description": "If true, invite can be used any number of times", + "type": "boolean" + }, + "notify-creation": { + "description": "Whether to notify the requesting user of account creation or not", + "type": "boolean" + }, + "notify-expiry": { + "description": "Whether to notify the requesting user of expiry or not", + "type": "boolean" + }, + "profile": { + "description": "Profile used on this invite", + "type": "string", + "example": "DefaultProfile" + }, + "remaining-uses": { + "description": "Remaining number of uses (if applicable)", + "type": "integer" + }, + "used-by": { + "description": "Users who have used this invite", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "main.inviteProfileDTO": { + "type": "object", + "properties": { + "invite": { + "description": "Invite to apply to", + "type": "string", + "example": "slakdaslkdl2342" + }, + "profile": { + "description": "Profile to use", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.modifyEmailsDTO": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "main.newProfileDTO": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "homescreen": { + "description": "Whether to store homescreen layout or not", + "type": "boolean", + "example": true + }, + "id": { + "description": "ID of user to source settings from", + "type": "string", + "example": "kasdjlaskjd342342" + }, + "name": { + "description": "Name of the profile", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.newUserDTO": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "code": { + "description": "Invite code (required on /newUser)", + "type": "string", + "example": "abc0933jncjkcjj" + }, + "email": { + "description": "User's email address", + "type": "string", + "example": "jeff@jellyf.in" + }, + "password": { + "description": "User's password", + "type": "string", + "example": "guest" + }, + "username": { + "description": "User's username", + "type": "string", + "example": "jeff" + } + } + }, + "main.ombiUser": { + "type": "object", + "properties": { + "id": { + "description": "userID of Ombi user", + "type": "string", + "example": "djgkjdg7dkjfsj8" + }, + "name": { + "description": "Name of Ombi user", + "type": "string", + "example": "jeff" + } + } + }, + "main.ombiUsersDTO": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/main.ombiUser" + } + } + } + }, + "main.profileChangeDTO": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Name of the profile", + "type": "string", + "example": "DefaultProfile" + } + } + }, + "main.profileDTO": { + "type": "object", + "properties": { + "admin": { + "description": "Whether profile has admin rights or not", + "type": "boolean", + "example": false + }, + "fromUser": { + "description": "The user the profile is based on", + "type": "string", + "example": "jeff" + }, + "libraries": { + "description": "Number of libraries profile has access to", + "type": "string", + "example": "all" + } + } + }, + "main.respUser": { + "type": "object", + "properties": { + "admin": { + "description": "Whether or not the user is Administrator", + "type": "boolean", + "example": false + }, + "email": { + "description": "Email address of user (if available)", + "type": "string", + "example": "jeff@jellyf.in" + }, + "id": { + "description": "userID of user", + "type": "string", + "example": "fdgsdfg45534fa" + }, + "last_active": { + "description": "Time of last activity on Jellyfin", + "type": "string" + }, + "name": { + "description": "Username of user", + "type": "string", + "example": "jeff" + } + } + }, + "main.setNotifyDTO": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/main.setNotifyValues" + } + }, + "main.setNotifyValues": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "notify-creation": { + "description": "Whether to notify the requesting user of account creation or not", + "type": "boolean" + }, + "notify-expiry": { + "description": "Whether to notify the requesting user of expiry or not", + "type": "boolean" + } + } + } + }, + "main.stringResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "errorDescription" + }, + "response": { + "type": "string", + "example": "message" + } + } + }, + "main.userSettingsDTO": { + "type": "object", + "properties": { + "apply_to": { + "description": "Users to apply settings to", + "type": "array", + "items": { + "type": "string" + } + }, + "from": { + "description": "Whether to apply from \"user\" or \"profile\"", + "type": "string" + }, + "homescreen": { + "description": "Whether to apply homescreen layout or not", + "type": "boolean" + }, + "id": { + "description": "ID of user (if from = \"user\")", + "type": "string" + }, + "profile": { + "description": "Name of profile (if from = \"profile\")", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..29c7b6d --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,678 @@ +basePath: / +definitions: + main.PasswordValidation: + properties: + characters: + description: Number of characters + type: boolean + lowercase characters: + description: Number of lowercase characters + type: boolean + numbers: + description: Number of numbers + type: boolean + special characters: + description: Number of special characters + type: boolean + uppercase characters: + description: Number of uppercase characters + type: boolean + type: object + main.boolResponse: + properties: + error: + example: true + type: boolean + success: + example: false + type: boolean + type: object + main.configDTO: + additionalProperties: true + type: object + main.deleteInviteDTO: + properties: + code: + description: Code of invite to delete + example: skjadajd43234s + type: string + type: object + main.deleteUserDTO: + properties: + notify: + description: Whether to notify users of deletion + type: boolean + reason: + description: Account deletion reason (for notification) + type: string + users: + description: List of usernames to delete + items: + type: string + type: array + required: + - users + type: object + main.errorListDTO: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + main.generateInviteDTO: + properties: + days: + description: Number of days + example: 1 + type: integer + email: + description: Send invite to this address + example: jeff@jellyf.in + type: string + hours: + description: Number of hours + example: 2 + type: integer + minutes: + description: Number of minutes + example: 3 + type: integer + multiple-uses: + description: Allow multiple uses + example: true + type: boolean + no-limit: + description: No invite use limit + example: false + type: boolean + profile: + description: Name of profile to apply on this invite + example: DefaultProfile + type: string + remaining-uses: + description: Remaining invite uses + example: 5 + type: integer + type: object + main.getInvitesDTO: + properties: + invites: + description: List of invites + items: + $ref: '#/definitions/main.inviteDTO' + type: array + profiles: + description: List of profiles (name only) + items: + type: string + type: array + type: object + main.getProfilesDTO: + properties: + default_profile: + type: string + profiles: + additionalProperties: + $ref: '#/definitions/main.profileDTO' + type: object + type: object + main.getUsersDTO: + properties: + users: + items: + $ref: '#/definitions/main.respUser' + type: array + type: object + main.inviteDTO: + properties: + code: + description: Invite code + example: sajdlj23423j23 + type: string + created: + description: Date of creation + example: 01/01/20 12:00 + type: string + days: + description: Number of days till expiry + example: 1 + type: integer + email: + description: Email the invite was sent to (if applicable) + type: string + hours: + description: Number of hours till expiry + example: 2 + type: integer + minutes: + description: Number of minutes till expiry + example: 3 + type: integer + no-limit: + description: If true, invite can be used any number of times + type: boolean + notify-creation: + description: Whether to notify the requesting user of account creation or not + type: boolean + notify-expiry: + description: Whether to notify the requesting user of expiry or not + type: boolean + profile: + description: Profile used on this invite + example: DefaultProfile + type: string + remaining-uses: + description: Remaining number of uses (if applicable) + type: integer + used-by: + description: Users who have used this invite + items: + items: + type: string + type: array + type: array + type: object + main.inviteProfileDTO: + properties: + invite: + description: Invite to apply to + example: slakdaslkdl2342 + type: string + profile: + description: Profile to use + example: DefaultProfile + type: string + type: object + main.modifyEmailsDTO: + additionalProperties: + type: string + type: object + main.newProfileDTO: + properties: + homescreen: + description: Whether to store homescreen layout or not + example: true + type: boolean + id: + description: ID of user to source settings from + example: kasdjlaskjd342342 + type: string + name: + description: Name of the profile + example: DefaultProfile + type: string + required: + - id + - name + type: object + main.newUserDTO: + properties: + code: + description: Invite code (required on /newUser) + example: abc0933jncjkcjj + type: string + email: + description: User's email address + example: jeff@jellyf.in + type: string + password: + description: User's password + example: guest + type: string + username: + description: User's username + example: jeff + type: string + required: + - password + - username + type: object + main.ombiUser: + properties: + id: + description: userID of Ombi user + example: djgkjdg7dkjfsj8 + type: string + name: + description: Name of Ombi user + example: jeff + type: string + type: object + main.ombiUsersDTO: + properties: + users: + items: + $ref: '#/definitions/main.ombiUser' + type: array + type: object + main.profileChangeDTO: + properties: + name: + description: Name of the profile + example: DefaultProfile + type: string + required: + - name + type: object + main.profileDTO: + properties: + admin: + description: Whether profile has admin rights or not + example: false + type: boolean + fromUser: + description: The user the profile is based on + example: jeff + type: string + libraries: + description: Number of libraries profile has access to + example: all + type: string + type: object + main.respUser: + properties: + admin: + description: Whether or not the user is Administrator + example: false + type: boolean + email: + description: Email address of user (if available) + example: jeff@jellyf.in + type: string + id: + description: userID of user + example: fdgsdfg45534fa + type: string + last_active: + description: Time of last activity on Jellyfin + type: string + name: + description: Username of user + example: jeff + type: string + type: object + main.setNotifyDTO: + additionalProperties: + $ref: '#/definitions/main.setNotifyValues' + type: object + main.setNotifyValues: + additionalProperties: + properties: + notify-creation: + description: Whether to notify the requesting user of account creation or not + type: boolean + notify-expiry: + description: Whether to notify the requesting user of expiry or not + type: boolean + type: object + type: object + main.stringResponse: + properties: + error: + example: errorDescription + type: string + response: + example: message + type: string + type: object + main.userSettingsDTO: + properties: + apply_to: + description: Users to apply settings to + items: + type: string + type: array + from: + description: Whether to apply from "user" or "profile" + type: string + homescreen: + description: Whether to apply homescreen layout or not + type: boolean + id: + description: ID of user (if from = "user") + type: string + profile: + description: Name of profile (if from = "profile") + type: string + type: object +info: + contact: + email: hrfee@protonmail.ch + name: Harvey Tindall + description: API for the jfa-go frontend + license: + name: MIT + url: https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE + title: jfa-go internal API + version: 0.2.0 +paths: + /config: + get: + produces: + - application/json + responses: + "200": + description: Uses the same format as config-base.json + schema: + $ref: '#/definitions/main.configDTO' + summary: Get jfa-go configuration. + /invites: + delete: + parameters: + - description: Delete invite object + in: body + name: deleteInviteDTO + required: true + schema: + $ref: '#/definitions/main.deleteInviteDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/main.stringResponse' + summary: Delete an invite. + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.getInvitesDTO' + summary: Get invites. + post: + parameters: + - description: New invite request object + in: body + name: generateInviteDTO + required: true + schema: + $ref: '#/definitions/main.generateInviteDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + summary: Create a new invite. + /invites/notify: + post: + parameters: + - description: Map of invite codes to notification settings objects + in: body + name: setNotifyDTO + required: true + schema: + $ref: '#/definitions/main.setNotifyDTO' + produces: + - application/json + responses: + "200": {} + "400": + description: Bad Request + schema: + $ref: '#/definitions/main.stringResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Set notification preferences for an invite. + /invites/profile: + post: + parameters: + - description: Invite profile object + in: body + name: inviteProfileDTO + required: true + schema: + $ref: '#/definitions/main.inviteProfileDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Set profile for an invite + /logout: + post: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Logout by deleting refresh token from cookies. + /newUser: + post: + parameters: + - description: New user request object + in: body + name: newUserDTO + required: true + schema: + $ref: '#/definitions/main.newUserDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.PasswordValidation' + "400": + description: Bad Request + schema: + $ref: '#/definitions/main.PasswordValidation' + summary: Creates a new Jellyfin user via invite code + /ombi/defaults: + post: + parameters: + - description: User to source settings from + in: body + name: ombiUser + required: true + schema: + $ref: '#/definitions/main.ombiUser' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Set new user defaults for Ombi accounts. + /ombi/users: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.ombiUsersDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Get a list of Ombi users. + /profiles: + delete: + parameters: + - description: Delete profile object + in: body + name: profileChangeDTO + required: true + schema: + $ref: '#/definitions/main.profileChangeDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + summary: Delete an existing profile + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.getProfilesDTO' + summary: Get a list of profiles + post: + parameters: + - description: New profile object + in: body + name: newProfileDTO + required: true + schema: + $ref: '#/definitions/main.newProfileDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Create a profile based on a Jellyfin user's settings. + /profiles/default: + post: + parameters: + - description: Default profile object + in: body + name: profileChangeDTO + required: true + schema: + $ref: '#/definitions/main.profileChangeDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Set the default profile to use. + /users: + delete: + parameters: + - description: User deletion request object + in: body + name: deleteUserDTO + required: true + schema: + $ref: '#/definitions/main.deleteUserDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/main.stringResponse' + "500": + description: List of errors + schema: + $ref: '#/definitions/main.errorListDTO' + summary: Delete a list of users, optionally notifying them why. + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.getUsersDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Get a list of Jellyfin users. + post: + parameters: + - description: New user request object + in: body + name: newUserDTO + required: true + schema: + $ref: '#/definitions/main.newUserDTO' + produces: + - application/json + responses: + "200": {} + summary: Creates a new Jellyfin user without an invite. + /users/emails: + post: + parameters: + - description: Map of userIDs to email addresses + in: body + name: modifyEmailsDTO + required: true + schema: + $ref: '#/definitions/main.modifyEmailsDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.boolResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/main.stringResponse' + summary: Modify user's email addresses. + /users/settings: + post: + parameters: + - description: Parameters for applying settings + in: body + name: userSettingsDTO + required: true + schema: + $ref: '#/definitions/main.userSettingsDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/main.errorListDTO' + "500": + description: Lists of errors that occured while applying settings + schema: + $ref: '#/definitions/main.errorListDTO' + summary: Apply settings to a list of users, either from a profile or from another user. +swagger: "2.0" diff --git a/go.mod b/go.mod index 8ecb5e9..29ab3ed 100644 --- a/go.mod +++ b/go.mod @@ -2,25 +2,38 @@ module github.com/hrfee/jfa-go go 1.14 +replace github.com/hrfee/jfa-go/docs => ./docs + require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 github.com/gin-contrib/pprof v1.3.0 github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1 github.com/gin-gonic/gin v1.6.3 github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-openapi/spec v0.19.9 // indirect + github.com/go-openapi/swag v0.19.9 // indirect github.com/go-playground/validator/v10 v10.3.0 // indirect github.com/golang/protobuf v1.4.2 // indirect + github.com/hrfee/jfa-go/docs v0.0.0-00010101000000-000000000000 github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e github.com/json-iterator/go v1.1.10 // indirect github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9 github.com/lithammer/shortuuid/v3 v3.0.4 github.com/mailgun/mailgun-go/v4 v4.1.3 - github.com/mailru/easyjson v0.7.3 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858 github.com/pkg/errors v0.9.1 // indirect + github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 + github.com/swaggo/gin-swagger v1.2.0 + github.com/swaggo/swag v1.6.7 // indirect + github.com/urfave/cli/v2 v2.2.0 // indirect + golang.org/x/net v0.0.0-20200923182212-328152dc79b1 // indirect golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed // indirect + golang.org/x/tools v0.0.0-20200923182640-463111b69878 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/ini.v1 v1.60.0 gopkg.in/yaml.v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index cf6d64d..8e9a5ff 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= @@ -15,14 +28,21 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 h1:xLG16iua01X7Gzms9045s2Y2niNpvSY/Zb1oBwgNYZY= github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ= github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1 h1:plQYoJeO9lI8Ag0xZy7dDF8FMwIOHsQylKjcclknvIc= github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= @@ -31,6 +51,28 @@ github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuY github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc= +github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -45,6 +87,7 @@ github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -68,6 +111,8 @@ github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e h1:OGunVjqY7y4 github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e/go.mod h1:Fy2gCFfZhay8jplf/Csj6cyH/oshQTkLQYZbKkcV+SY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -75,6 +120,14 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9 h1:GQE1iatYDRrIidq4Zf/9ZzKWyrTk2sXOYc1JADbkAjQ= github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= +github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= +github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= @@ -86,12 +139,22 @@ github.com/mailgun/mailgun-go v1.1.1 h1:mjMcm4qz+SbjAYbGJ6DKROViKtO5S0YjpuOUxQfd github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o= github.com/mailgun/mailgun-go/v4 v4.1.3 h1:KLa5EZaOMMeyvY/lfAhWxv9ealB3mtUsMz0O9XmTtP0= github.com/mailgun/mailgun-go/v4 v4.1.3/go.mod h1:R9kHUQBptF4iSEjhriCQizplCDwrnDShy8w/iPiOfaM= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.2 h1:V9ecaZWDYm7v9uJ15RZD6DajMu5sE0hdep0aoDwT9g4= github.com/mailru/easyjson v0.7.2/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.3 h1:M6wcO9gFHCIPynXGu4iA+NMs//FCgFUWR2jxqV3/+Xk= github.com/mailru/easyjson v0.7.3/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -103,50 +166,121 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858 h1:lgbJiJQx8bXo+eM88AFdd0VxUvaTLzCBXpK+H9poJ+Y= +github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858/go.mod h1:y02HeaN0visd95W6cEX2NXDv5sCwyqfzucWTdDGEwYY= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= +github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= +github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= +github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= +github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1 h1:Iu68XRPd67wN4aRGGWwwq6bZo/25jR6uu52l/j2KkUE= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ= +golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -167,13 +301,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.60.0 h1:P5ZzC7RJO04094NJYlEnBdFK2wwmnCAy/+7sAzvWs60= gopkg.in/ini.v1 v1.60.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/main.go b/main.go index d1bb57d..00e1bf3 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,10 @@ import ( "github.com/gin-contrib/pprof" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" + _ "github.com/hrfee/jfa-go/docs" "github.com/lithammer/shortuuid/v3" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" "gopkg.in/ini.v1" ) @@ -108,6 +111,7 @@ var ( PORT *int DEBUG *bool TEST bool + SWAGGER *bool ) func test(app *appContext) { @@ -165,6 +169,7 @@ func start(asDaemon, firstCall bool) { HOST = flag.String("host", "", "alternate address to host web ui on.") PORT = flag.Int("port", 0, "alternate port to host web ui on.") DEBUG = flag.Bool("debug", false, "Enables debug logging and exposes pprof.") + SWAGGER = flag.Bool("swagger", false, "Enable swagger at /swagger/index.html") flag.Parse() } @@ -453,6 +458,10 @@ func start(asDaemon, firstCall bool) { router.POST("/newUser", app.NewUser) router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.local_path, "static"), false))) router.GET("/invite/:invCode", app.InviteProxy) + if *SWAGGER { + app.info.Println("WARNING: Swagger should not be used on a public instance.") + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + } api := router.Group("/", app.webAuth()) router.POST("/logout", app.Logout) api.DELETE("/users", app.DeleteUser) @@ -532,6 +541,15 @@ func flagPassed(name string) (found bool) { return } +// @title jfa-go internal API +// @version 0.2.0 +// @description API for the jfa-go frontend +// @contact.name Harvey Tindall +// @contact.email hrfee@protonmail.ch +// @license.name MIT +// @license.url https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE +// @BasePath / + func main() { fmt.Printf("jfa-go version: %s (%s)\n", VERSION, COMMIT) folder := "/tmp" diff --git a/pwval.go b/pwval.go index 894a571..efa76d7 100644 --- a/pwval.go +++ b/pwval.go @@ -19,9 +19,18 @@ func (vd *Validator) init(criteria ValidatorConf) { vd.criteria = criteria } +// This isn't used, its for swagger +type PasswordValidation struct { + Characters bool `json:"characters,omitempty"` // Number of characters + Lowercase bool `json:"lowercase characters,omitempty"` // Number of lowercase characters + Uppercase bool `json:"uppercase characters,omitempty"` // Number of uppercase characters + Numbers bool `json:"numbers,omitempty"` // Number of numbers + Specials bool `json:"special characters,omitempty"` // Number of special characters +} + func (vd *Validator) validate(password string) map[string]bool { count := map[string]int{} - for key, _ := range vd.criteria { + for key := range vd.criteria { count[key] = 0 } for _, c := range password { diff --git a/ts/settings.ts b/ts/settings.ts index 89af315..41fdc9c 100644 --- a/ts/settings.ts +++ b/ts/settings.ts @@ -144,15 +144,16 @@ const populateProfiles = (noTable?: boolean): void => _get("/profiles", null, fu const profileList = document.getElementById('profileList'); profileList.textContent = ''; availableProfiles = [this.response["default_profile"]]; - for (let name in this.response) { + for (let name in this.response["profiles"]) { if (name != availableProfiles[0]) { availableProfiles.push(name); } + const reqProfile = this.response["profiles"][name]; if (!noTable && name != "default_profile") { const profile: Profile = { - Admin: this.response[name]["admin"], - LibraryAccess: this.response[name]["libraries"], - FromUser: this.response[name]["fromUser"] + Admin: reqProfile["admin"], + LibraryAccess: reqProfile["libraries"], + FromUser: reqProfile["fromUser"] }; profileList.innerHTML += ` ${name}