feat(backend): add DELETE /users/@me endpoint

This commit is contained in:
Sam 2023-03-08 10:32:18 +01:00
parent c4b8b26ec7
commit ff3d612b06
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
9 changed files with 162 additions and 45 deletions

View file

@ -5,23 +5,27 @@ package queries
import (
"context"
"fmt"
"github.com/jackc/pgtype"
"github.com/jackc/pgx/v4"
)
const getUserByIDSQL = `SELECT * FROM users WHERE id = $1;`
type GetUserByIDRow struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
SelfDelete *bool `json:"self_delete"`
DeleteReason *string `json:"delete_reason"`
}
// GetUserByID implements Querier.GetUserByID.
@ -31,7 +35,7 @@ func (q *DBQuerier) GetUserByID(ctx context.Context, id string) (GetUserByIDRow,
var item GetUserByIDRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("query GetUserByID: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {
@ -54,7 +58,7 @@ func (q *DBQuerier) GetUserByIDScan(results pgx.BatchResults) (GetUserByIDRow, e
var item GetUserByIDRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("scan GetUserByIDBatch row: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {
@ -69,17 +73,20 @@ func (q *DBQuerier) GetUserByIDScan(results pgx.BatchResults) (GetUserByIDRow, e
const getUserByUsernameSQL = `SELECT * FROM users WHERE username = $1;`
type GetUserByUsernameRow struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
SelfDelete *bool `json:"self_delete"`
DeleteReason *string `json:"delete_reason"`
}
// GetUserByUsername implements Querier.GetUserByUsername.
@ -89,7 +96,7 @@ func (q *DBQuerier) GetUserByUsername(ctx context.Context, username string) (Get
var item GetUserByUsernameRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("query GetUserByUsername: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {
@ -112,7 +119,7 @@ func (q *DBQuerier) GetUserByUsernameScan(results pgx.BatchResults) (GetUserByUs
var item GetUserByUsernameRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("scan GetUserByUsernameBatch row: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {
@ -137,17 +144,20 @@ type UpdateUserNamesPronounsParams struct {
}
type UpdateUserNamesPronounsRow struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
ID string `json:"id"`
Username string `json:"username"`
DisplayName *string `json:"display_name"`
Bio *string `json:"bio"`
AvatarUrls []string `json:"avatar_urls"`
Links []string `json:"links"`
Discord *string `json:"discord"`
DiscordUsername *string `json:"discord_username"`
MaxInvites int32 `json:"max_invites"`
Names []FieldEntry `json:"names"`
Pronouns []PronounEntry `json:"pronouns"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
SelfDelete *bool `json:"self_delete"`
DeleteReason *string `json:"delete_reason"`
}
// UpdateUserNamesPronouns implements Querier.UpdateUserNamesPronouns.
@ -157,7 +167,7 @@ func (q *DBQuerier) UpdateUserNamesPronouns(ctx context.Context, params UpdateUs
var item UpdateUserNamesPronounsRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("query UpdateUserNamesPronouns: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {
@ -180,7 +190,7 @@ func (q *DBQuerier) UpdateUserNamesPronounsScan(results pgx.BatchResults) (Updat
var item UpdateUserNamesPronounsRow
namesArray := q.types.newFieldEntryArray()
pronounsArray := q.types.newPronounEntryArray()
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil {
if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray, &item.DeletedAt, &item.SelfDelete, &item.DeleteReason); err != nil {
return item, fmt.Errorf("scan UpdateUserNamesPronounsBatch row: %w", err)
}
if err := namesArray.AssignTo(&item.Names); err != nil {

View file

@ -96,3 +96,16 @@ func (db *DB) InvalidateToken(ctx context.Context, userID xid.ID, tokenID xid.ID
}
return t, nil
}
func (db *DB) InvalidateAllTokens(ctx context.Context, q querier, userID xid.ID) error {
sql, args, err := sq.Update("tokens").Where("user_id = ?", userID).Set("invalidated", true).ToSql()
if err != nil {
return errors.Wrap(err, "building sql")
}
_, err = q.Exec(ctx, sql, args...)
if err != nil {
return errors.Wrap(err, "executing query")
}
return nil
}

View file

@ -3,11 +3,13 @@ package db
import (
"context"
"regexp"
"time"
"codeberg.org/u1f320/pronouns.cc/backend/db/queries"
"emperror.dev/errors"
"github.com/bwmarrin/discordgo"
"github.com/jackc/pgconn"
"github.com/jackc/pgtype"
"github.com/jackc/pgx/v4"
"github.com/rs/xid"
)
@ -28,6 +30,10 @@ type User struct {
DiscordUsername *string
MaxInvites int
DeletedAt *time.Time
SelfDelete *bool
DeleteReason *string
}
// usernames must match this regex
@ -134,6 +140,11 @@ func (db *DB) getUser(ctx context.Context, q querier, id xid.ID) (u User, err er
return u, errors.Wrap(err, "getting user from database")
}
var deletedAt *time.Time
if qu.DeletedAt.Status == pgtype.Present {
deletedAt = &qu.DeletedAt.Time
}
u = User{
ID: id,
Username: qu.Username,
@ -146,6 +157,9 @@ func (db *DB) getUser(ctx context.Context, q querier, id xid.ID) (u User, err er
Discord: qu.Discord,
DiscordUsername: qu.DiscordUsername,
MaxInvites: int(qu.MaxInvites),
DeletedAt: deletedAt,
SelfDelete: qu.SelfDelete,
DeleteReason: qu.DeleteReason,
}
return u, nil
@ -283,3 +297,20 @@ func (db *DB) UpdateUser(
}
return u, nil
}
func (db *DB) DeleteUser(ctx context.Context, q querier, id xid.ID, selfDelete bool, reason string) error {
builder := sq.Update("users").Set("deleted_at", time.Now().UTC()).Set("self_delete", selfDelete).Where("id = ?", id)
if !selfDelete {
builder = builder.Set("delete_reason", reason)
}
sql, args, err := builder.ToSql()
if err != nil {
return errors.Wrap(err, "building sql")
}
_, err = q.Exec(ctx, sql, args...)
if err != nil {
return errors.Wrap(err, "executing query")
}
return nil
}