Make it possible to change more user fields
This commit is contained in:
parent
7ee70b24ee
commit
fc56c1406a
8 changed files with 113 additions and 73 deletions
|
|
@ -132,6 +132,15 @@ var Users = Table[
|
|||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Avatar: column{
|
||||
Name: "avatar",
|
||||
DBType: "uuid",
|
||||
Default: "NULL",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
IsActive: column{
|
||||
Name: "is_active",
|
||||
DBType: "boolean",
|
||||
|
|
@ -144,27 +153,18 @@ var Users = Table[
|
|||
IsDronePilot: column{
|
||||
Name: "is_drone_pilot",
|
||||
DBType: "boolean",
|
||||
Default: "NULL",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
IsWarrant: column{
|
||||
Name: "is_warrant",
|
||||
DBType: "boolean",
|
||||
Default: "NULL",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Avatar: column{
|
||||
Name: "avatar",
|
||||
DBType: "uuid",
|
||||
Default: "NULL",
|
||||
Comment: "",
|
||||
Nullable: true,
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
|
|
@ -246,15 +246,15 @@ type userColumns struct {
|
|||
PasswordHashType column
|
||||
PasswordHash column
|
||||
Role column
|
||||
Avatar column
|
||||
IsActive column
|
||||
IsDronePilot column
|
||||
IsWarrant column
|
||||
Avatar column
|
||||
}
|
||||
|
||||
func (c userColumns) AsSlice() []column {
|
||||
return []column{
|
||||
c.ID, c.ArcgisAccessToken, c.ArcgisLicense, c.ArcgisRefreshToken, c.ArcgisRefreshTokenExpires, c.ArcgisRole, c.DisplayName, c.Email, c.OrganizationID, c.Username, c.PasswordHashType, c.PasswordHash, c.Role, c.IsActive, c.IsDronePilot, c.IsWarrant, c.Avatar,
|
||||
c.ID, c.ArcgisAccessToken, c.ArcgisLicense, c.ArcgisRefreshToken, c.ArcgisRefreshTokenExpires, c.ArcgisRole, c.DisplayName, c.Email, c.OrganizationID, c.Username, c.PasswordHashType, c.PasswordHash, c.Role, c.Avatar, c.IsActive, c.IsDronePilot, c.IsWarrant,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
-- +goose Up
|
||||
ALTER TABLE user_
|
||||
ADD COLUMN avatar UUID,
|
||||
ADD COLUMN is_active BOOLEAN,
|
||||
ADD COLUMN is_drone_pilot BOOLEAN,
|
||||
ADD COLUMN is_warrant BOOLEAN,
|
||||
ADD COLUMN avatar UUID;
|
||||
UPDATE user_ SET is_active = TRUE;
|
||||
ALTER TABLE user_ ALTER COLUMN is_active SET NOT NULL;
|
||||
ADD COLUMN is_warrant BOOLEAN;
|
||||
UPDATE user_ SET avatar=NULL, is_active = TRUE, is_drone_pilot=FALSE, is_warrant=FALSE;
|
||||
ALTER TABLE user_
|
||||
ALTER COLUMN is_active SET NOT NULL,
|
||||
ALTER COLUMN is_drone_pilot SET NOT NULL,
|
||||
ALTER COLUMN is_warrant SET NOT NULL;
|
||||
-- +goose Down
|
||||
ALTER TABLE user_
|
||||
DROP COLUMN avatar,
|
||||
DROP COLUMN is_active,
|
||||
DROP COLUMN is_drone_pilot,
|
||||
DROP COLUMN is_warrant,
|
||||
DROP COLUMN avatar;
|
||||
DROP COLUMN is_warrant;
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ type User struct {
|
|||
PasswordHashType enums.Hashtype `db:"password_hash_type" `
|
||||
PasswordHash string `db:"password_hash" `
|
||||
Role enums.Userrole `db:"role" `
|
||||
IsActive bool `db:"is_active" `
|
||||
IsDronePilot null.Val[bool] `db:"is_drone_pilot" `
|
||||
IsWarrant null.Val[bool] `db:"is_warrant" `
|
||||
Avatar null.Val[uuid.UUID] `db:"avatar" `
|
||||
IsActive bool `db:"is_active" `
|
||||
IsDronePilot bool `db:"is_drone_pilot" `
|
||||
IsWarrant bool `db:"is_warrant" `
|
||||
|
||||
R userR `db:"-" `
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ type userR struct {
|
|||
func buildUserColumns(alias string) userColumns {
|
||||
return userColumns{
|
||||
ColumnsExpr: expr.NewColumnsExpr(
|
||||
"id", "arcgis_access_token", "arcgis_license", "arcgis_refresh_token", "arcgis_refresh_token_expires", "arcgis_role", "display_name", "email", "organization_id", "username", "password_hash_type", "password_hash", "role", "is_active", "is_drone_pilot", "is_warrant", "avatar",
|
||||
"id", "arcgis_access_token", "arcgis_license", "arcgis_refresh_token", "arcgis_refresh_token_expires", "arcgis_role", "display_name", "email", "organization_id", "username", "password_hash_type", "password_hash", "role", "avatar", "is_active", "is_drone_pilot", "is_warrant",
|
||||
).WithParent("user_"),
|
||||
tableAlias: alias,
|
||||
ID: psql.Quote(alias, "id"),
|
||||
|
|
@ -109,10 +109,10 @@ func buildUserColumns(alias string) userColumns {
|
|||
PasswordHashType: psql.Quote(alias, "password_hash_type"),
|
||||
PasswordHash: psql.Quote(alias, "password_hash"),
|
||||
Role: psql.Quote(alias, "role"),
|
||||
Avatar: psql.Quote(alias, "avatar"),
|
||||
IsActive: psql.Quote(alias, "is_active"),
|
||||
IsDronePilot: psql.Quote(alias, "is_drone_pilot"),
|
||||
IsWarrant: psql.Quote(alias, "is_warrant"),
|
||||
Avatar: psql.Quote(alias, "avatar"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,10 +132,10 @@ type userColumns struct {
|
|||
PasswordHashType psql.Expression
|
||||
PasswordHash psql.Expression
|
||||
Role psql.Expression
|
||||
Avatar psql.Expression
|
||||
IsActive psql.Expression
|
||||
IsDronePilot psql.Expression
|
||||
IsWarrant psql.Expression
|
||||
Avatar psql.Expression
|
||||
}
|
||||
|
||||
func (c userColumns) Alias() string {
|
||||
|
|
@ -163,10 +163,10 @@ type UserSetter struct {
|
|||
PasswordHashType omit.Val[enums.Hashtype] `db:"password_hash_type" `
|
||||
PasswordHash omit.Val[string] `db:"password_hash" `
|
||||
Role omit.Val[enums.Userrole] `db:"role" `
|
||||
IsActive omit.Val[bool] `db:"is_active" `
|
||||
IsDronePilot omitnull.Val[bool] `db:"is_drone_pilot" `
|
||||
IsWarrant omitnull.Val[bool] `db:"is_warrant" `
|
||||
Avatar omitnull.Val[uuid.UUID] `db:"avatar" `
|
||||
IsActive omit.Val[bool] `db:"is_active" `
|
||||
IsDronePilot omit.Val[bool] `db:"is_drone_pilot" `
|
||||
IsWarrant omit.Val[bool] `db:"is_warrant" `
|
||||
}
|
||||
|
||||
func (s UserSetter) SetColumns() []string {
|
||||
|
|
@ -210,18 +210,18 @@ func (s UserSetter) SetColumns() []string {
|
|||
if s.Role.IsValue() {
|
||||
vals = append(vals, "role")
|
||||
}
|
||||
if !s.Avatar.IsUnset() {
|
||||
vals = append(vals, "avatar")
|
||||
}
|
||||
if s.IsActive.IsValue() {
|
||||
vals = append(vals, "is_active")
|
||||
}
|
||||
if !s.IsDronePilot.IsUnset() {
|
||||
if s.IsDronePilot.IsValue() {
|
||||
vals = append(vals, "is_drone_pilot")
|
||||
}
|
||||
if !s.IsWarrant.IsUnset() {
|
||||
if s.IsWarrant.IsValue() {
|
||||
vals = append(vals, "is_warrant")
|
||||
}
|
||||
if !s.Avatar.IsUnset() {
|
||||
vals = append(vals, "avatar")
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
|
|
@ -265,17 +265,17 @@ func (s UserSetter) Overwrite(t *User) {
|
|||
if s.Role.IsValue() {
|
||||
t.Role = s.Role.MustGet()
|
||||
}
|
||||
if !s.Avatar.IsUnset() {
|
||||
t.Avatar = s.Avatar.MustGetNull()
|
||||
}
|
||||
if s.IsActive.IsValue() {
|
||||
t.IsActive = s.IsActive.MustGet()
|
||||
}
|
||||
if !s.IsDronePilot.IsUnset() {
|
||||
t.IsDronePilot = s.IsDronePilot.MustGetNull()
|
||||
if s.IsDronePilot.IsValue() {
|
||||
t.IsDronePilot = s.IsDronePilot.MustGet()
|
||||
}
|
||||
if !s.IsWarrant.IsUnset() {
|
||||
t.IsWarrant = s.IsWarrant.MustGetNull()
|
||||
}
|
||||
if !s.Avatar.IsUnset() {
|
||||
t.Avatar = s.Avatar.MustGetNull()
|
||||
if s.IsWarrant.IsValue() {
|
||||
t.IsWarrant = s.IsWarrant.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,26 +364,26 @@ func (s *UserSetter) Apply(q *dialect.InsertQuery) {
|
|||
vals[12] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.IsActive.IsValue() {
|
||||
vals[13] = psql.Arg(s.IsActive.MustGet())
|
||||
if !s.Avatar.IsUnset() {
|
||||
vals[13] = psql.Arg(s.Avatar.MustGetNull())
|
||||
} else {
|
||||
vals[13] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if !s.IsDronePilot.IsUnset() {
|
||||
vals[14] = psql.Arg(s.IsDronePilot.MustGetNull())
|
||||
if s.IsActive.IsValue() {
|
||||
vals[14] = psql.Arg(s.IsActive.MustGet())
|
||||
} else {
|
||||
vals[14] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if !s.IsWarrant.IsUnset() {
|
||||
vals[15] = psql.Arg(s.IsWarrant.MustGetNull())
|
||||
if s.IsDronePilot.IsValue() {
|
||||
vals[15] = psql.Arg(s.IsDronePilot.MustGet())
|
||||
} else {
|
||||
vals[15] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if !s.Avatar.IsUnset() {
|
||||
vals[16] = psql.Arg(s.Avatar.MustGetNull())
|
||||
if s.IsWarrant.IsValue() {
|
||||
vals[16] = psql.Arg(s.IsWarrant.MustGet())
|
||||
} else {
|
||||
vals[16] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
|
@ -490,6 +490,13 @@ func (s UserSetter) Expressions(prefix ...string) []bob.Expression {
|
|||
}})
|
||||
}
|
||||
|
||||
if !s.Avatar.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "avatar")...),
|
||||
psql.Arg(s.Avatar),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.IsActive.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "is_active")...),
|
||||
|
|
@ -497,27 +504,20 @@ func (s UserSetter) Expressions(prefix ...string) []bob.Expression {
|
|||
}})
|
||||
}
|
||||
|
||||
if !s.IsDronePilot.IsUnset() {
|
||||
if s.IsDronePilot.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "is_drone_pilot")...),
|
||||
psql.Arg(s.IsDronePilot),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.IsWarrant.IsUnset() {
|
||||
if s.IsWarrant.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "is_warrant")...),
|
||||
psql.Arg(s.IsWarrant),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Avatar.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "avatar")...),
|
||||
psql.Arg(s.Avatar),
|
||||
}})
|
||||
}
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
|
|
@ -3314,10 +3314,10 @@ type userWhere[Q psql.Filterable] struct {
|
|||
PasswordHashType psql.WhereMod[Q, enums.Hashtype]
|
||||
PasswordHash psql.WhereMod[Q, string]
|
||||
Role psql.WhereMod[Q, enums.Userrole]
|
||||
IsActive psql.WhereMod[Q, bool]
|
||||
IsDronePilot psql.WhereNullMod[Q, bool]
|
||||
IsWarrant psql.WhereNullMod[Q, bool]
|
||||
Avatar psql.WhereNullMod[Q, uuid.UUID]
|
||||
IsActive psql.WhereMod[Q, bool]
|
||||
IsDronePilot psql.WhereMod[Q, bool]
|
||||
IsWarrant psql.WhereMod[Q, bool]
|
||||
}
|
||||
|
||||
func (userWhere[Q]) AliasedAs(alias string) userWhere[Q] {
|
||||
|
|
@ -3339,10 +3339,10 @@ func buildUserWhere[Q psql.Filterable](cols userColumns) userWhere[Q] {
|
|||
PasswordHashType: psql.Where[Q, enums.Hashtype](cols.PasswordHashType),
|
||||
PasswordHash: psql.Where[Q, string](cols.PasswordHash),
|
||||
Role: psql.Where[Q, enums.Userrole](cols.Role),
|
||||
IsActive: psql.Where[Q, bool](cols.IsActive),
|
||||
IsDronePilot: psql.WhereNull[Q, bool](cols.IsDronePilot),
|
||||
IsWarrant: psql.WhereNull[Q, bool](cols.IsWarrant),
|
||||
Avatar: psql.WhereNull[Q, uuid.UUID](cols.Avatar),
|
||||
IsActive: psql.Where[Q, bool](cols.IsActive),
|
||||
IsDronePilot: psql.Where[Q, bool](cols.IsDronePilot),
|
||||
IsWarrant: psql.Where[Q, bool](cols.IsWarrant),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,6 @@ func NewErrorStatus(status int, mesg_format string, args ...any) *ErrorWithStatu
|
|||
Status: status,
|
||||
}
|
||||
}
|
||||
func NewForbidden(mesg_format string, args ...any) *ErrorWithStatus {
|
||||
return NewErrorStatus(http.StatusForbidden, mesg_format, args...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ func CreateUser(ctx context.Context, username string, name string, password_hash
|
|||
u_setter := models.UserSetter{
|
||||
DisplayName: omit.From(name),
|
||||
IsActive: omit.From(true),
|
||||
IsDronePilot: omit.From(false),
|
||||
IsWarrant: omit.From(false),
|
||||
OrganizationID: omit.From(o.ID),
|
||||
PasswordHash: omit.From(password_hash),
|
||||
PasswordHashType: omit.From(enums.HashtypeBcrypt14),
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.Us
|
|||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "user id conversion: %w", err)
|
||||
}
|
||||
user_changes := &models.UserSetter{}
|
||||
if !(user.HasRoot() || user.Role == enums.UserroleAccountOwner || user.ID == user_id) {
|
||||
return "", nhttp.NewForbidden("Only account owners can change other users")
|
||||
}
|
||||
if updates.Avatar.IsValue() {
|
||||
avatar_uuid, err := res.router.UUIDFromURI("avatar.ByUUIDGet", updates.Avatar.MustGet())
|
||||
if err != nil {
|
||||
|
|
@ -98,6 +101,29 @@ func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.Us
|
|||
if updates.DisplayName.IsValue() {
|
||||
user_changes.DisplayName = updates.DisplayName
|
||||
}
|
||||
if updates.Role.IsValue() {
|
||||
// Don't allow privilege escalation
|
||||
if user.HasRoot() || user.Role == enums.UserroleAccountOwner {
|
||||
user_changes.Role = updates.Role.MustGet()
|
||||
} else {
|
||||
return "", nhttp.NewBadRequest("you aren't allowed to change roles")
|
||||
}
|
||||
}
|
||||
if updates.Tags.IsValue() {
|
||||
for i, v := range updates.Tags.MustGet() {
|
||||
user_changes.IsDronePilot = omit.From(false)
|
||||
user_changes.IsWarrant = omit.From(false)
|
||||
switch v {
|
||||
case "drone pilot":
|
||||
user_changes.IsDronePilot = omit.From(true)
|
||||
case "warrant":
|
||||
user_changes.IsWarrant = omit.From(true)
|
||||
default:
|
||||
return "", nhttp.NewBadRequest("'%s' (item %d) is not a valid tag", v, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = platform.UserUpdate(ctx, user, user_id, user_changes)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("user update: %w", err)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<RouterLink :to="`/_/configuration${user.uri}`">
|
||||
<RouterLink :to="`/_/configuration/user/${user.id}`">
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-person-x"></i>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -291,13 +291,7 @@ const optionRoles: Option[] = [
|
|||
{ value: "tech3", label: "Tech 3" },
|
||||
];
|
||||
|
||||
const availableTags: string[] = [
|
||||
"warrant",
|
||||
"drone pilot",
|
||||
"certified",
|
||||
"supervisor",
|
||||
"field ops",
|
||||
];
|
||||
const availableTags: string[] = ["warrant", "drone pilot"];
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInput.value?.click();
|
||||
|
|
@ -354,6 +348,9 @@ const removeTag = (tag: string) => {
|
|||
interface UserRequestPut {
|
||||
avatar?: string | null;
|
||||
display_name?: string;
|
||||
is_active?: boolean;
|
||||
role?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
const saveChanges = async () => {
|
||||
const u = user.value;
|
||||
|
|
@ -399,6 +396,15 @@ const saveChanges = async () => {
|
|||
if (uc.display_name != u.display_name) {
|
||||
payload.display_name = uc.display_name;
|
||||
}
|
||||
if (uc.is_active != u.is_active) {
|
||||
payload.is_active = uc.is_active;
|
||||
}
|
||||
if (uc.role != u.role) {
|
||||
payload.role = uc.role;
|
||||
}
|
||||
if (uc.tags != u.tags) {
|
||||
payload.tags = uc.tags;
|
||||
}
|
||||
const url = u.uri;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue