feat: start custom preferences on backend

This commit is contained in:
Sam 2023-04-19 11:05:01 +02:00 committed by Gitea
parent 86a1841f4f
commit 7ea5efae93
8 changed files with 2118 additions and 39 deletions

View file

@ -4,9 +4,12 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt"
"regexp" "regexp"
"time" "time"
"codeberg.org/u1f320/pronouns.cc/backend/common"
"codeberg.org/u1f320/pronouns.cc/backend/icons"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/georgysavva/scany/v2/pgxscan" "github.com/georgysavva/scany/v2/pgxscan"
@ -49,8 +52,47 @@ type User struct {
DeletedAt *time.Time DeletedAt *time.Time
SelfDelete *bool SelfDelete *bool
DeleteReason *string DeleteReason *string
CustomPreferences CustomPreferences
} }
type CustomPreferences = map[string]CustomPreference
type CustomPreference struct {
Icon string `json:"icon"`
Tooltip string `json:"tooltip"`
Size PreferenceSize `json:"size"`
Muted bool `json:"muted"`
Favourite bool `json:"favourite"`
}
func (c CustomPreference) Validate() string {
if !icons.IsValid(c.Icon) {
return fmt.Sprintf("custom preference icon %q is invalid", c.Icon)
}
if c.Tooltip == "" {
return "custom preference tooltip is empty"
}
if common.StringLength(&c.Tooltip) > FieldEntryMaxLength {
return fmt.Sprintf("custom preference tooltip is too long, max %d characters, is %d characters", FieldEntryMaxLength, common.StringLength(&c.Tooltip))
}
if c.Size != PreferenceSizeLarge && c.Size != PreferenceSizeNormal && c.Size != PreferenceSizeSmall {
return fmt.Sprintf("custom preference size %q is invalid", string(c.Size))
}
return ""
}
type PreferenceSize string
const (
PreferenceSizeLarge PreferenceSize = "large"
PreferenceSizeNormal PreferenceSize = "normal"
PreferenceSizeSmall PreferenceSize = "small"
)
func (u User) NumProviders() (numProviders int) { func (u User) NumProviders() (numProviders int) {
if u.Discord != nil { if u.Discord != nil {
numProviders++ numProviders++

1968
backend/icons/icons.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -42,10 +42,11 @@ func dbMemberToMember(u db.User, m db.Member, fields []db.Field, isOwnMember boo
Fields: db.NotNull(fields), Fields: db.NotNull(fields),
User: PartialUser{ User: PartialUser{
ID: u.ID, ID: u.ID,
Username: u.Username, Username: u.Username,
DisplayName: u.DisplayName, DisplayName: u.DisplayName,
Avatar: u.Avatar, Avatar: u.Avatar,
CustomPreferences: u.CustomPreferences,
}, },
} }
@ -57,10 +58,11 @@ func dbMemberToMember(u db.User, m db.Member, fields []db.Field, isOwnMember boo
} }
type PartialUser struct { type PartialUser struct {
ID xid.ID `json:"id"` ID xid.ID `json:"id"`
Username string `json:"name"` Username string `json:"name"`
DisplayName *string `json:"display_name"` DisplayName *string `json:"display_name"`
Avatar *string `json:"avatar"` Avatar *string `json:"avatar"`
CustomPreferences db.CustomPreferences `json:"custom_preferences"`
} }
func (s *Server) getMember(w http.ResponseWriter, r *http.Request) error { func (s *Server) getMember(w http.ResponseWriter, r *http.Request) error {

View file

@ -12,17 +12,18 @@ import (
) )
type GetUserResponse struct { type GetUserResponse struct {
ID xid.ID `json:"id"` ID xid.ID `json:"id"`
Username string `json:"name"` Username string `json:"name"`
DisplayName *string `json:"display_name"` DisplayName *string `json:"display_name"`
Bio *string `json:"bio"` Bio *string `json:"bio"`
MemberTitle *string `json:"member_title"` MemberTitle *string `json:"member_title"`
Avatar *string `json:"avatar"` Avatar *string `json:"avatar"`
Links []string `json:"links"` Links []string `json:"links"`
Names []db.FieldEntry `json:"names"` Names []db.FieldEntry `json:"names"`
Pronouns []db.PronounEntry `json:"pronouns"` Pronouns []db.PronounEntry `json:"pronouns"`
Members []PartialMember `json:"members"` Members []PartialMember `json:"members"`
Fields []db.Field `json:"fields"` Fields []db.Field `json:"fields"`
CustomPreferences db.CustomPreferences `json:"custom_preferences"`
} }
type GetMeResponse struct { type GetMeResponse struct {
@ -59,16 +60,17 @@ type PartialMember struct {
func dbUserToResponse(u db.User, fields []db.Field, members []db.Member) GetUserResponse { func dbUserToResponse(u db.User, fields []db.Field, members []db.Member) GetUserResponse {
resp := GetUserResponse{ resp := GetUserResponse{
ID: u.ID, ID: u.ID,
Username: u.Username, Username: u.Username,
DisplayName: u.DisplayName, DisplayName: u.DisplayName,
Bio: u.Bio, Bio: u.Bio,
MemberTitle: u.MemberTitle, MemberTitle: u.MemberTitle,
Avatar: u.Avatar, Avatar: u.Avatar,
Links: db.NotNull(u.Links), Links: db.NotNull(u.Links),
Names: db.NotNull(u.Names), Names: db.NotNull(u.Names),
Pronouns: db.NotNull(u.Pronouns), Pronouns: db.NotNull(u.Pronouns),
Fields: db.NotNull(fields), Fields: db.NotNull(fields),
CustomPreferences: u.CustomPreferences,
} }
resp.Members = make([]PartialMember, len(members)) resp.Members = make([]PartialMember, len(members))

View file

@ -10,19 +10,21 @@ import (
"codeberg.org/u1f320/pronouns.cc/backend/server" "codeberg.org/u1f320/pronouns.cc/backend/server"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/google/uuid"
) )
type PatchUserRequest struct { type PatchUserRequest struct {
Username *string `json:"username"` Username *string `json:"username"`
DisplayName *string `json:"display_name"` DisplayName *string `json:"display_name"`
Bio *string `json:"bio"` Bio *string `json:"bio"`
MemberTitle *string `json:"member_title"` MemberTitle *string `json:"member_title"`
Links *[]string `json:"links"` Links *[]string `json:"links"`
Names *[]db.FieldEntry `json:"names"` Names *[]db.FieldEntry `json:"names"`
Pronouns *[]db.PronounEntry `json:"pronouns"` Pronouns *[]db.PronounEntry `json:"pronouns"`
Fields *[]db.Field `json:"fields"` Fields *[]db.Field `json:"fields"`
Avatar *string `json:"avatar"` Avatar *string `json:"avatar"`
ListPrivate *bool `json:"list_private"` ListPrivate *bool `json:"list_private"`
CustomPreferences *db.CustomPreferences `json:"custom_preferences"`
} }
// patchUser parses a PatchUserRequest and updates the user with the given ID. // patchUser parses a PatchUserRequest and updates the user with the given ID.
@ -115,6 +117,19 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
return *err return *err
} }
// validate custom preferences
if req.CustomPreferences != nil {
for k, v := range *req.CustomPreferences {
_, err := uuid.Parse(k)
if err != nil {
return server.APIError{Code: server.ErrBadRequest, Details: "One or more custom preference IDs is not a UUID."}
}
if s := v.Validate(); s != "" {
return server.APIError{Code: server.ErrBadRequest, Details: s}
}
}
}
// update avatar // update avatar
var avatarHash *string = nil var avatarHash *string = nil
if req.Avatar != nil { if req.Avatar != nil {

44
frontend/icons.js Normal file
View file

@ -0,0 +1,44 @@
// This script regenerates the list of icons for the frontend (frontend/src/icons.json)
// and the backend (backend/icons/icons.go) from the currently installed version of Bootstrap Icons.
// Run with `pnpm node icons.js` in the frontend directory.
import { writeFileSync } from "fs";
import icons from "bootstrap-icons/font/bootstrap-icons.json" assert { type: "json" };
const keys = Object.keys(icons);
console.log(`Found ${keys.length} icons`);
const output = JSON.stringify(keys);
console.log(`Saving file as src/icons.json`);
writeFileSync("src/icons.json", output);
const goCode1 = `// Generated code. DO NOT EDIT
package icons
var icons = [...]string{
`;
const goCode2 = `}
// IsValid returns true if the input is the name of a Bootstrap icon.
func IsValid(name string) bool {
for i := range icons {
if icons[i] == name {
return true
}
}
return false
}
`;
let goOutput = goCode1;
keys.forEach((element) => {
goOutput += ` "${element}",\n`;
});
goOutput += goCode2;
console.log("Writing Go code");
writeFileSync("../backend/icons/icons.go", goOutput);

1
frontend/src/icons.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
-- +migrate Up
-- 2023-04-19: Add custom preferences
alter table users add column custom_preferences jsonb not null default '{}';