refactor(admin): extract shared admin overview, user-create and user-view helpers
Move the admin overview computation and struct into models.BuildOverview / models.Overview, the admin create-user flow into models.CreateUserAsAdmin / models.CreateUserBody, and the admin user response view into a new pkg/routes/api/shared package (shared.AdminUser / shared.NewAdminUser) so both the v1 and v2 admin routes call the same code. The v1 handlers are refactored onto these helpers and stay byte-identical on the wire.
This commit is contained in:
parent
9c3c1047ac
commit
e25f997281
|
|
@ -0,0 +1,83 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/license"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type ShareCounts struct {
|
||||
LinkShares int64 `json:"link_shares" readOnly:"true" doc:"Number of link shares across all projects."`
|
||||
TeamShares int64 `json:"team_shares" readOnly:"true" doc:"Number of team-project shares."`
|
||||
UserShares int64 `json:"user_shares" readOnly:"true" doc:"Number of user-project shares."`
|
||||
}
|
||||
|
||||
type Overview struct {
|
||||
Users int64 `json:"users" readOnly:"true" doc:"Total number of user accounts."`
|
||||
Projects int64 `json:"projects" readOnly:"true" doc:"Total number of projects."`
|
||||
Tasks int64 `json:"tasks" readOnly:"true" doc:"Total number of tasks."`
|
||||
Teams int64 `json:"teams" readOnly:"true" doc:"Total number of teams."`
|
||||
Shares ShareCounts `json:"shares" readOnly:"true" doc:"Aggregate share counts."`
|
||||
License license.Info `json:"license" readOnly:"true" doc:"Snapshot of the instance license state."`
|
||||
}
|
||||
|
||||
// BuildOverview returns aggregate instance counts plus the current license snapshot.
|
||||
func BuildOverview(s *xorm.Session) (*Overview, error) {
|
||||
users, err := s.Table("users").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projects, err := s.Table("projects").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks, err := s.Table("tasks").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams, err := s.Table("teams").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
linkShares, err := s.Table("link_shares").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teamShares, err := s.Table("team_projects").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userShares, err := s.Table("users_projects").Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Overview{
|
||||
Users: users,
|
||||
Projects: projects,
|
||||
Tasks: tasks,
|
||||
Teams: teams,
|
||||
Shares: ShareCounts{
|
||||
LinkShares: linkShares,
|
||||
TeamShares: teamShares,
|
||||
UserShares: userShares,
|
||||
},
|
||||
License: license.CurrentInfo(),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// CreateUserBody wraps user.APIUserPassword with admin-only fields.
|
||||
type CreateUserBody struct {
|
||||
// The full name of the new user. Optional.
|
||||
Name string `json:"name" doc:"The full name of the new user. Optional."`
|
||||
// The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja.
|
||||
Language string `json:"language" valid:"language" doc:"IETF BCP 47 language code; must exist in Vikunja."`
|
||||
user.APIUserPassword
|
||||
// Mark the new user as an instance admin.
|
||||
IsAdmin bool `json:"is_admin" doc:"Mark the new user as an instance admin."`
|
||||
// Activate the new user immediately without email confirmation.
|
||||
SkipEmailConfirm bool `json:"skip_email_confirm" doc:"Activate the new user immediately, skipping email confirmation."`
|
||||
}
|
||||
|
||||
// CreateUserAsAdmin provisions a new local account on behalf of an instance admin,
|
||||
// honouring the admin-only is_admin and skip_email_confirm fields and bypassing the
|
||||
// public-registration toggle. It commits s and returns the persisted user reloaded
|
||||
// so the status reflects what was actually stored.
|
||||
func CreateUserAsAdmin(s *xorm.Session, body *CreateUserBody) (*user.User, error) {
|
||||
newUser, err := RegisterUser(s, &user.User{
|
||||
Username: body.Username,
|
||||
Password: body.Password,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
Language: body.Language,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if body.IsAdmin {
|
||||
if _, err := s.ID(newUser.ID).Cols("is_admin").Update(&user.User{IsAdmin: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newUser.IsAdmin = true
|
||||
}
|
||||
|
||||
// Force Active when the admin asked to skip, or when no mailer exists to send the confirmation.
|
||||
if body.SkipEmailConfirm || !config.MailerEnabled.GetBool() {
|
||||
if err := user.SetUserStatus(s, newUser, user.StatusActive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newUser.Status = user.StatusActive
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reload on a fresh session so the returned status reflects what was actually
|
||||
// persisted (e.g. StatusEmailConfirmationRequired on mail-enabled instances).
|
||||
rs := db.NewSession()
|
||||
defer rs.Close()
|
||||
return user.GetUserByID(rs, newUser.ID)
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package shared holds route helpers used by both /api/v1 and /api/v2 so the two
|
||||
// versions render identical responses without one importing the other.
|
||||
package shared
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/modules/auth/openid"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// AdminUser re-exposes fields hidden by the default user.User JSON view.
|
||||
type AdminUser struct {
|
||||
*user.User
|
||||
IsAdmin bool `json:"is_admin" readOnly:"true" doc:"Whether the user is an instance admin."`
|
||||
Status user.Status `json:"status" readOnly:"true" doc:"Account status (0=active, 1=email-confirmation required, 2=disabled, 3=locked)."`
|
||||
Issuer string `json:"issuer" readOnly:"true" doc:"Authentication issuer; empty or 'local' for local accounts."`
|
||||
Subject string `json:"subject,omitempty" readOnly:"true" doc:"External subject identifier, for non-local accounts."`
|
||||
AuthProvider string `json:"auth_provider,omitempty" readOnly:"true" doc:"Resolved auth provider name (e.g. 'LDAP' or an OIDC provider), empty for local accounts."`
|
||||
}
|
||||
|
||||
// NewAdminUser builds the admin-facing user view, resolving the auth-provider
|
||||
// display name from the configured OIDC providers.
|
||||
func NewAdminUser(u *user.User, providers []*openid.Provider) *AdminUser {
|
||||
return &AdminUser{
|
||||
User: u,
|
||||
IsAdmin: u.IsAdmin,
|
||||
Status: u.Status,
|
||||
Issuer: u.Issuer,
|
||||
Subject: u.Subject,
|
||||
AuthProvider: resolveAuthProvider(u, providers),
|
||||
}
|
||||
}
|
||||
|
||||
func resolveAuthProvider(u *user.User, providers []*openid.Provider) string {
|
||||
switch u.Issuer {
|
||||
case "", user.IssuerLocal:
|
||||
return ""
|
||||
case user.IssuerLDAP:
|
||||
return "LDAP"
|
||||
}
|
||||
for _, provider := range providers {
|
||||
issuerURL, err := provider.Issuer()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if issuerURL == u.Issuer {
|
||||
return provider.Name
|
||||
}
|
||||
}
|
||||
return u.Issuer
|
||||
}
|
||||
|
|
@ -20,77 +20,27 @@ import (
|
|||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/license"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
type ShareCounts struct {
|
||||
LinkShares int64 `json:"link_shares"`
|
||||
TeamShares int64 `json:"team_shares"`
|
||||
UserShares int64 `json:"user_shares"`
|
||||
}
|
||||
|
||||
type Overview struct {
|
||||
Users int64 `json:"users"`
|
||||
Projects int64 `json:"projects"`
|
||||
Tasks int64 `json:"tasks"`
|
||||
Teams int64 `json:"teams"`
|
||||
Shares ShareCounts `json:"shares"`
|
||||
License license.Info `json:"license"`
|
||||
}
|
||||
|
||||
// GetOverview returns aggregate instance counts and metadata.
|
||||
// @Summary Admin overview
|
||||
// @Description Returns per-instance counts (users, projects, shares) plus version and license info. Instance-admin only, gated by the admin_panel feature.
|
||||
// @tags admin
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} admin.Overview
|
||||
// @Success 200 {object} models.Overview
|
||||
// @Failure 404 {object} web.HTTPError
|
||||
// @Router /admin/overview [get]
|
||||
func GetOverview(c *echo.Context) error {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users, err := s.Table("users").Count()
|
||||
overview, err := models.BuildOverview(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projects, err := s.Table("projects").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tasks, err := s.Table("tasks").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teams, err := s.Table("teams").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
linkShares, err := s.Table("link_shares").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teamShares, err := s.Table("team_projects").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userShares, err := s.Table("users_projects").Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, Overview{
|
||||
Users: users,
|
||||
Projects: projects,
|
||||
Tasks: tasks,
|
||||
Teams: teams,
|
||||
Shares: ShareCounts{
|
||||
LinkShares: linkShares,
|
||||
TeamShares: teamShares,
|
||||
UserShares: userShares,
|
||||
},
|
||||
License: license.CurrentInfo(),
|
||||
})
|
||||
return c.JSON(http.StatusOK, overview)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,28 +20,14 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth/openid"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/routes/api/shared"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
// CreateUserBody wraps user.APIUserPassword with admin-only fields.
|
||||
type CreateUserBody struct {
|
||||
// The full name of the new user. Optional.
|
||||
Name string `json:"name"`
|
||||
// The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja.
|
||||
Language string `json:"language" valid:"language"`
|
||||
user.APIUserPassword
|
||||
// Mark the new user as an instance admin.
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
// Activate the new user immediately without email confirmation.
|
||||
SkipEmailConfirm bool `json:"skip_email_confirm"`
|
||||
}
|
||||
|
||||
// CreateUser provisions a new account on behalf of an instance admin.
|
||||
// @Summary Create a user (admin)
|
||||
// @Description Create a new local user account. Respects the admin-only fields `is_admin` and `skip_email_confirm`. The public registration toggle is bypassed.
|
||||
|
|
@ -49,12 +35,12 @@ type CreateUserBody struct {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param body body admin.CreateUserBody true "The user to create"
|
||||
// @Success 200 {object} admin.User
|
||||
// @Param body body models.CreateUserBody true "The user to create"
|
||||
// @Success 200 {object} shared.AdminUser
|
||||
// @Failure 400 {object} web.HTTPError
|
||||
// @Router /admin/users [post]
|
||||
func CreateUser(c *echo.Context) error {
|
||||
body := &CreateUserBody{}
|
||||
body := &models.CreateUserBody{}
|
||||
if err := c.Bind(body); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."})
|
||||
}
|
||||
|
|
@ -69,52 +55,15 @@ func CreateUser(c *echo.Context) error {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
newUser, err := models.RegisterUser(s, &user.User{
|
||||
Username: body.Username,
|
||||
Password: body.Password,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
Language: body.Language,
|
||||
})
|
||||
newUser, err := models.CreateUserAsAdmin(s, body)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if body.IsAdmin {
|
||||
if _, err := s.ID(newUser.ID).Cols("is_admin").Update(&user.User{IsAdmin: true}); err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
newUser.IsAdmin = true
|
||||
}
|
||||
|
||||
// Force Active when the admin asked to skip, or when no mailer exists to send the confirmation.
|
||||
if body.SkipEmailConfirm || !config.MailerEnabled.GetBool() {
|
||||
if err := user.SetUserStatus(s, newUser, user.StatusActive); err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
newUser.Status = user.StatusActive
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload the user so the returned status reflects what was actually persisted
|
||||
// (e.g. StatusEmailConfirmationRequired on mail-enabled instances).
|
||||
rs := db.NewSession()
|
||||
defer rs.Close()
|
||||
newUser, err = user.GetUserByID(rs, newUser.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
providers, err := openid.GetAllProviders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, newAdminUser(newUser, providers))
|
||||
return c.JSON(http.StatusOK, shared.NewAdminUser(newUser, providers))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,52 +18,13 @@ package admin
|
|||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/modules/auth/openid"
|
||||
"code.vikunja.io/api/pkg/routes/api/shared"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// User re-exposes fields hidden by the default user.User JSON view.
|
||||
type User struct {
|
||||
*user.User
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
Status user.Status `json:"status"`
|
||||
Issuer string `json:"issuer"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
AuthProvider string `json:"auth_provider,omitempty"`
|
||||
}
|
||||
|
||||
func newAdminUser(u *user.User, providers []*openid.Provider) *User {
|
||||
return &User{
|
||||
User: u,
|
||||
IsAdmin: u.IsAdmin,
|
||||
Status: u.Status,
|
||||
Issuer: u.Issuer,
|
||||
Subject: u.Subject,
|
||||
AuthProvider: resolveAuthProvider(u, providers),
|
||||
}
|
||||
}
|
||||
|
||||
func resolveAuthProvider(u *user.User, providers []*openid.Provider) string {
|
||||
switch u.Issuer {
|
||||
case "", user.IssuerLocal:
|
||||
return ""
|
||||
case user.IssuerLDAP:
|
||||
return "LDAP"
|
||||
}
|
||||
for _, provider := range providers {
|
||||
issuerURL, err := provider.Issuer()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if issuerURL == u.Issuer {
|
||||
return provider.Name
|
||||
}
|
||||
}
|
||||
return u.Issuer
|
||||
}
|
||||
|
||||
// UserList backs the admin list-users route via handler.ReadAllWeb; only ReadAll is used.
|
||||
type UserList struct {
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
|
|
@ -79,7 +40,7 @@ type UserList struct {
|
|||
// @Param s query string false "Search string matched against username and email."
|
||||
// @Param page query int false "Page number, defaults to 1."
|
||||
// @Param per_page query int false "Items per page, defaults to the service setting."
|
||||
// @Success 200 {array} admin.User
|
||||
// @Success 200 {array} shared.AdminUser
|
||||
// @Failure 404 {object} web.HTTPError
|
||||
// @Router /admin/users [get]
|
||||
func (*UserList) ReadAll(s *xorm.Session, _ web.Auth, search string, page, perPage int) (interface{}, int, int64, error) {
|
||||
|
|
@ -106,9 +67,9 @@ func (*UserList) ReadAll(s *xorm.Session, _ web.Auth, search string, page, perPa
|
|||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
out := make([]*User, 0, len(users))
|
||||
out := make([]*shared.AdminUser, 0, len(users))
|
||||
for _, u := range users {
|
||||
out = append(out, newAdminUser(u, providers))
|
||||
out = append(out, shared.NewAdminUser(u, providers))
|
||||
}
|
||||
return out, len(out), totalCount, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import (
|
|||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth/openid"
|
||||
"code.vikunja.io/api/pkg/routes/api/shared"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ type IsAdminPatch struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "User ID"
|
||||
// @Param body body admin.IsAdminPatch true "New admin value"
|
||||
// @Success 200 {object} admin.User
|
||||
// @Success 200 {object} shared.AdminUser
|
||||
// @Failure 400 {object} web.HTTPError
|
||||
// @Failure 404 {object} web.HTTPError
|
||||
// @Router /admin/users/{id}/admin [patch]
|
||||
|
|
@ -92,5 +94,5 @@ func PatchAdmin(c *echo.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, newAdminUser(target, providers))
|
||||
return c.JSON(http.StatusOK, shared.NewAdminUser(target, providers))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import (
|
|||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth/openid"
|
||||
"code.vikunja.io/api/pkg/routes/api/shared"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ type StatusPatch struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "User ID"
|
||||
// @Param body body admin.StatusPatch true "Status"
|
||||
// @Success 200 {object} admin.User
|
||||
// @Success 200 {object} shared.AdminUser
|
||||
// @Failure 400 {object} web.HTTPError
|
||||
// @Failure 404 {object} web.HTTPError
|
||||
// @Router /admin/users/{id}/status [patch]
|
||||
|
|
@ -96,7 +98,7 @@ func PatchStatus(c *echo.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, newAdminUser(target, providers))
|
||||
return c.JSON(http.StatusOK, shared.NewAdminUser(target, providers))
|
||||
}
|
||||
|
||||
// DeleteUser removes a user either immediately or through the self-deletion flow.
|
||||
|
|
|
|||
Loading…
Reference in New Issue