forked from mirrors/pronouns.cc
Compare commits
19 commits
feature/pr
...
main
Author | SHA1 | Date | |
---|---|---|---|
8f34367d1a | |||
|
5fcd87a94a | ||
|
0633a32f64 | ||
|
623cdb545e | ||
|
4745a1c04b | ||
|
4e78d36eff | ||
|
31e1862ca9 | ||
|
4308bd4d98 | ||
|
40672d6d41 | ||
|
cfed74d6bf | ||
|
b29a0c86db | ||
|
1339550c80 | ||
|
55479ae8da | ||
|
ebc10d9558 | ||
|
ac603ac18e | ||
|
00abe1cb32 | ||
|
c13c4e90b6 | ||
|
e37b5be376 | ||
|
44b667ff43 |
63 changed files with 325 additions and 101 deletions
13
.woodpecker/.backend.yml
Normal file
13
.woodpecker/.backend.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
exclude: stable
|
||||||
|
|
||||||
|
steps:
|
||||||
|
check:
|
||||||
|
image: golang:alpine
|
||||||
|
commands:
|
||||||
|
- apk update && apk add curl vips-dev build-base
|
||||||
|
- make backend
|
||||||
|
# Install golangci-lint
|
||||||
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
|
||||||
|
- golangci-lint run
|
20
.woodpecker/.frontend.yml
Normal file
20
.woodpecker/.frontend.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
exclude: stable
|
||||||
|
|
||||||
|
steps:
|
||||||
|
check:
|
||||||
|
image: node
|
||||||
|
directory: frontend
|
||||||
|
environment: # SvelteKit expects these in the environment during build time.
|
||||||
|
- PRIVATE_SENTRY_DSN=
|
||||||
|
- PUBLIC_BASE_URL=http://pronouns.localhost
|
||||||
|
- PUBLIC_MEDIA_URL=http://pronouns.localhost/media
|
||||||
|
- PUBLIC_SHORT_BASE=http://prns.localhost
|
||||||
|
- PUBLIC_HCAPTCHA_SITEKEY=non_existent_sitekey
|
||||||
|
commands:
|
||||||
|
- corepack enable
|
||||||
|
- pnpm install
|
||||||
|
- pnpm check
|
||||||
|
- pnpm lint
|
||||||
|
- pnpm build
|
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@ all: generate backend frontend
|
||||||
|
|
||||||
.PHONY: backend
|
.PHONY: backend
|
||||||
backend:
|
backend:
|
||||||
go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/pronounscc/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD` -X codeberg.org/pronounscc/pronouns.cc/backend/server.Tag=`git describe --tags --long`" .
|
go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/pronounscc/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD` -X codeberg.org/pronounscc/pronouns.cc/backend/server.Tag=`git describe --tags --long --always`" .
|
||||||
|
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate:
|
generate:
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (db *DB) CreateExport(ctx context.Context, userID xid.ID, filename string,
|
||||||
return de, errors.Wrap(err, "building query")
|
return de, errors.Wrap(err, "building query")
|
||||||
}
|
}
|
||||||
|
|
||||||
pgxscan.Get(ctx, db, &de, sql, args...)
|
err = pgxscan.Get(ctx, db, &de, sql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return de, errors.Wrap(err, "executing sql")
|
return de, errors.Wrap(err, "executing sql")
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,11 +48,11 @@ func (f FediverseApp) ClientConfig() *oauth2.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FediverseApp) MastodonCompatible() bool {
|
func (f FediverseApp) MastodonCompatible() bool {
|
||||||
return f.InstanceType == "mastodon" || f.InstanceType == "pleroma" || f.InstanceType == "akkoma" || f.InstanceType == "pixelfed" || f.InstanceType == "gotosocial"
|
return f.InstanceType == "mastodon" || f.InstanceType == "pleroma" || f.InstanceType == "akkoma" || f.InstanceType == "incestoma" || f.InstanceType == "pixelfed" || f.InstanceType == "gotosocial"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FediverseApp) Misskey() bool {
|
func (f FediverseApp) Misskey() bool {
|
||||||
return f.InstanceType == "misskey" || f.InstanceType == "foundkey" || f.InstanceType == "calckey" || f.InstanceType == "firefish"
|
return f.InstanceType == "misskey" || f.InstanceType == "foundkey" || f.InstanceType == "calckey" || f.InstanceType == "firefish" || f.InstanceType == "sharkey"
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrNoInstanceApp = errors.Sentinel("instance doesn't have an app")
|
const ErrNoInstanceApp = errors.Sentinel("instance doesn't have an app")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
"github.com/georgysavva/scany/v2/pgxscan"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
@ -43,7 +44,12 @@ func (db *DB) CreateInvite(ctx context.Context, userID xid.ID) (i Invite, err er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i, errors.Wrap(err, "beginning transaction")
|
return i, errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var maxInvites, inviteCount int
|
var maxInvites, inviteCount int
|
||||||
err = tx.QueryRow(ctx, "SELECT max_invites FROM users WHERE id = $1", userID).Scan(&maxInvites)
|
err = tx.QueryRow(ctx, "SELECT max_invites FROM users WHERE id = $1", userID).Scan(&maxInvites)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/common"
|
"codeberg.org/pronounscc/pronouns.cc/backend/common"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
"github.com/georgysavva/scany/v2/pgxscan"
|
||||||
|
@ -287,7 +288,12 @@ func (db *DB) RerollMemberSID(ctx context.Context, userID, memberID xid.ID) (new
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "beginning transaction")
|
return "", errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
sql, args, err := sq.Update("members").
|
sql, args, err := sq.Update("members").
|
||||||
Set("sid", squirrel.Expr("find_free_member_sid()")).
|
Set("sid", squirrel.Expr("find_free_member_sid()")).
|
||||||
|
|
|
@ -26,7 +26,8 @@ var Command = &cli.Command{
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
// initialize sentry
|
// initialize sentry
|
||||||
if dsn := os.Getenv("SENTRY_DSN"); dsn != "" {
|
if dsn := os.Getenv("SENTRY_DSN"); dsn != "" {
|
||||||
sentry.Init(sentry.ClientOptions{
|
// We don't need to check the error here--it's fine if no DSN is set.
|
||||||
|
_ = sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: dsn,
|
Dsn: dsn,
|
||||||
Debug: os.Getenv("DEBUG") == "true",
|
Debug: os.Getenv("DEBUG") == "true",
|
||||||
Release: server.Tag,
|
Release: server.Tag,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/mediocregopher/radix/v4"
|
"github.com/mediocregopher/radix/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -291,7 +292,12 @@ func (s *Server) discordSignup(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
du := new(discordgo.User)
|
du := new(discordgo.User)
|
||||||
err = s.DB.GetJSON(ctx, "discord:"+req.Ticket, &du)
|
err = s.DB.GetJSON(ctx, "discord:"+req.Ticket, &du)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/mediocregopher/radix/v4"
|
"github.com/mediocregopher/radix/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
@ -319,7 +320,12 @@ func (s *Server) mastodonSignup(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
mu := new(partialMastodonAccount)
|
mu := new(partialMastodonAccount)
|
||||||
err = s.DB.GetJSON(ctx, "mastodon:"+req.Ticket, &mu)
|
err = s.DB.GetJSON(ctx, "mastodon:"+req.Ticket, &mu)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/mediocregopher/radix/v4"
|
"github.com/mediocregopher/radix/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
@ -247,7 +248,12 @@ func (s *Server) misskeySignup(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
mu := new(partialMisskeyAccount)
|
mu := new(partialMisskeyAccount)
|
||||||
err = s.DB.GetJSON(ctx, "misskey:"+req.Ticket, &mu)
|
err = s.DB.GetJSON(ctx, "misskey:"+req.Ticket, &mu)
|
||||||
|
|
|
@ -68,13 +68,10 @@ func (s *Server) noAppFediverseURL(ctx context.Context, w http.ResponseWriter, r
|
||||||
case "iceshrimp":
|
case "iceshrimp":
|
||||||
softwareName = "firefish"
|
softwareName = "firefish"
|
||||||
fallthrough
|
fallthrough
|
||||||
case "misskey", "foundkey", "calckey", "firefish":
|
case "misskey", "foundkey", "calckey", "firefish", "sharkey":
|
||||||
return s.noAppMisskeyURL(ctx, w, r, softwareName, instance)
|
return s.noAppMisskeyURL(ctx, w, r, softwareName, instance)
|
||||||
case "mastodon", "pleroma", "akkoma", "pixelfed", "gotosocial":
|
case "mastodon", "pleroma", "akkoma", "incestoma", "pixelfed", "gotosocial":
|
||||||
case "glitchcafe", "hometown":
|
case "glitchcafe", "hometown":
|
||||||
// plural.cafe (potentially other instances too?) runs Mastodon but changes the software name
|
|
||||||
// Hometown is a lightweight fork of Mastodon so we can just treat it the same
|
|
||||||
// changing it back to mastodon here for consistency
|
|
||||||
softwareName = "mastodon"
|
softwareName = "mastodon"
|
||||||
default:
|
default:
|
||||||
return server.APIError{Code: server.ErrUnsupportedInstance}
|
return server.APIError{Code: server.ErrUnsupportedInstance}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/mediocregopher/radix/v4"
|
"github.com/mediocregopher/radix/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -294,7 +295,12 @@ func (s *Server) googleSignup(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
gu := new(partialGoogleUser)
|
gu := new(partialGoogleUser)
|
||||||
err = s.DB.GetJSON(ctx, "google:"+req.Ticket, &gu)
|
err = s.DB.GetJSON(ctx, "google:"+req.Ticket, &gu)
|
||||||
|
|
|
@ -185,7 +185,7 @@ func (s *Server) oauthURLs(w http.ResponseWriter, r *http.Request) error {
|
||||||
if googleOAuthConfig.ClientID != "" {
|
if googleOAuthConfig.ClientID != "" {
|
||||||
googleCfg := googleOAuthConfig
|
googleCfg := googleOAuthConfig
|
||||||
googleCfg.RedirectURL = req.CallbackDomain + "/auth/login/google"
|
googleCfg.RedirectURL = req.CallbackDomain + "/auth/login/google"
|
||||||
resp.Google = googleCfg.AuthCodeURL(state)
|
resp.Google = googleCfg.AuthCodeURL(state) + "&prompt=select_account"
|
||||||
}
|
}
|
||||||
|
|
||||||
render.JSON(w, r, resp)
|
render.JSON(w, r, resp)
|
||||||
|
|
|
@ -5,9 +5,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,7 +65,12 @@ func (s *Server) deleteToken(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = s.DB.InvalidateAllTokens(ctx, tx, claims.UserID)
|
err = s.DB.InvalidateAllTokens(ctx, tx, claims.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/mediocregopher/radix/v4"
|
"github.com/mediocregopher/radix/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -327,7 +328,12 @@ func (s *Server) tumblrSignup(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
tui := new(tumblrUserInfo)
|
tui := new(tumblrUserInfo)
|
||||||
err = s.DB.GetJSON(ctx, "tumblr:"+req.Ticket, &tui)
|
err = s.DB.GetJSON(ctx, "tumblr:"+req.Ticket, &tui)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateMemberRequest struct {
|
type CreateMemberRequest struct {
|
||||||
|
@ -119,7 +120,12 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "starting transaction")
|
return errors.Wrap(err, "starting transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
m, err := s.DB.CreateMember(ctx, tx, claims.UserID, cmr.Name, cmr.DisplayName, cmr.Bio, cmr.Links)
|
m, err := s.DB.CreateMember(ctx, tx, claims.UserID, cmr.Name, cmr.DisplayName, cmr.Bio, cmr.Links)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -246,7 +247,12 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
|
||||||
log.Errorf("creating transaction: %v", err)
|
log.Errorf("creating transaction: %v", err)
|
||||||
return errors.Wrap(err, "creating transaction")
|
return errors.Wrap(err, "creating transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
m, err = s.DB.UpdateMember(ctx, tx, m.ID, req.Name, req.DisplayName, req.Bio, req.Unlisted, req.Links, avatarHash)
|
m, err = s.DB.UpdateMember(ctx, tx, m.ID, req.Name, req.DisplayName, req.Bio, req.Unlisted, req.Links, avatarHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resolveReportRequest struct {
|
type resolveReportRequest struct {
|
||||||
|
@ -43,7 +44,12 @@ func (s *Server) resolveReport(w http.ResponseWriter, r *http.Request) error {
|
||||||
log.Errorf("creating transaction: %v", err)
|
log.Errorf("creating transaction: %v", err)
|
||||||
return errors.Wrap(err, "creating transaction")
|
return errors.Wrap(err, "creating transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
report, err := s.DB.Report(ctx, tx, id)
|
report, err := s.DB.Report(ctx, tx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,9 +3,11 @@ package user
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) deleteUser(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) deleteUser(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
@ -20,7 +22,12 @@ func (s *Server) deleteUser(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "creating transaction")
|
return errors.Wrap(err, "creating transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = s.DB.DeleteUser(ctx, tx, claims.UserID, true, "")
|
err = s.DB.DeleteUser(ctx, tx, claims.UserID, true, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,7 +81,12 @@ func (s *Server) postUserFlag(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "starting transaction")
|
return errors.Wrap(err, "starting transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
flag, err := s.DB.CreateFlag(ctx, tx, claims.UserID, req.Name, req.Description)
|
flag, err := s.DB.CreateFlag(ctx, tx, claims.UserID, req.Name, req.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -192,7 +198,12 @@ func (s *Server) patchUserFlag(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "beginning transaction")
|
return errors.Wrap(err, "beginning transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
flag, err = s.DB.EditFlag(ctx, tx, flag.ID, req.Name, req.Description, nil)
|
flag, err = s.DB.EditFlag(ctx, tx, flag.ID, req.Name, req.Description, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -221,7 +222,12 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
|
||||||
log.Errorf("creating transaction: %v", err)
|
log.Errorf("creating transaction: %v", err)
|
||||||
return errors.Wrap(err, "creating transaction")
|
return errors.Wrap(err, "creating transaction")
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// update username
|
// update username
|
||||||
if req.Username != nil && *req.Username != u.Username {
|
if req.Username != nil && *req.Username != u.Username {
|
||||||
|
|
|
@ -100,23 +100,23 @@ func New() (*Server, error) {
|
||||||
|
|
||||||
// set scopes
|
// set scopes
|
||||||
// users
|
// users
|
||||||
rateLimiter.Scope("GET", "/users/*", 60)
|
_ = rateLimiter.Scope("GET", "/users/*", 60)
|
||||||
rateLimiter.Scope("PATCH", "/users/@me", 10)
|
_ = rateLimiter.Scope("PATCH", "/users/@me", 10)
|
||||||
|
|
||||||
// members
|
// members
|
||||||
rateLimiter.Scope("GET", "/users/*/members", 60)
|
_ = rateLimiter.Scope("GET", "/users/*/members", 60)
|
||||||
rateLimiter.Scope("GET", "/users/*/members/*", 60)
|
_ = rateLimiter.Scope("GET", "/users/*/members/*", 60)
|
||||||
|
|
||||||
rateLimiter.Scope("POST", "/members", 10)
|
_ = rateLimiter.Scope("POST", "/members", 10)
|
||||||
rateLimiter.Scope("GET", "/members/*", 60)
|
_ = rateLimiter.Scope("GET", "/members/*", 60)
|
||||||
rateLimiter.Scope("PATCH", "/members/*", 20)
|
_ = rateLimiter.Scope("PATCH", "/members/*", 20)
|
||||||
rateLimiter.Scope("DELETE", "/members/*", 5)
|
_ = rateLimiter.Scope("DELETE", "/members/*", 5)
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
rateLimiter.Scope("*", "/auth/*", 20)
|
_ = rateLimiter.Scope("*", "/auth/*", 20)
|
||||||
rateLimiter.Scope("*", "/auth/tokens", 10)
|
_ = rateLimiter.Scope("*", "/auth/tokens", 10)
|
||||||
rateLimiter.Scope("*", "/auth/invites", 10)
|
_ = rateLimiter.Scope("*", "/auth/invites", 10)
|
||||||
rateLimiter.Scope("POST", "/auth/discord/*", 10)
|
_ = rateLimiter.Scope("POST", "/auth/discord/*", 10)
|
||||||
|
|
||||||
s.Router.Use(rateLimiter.Handler())
|
s.Router.Use(rateLimiter.Handler())
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,15 @@ module.exports = {
|
||||||
es2017: true,
|
es2017: true,
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
25
frontend/src/app.d.ts
vendored
25
frontend/src/app.d.ts
vendored
|
@ -1,38 +1,19 @@
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
|
||||||
import type { APIError, ErrorCode } from "$lib/api/entities";
|
import type { ErrorCode } from "$lib/api/entities";
|
||||||
|
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
type Error = {
|
interface Error {
|
||||||
code: ErrorCode;
|
code: ErrorCode;
|
||||||
message?: string | undefined;
|
message?: string | undefined;
|
||||||
details?: string | undefined;
|
details?: string | undefined;
|
||||||
} | APIError
|
}
|
||||||
// interface Locals {}
|
// interface Locals {}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "svelte-hcaptcha" {
|
|
||||||
import type { SvelteComponent } from "svelte";
|
|
||||||
|
|
||||||
export interface HCaptchaProps {
|
|
||||||
sitekey?: string;
|
|
||||||
apihost?: string;
|
|
||||||
hl?: string;
|
|
||||||
reCaptchaCompat?: boolean;
|
|
||||||
theme?: CaptchaTheme;
|
|
||||||
size?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare class HCaptcha extends SvelteComponent {
|
|
||||||
$$prop_def: HCaptchaProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HCaptcha;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { PRIVATE_SENTRY_DSN } from "$env/static/private";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
import type { HandleServerError } from "@sveltejs/kit";
|
import type { HandleServerError } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
if (PRIVATE_SENTRY_DSN) {
|
||||||
Sentry.init({ dsn: PRIVATE_SENTRY_DSN });
|
Sentry.init({ dsn: PRIVATE_SENTRY_DSN });
|
||||||
|
}
|
||||||
|
|
||||||
export const handleError = (({ error, event }) => {
|
export const handleError = (({ error, event }) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
import { PUBLIC_BASE_URL, PUBLIC_MEDIA_URL } from "$env/static/public";
|
import { PUBLIC_BASE_URL, PUBLIC_MEDIA_URL } from "$env/static/public";
|
||||||
|
|
||||||
export const MAX_MEMBERS = 500;
|
export const MAX_MEMBERS = 500;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
export let urls: string[];
|
export let urls: string[];
|
||||||
export let alt: string;
|
export let alt: string;
|
||||||
export let width = 300;
|
export let width = 300;
|
||||||
|
export let lazyLoad = false;
|
||||||
|
|
||||||
const contentTypeFor = (url: string) => {
|
const contentTypeFor = (url: string) => {
|
||||||
if (url.endsWith(".webp")) {
|
if (url.endsWith(".webp")) {
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
src={urls[0] || defaultAvatars[0]}
|
src={urls[0] || defaultAvatars[0]}
|
||||||
{alt}
|
{alt}
|
||||||
class="rounded-circle img-fluid"
|
class="rounded-circle img-fluid"
|
||||||
|
loading={lazyLoad ? "lazy" : "eager"}
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
export let user: User;
|
export let user: User;
|
||||||
export let member: PartialMember & {
|
export let member: PartialMember & {
|
||||||
unlisted?: boolean
|
unlisted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
let pronouns: string | undefined;
|
let pronouns: string | undefined;
|
||||||
|
@ -46,7 +46,12 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href="/@{user.name}/{member.name}">
|
<a href="/@{user.name}/{member.name}">
|
||||||
<FallbackImage urls={memberAvatars(member)} width={200} alt="Avatar for {member.name}" />
|
<FallbackImage
|
||||||
|
urls={memberAvatars(member)}
|
||||||
|
width={200}
|
||||||
|
lazyLoad
|
||||||
|
alt="Avatar for {member.name}"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<p class="m-2">
|
<p class="m-2">
|
||||||
<a class="text-reset fs-5 text-break" href="/@{user.name}/{member.name}">
|
<a class="text-reset fs-5 text-break" href="/@{user.name}/{member.name}">
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
const resp = await apiFetchClient<Settings>(
|
const resp = await apiFetchClient<Settings>(
|
||||||
"/users/@me/settings",
|
"/users/@me/settings",
|
||||||
"PATCH",
|
"PATCH",
|
||||||
|
// If this function is run, notice will always be non-null
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
{ read_global_notice: data.notice!.id },
|
{ read_global_notice: data.notice!.id },
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const load = async ({ params }) => {
|
||||||
return resp;
|
return resp;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if ((e as APIError).code === ErrorCode.UserNotFound) {
|
if ((e as APIError).code === ErrorCode.UserNotFound) {
|
||||||
error(404, e as APIError);
|
error(404, e as App.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Badge,
|
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -15,7 +14,7 @@
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { DateTime, Duration, FixedOffsetZone, Zone } from "luxon";
|
import { DateTime, FixedOffsetZone } from "luxon";
|
||||||
import FieldCard from "$lib/components/FieldCard.svelte";
|
import FieldCard from "$lib/components/FieldCard.svelte";
|
||||||
import PronounLink from "$lib/components/PronounLink.svelte";
|
import PronounLink from "$lib/components/PronounLink.svelte";
|
||||||
import PartialMemberCard from "$lib/components/PartialMemberCard.svelte";
|
import PartialMemberCard from "$lib/components/PartialMemberCard.svelte";
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { CustomPreferences, Field, FieldEntry, Pronoun } from "$lib/api/entities";
|
import type {
|
||||||
|
CustomPreferences,
|
||||||
|
CustomPreference,
|
||||||
|
Field,
|
||||||
|
FieldEntry,
|
||||||
|
Pronoun,
|
||||||
|
} from "$lib/api/entities";
|
||||||
import defaultPreferences from "$lib/api/default_preferences";
|
import defaultPreferences from "$lib/api/default_preferences";
|
||||||
import StatusIcon from "$lib/components/StatusIcon.svelte";
|
import StatusIcon from "$lib/components/StatusIcon.svelte";
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ export const load = async ({ params }) => {
|
||||||
(e as APIError).code === ErrorCode.UserNotFound ||
|
(e as APIError).code === ErrorCode.UserNotFound ||
|
||||||
(e as APIError).code === ErrorCode.MemberNotFound
|
(e as APIError).code === ErrorCode.MemberNotFound
|
||||||
) {
|
) {
|
||||||
error(404, e as APIError);
|
error(404, e as App.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(500, e as APIError);
|
error(500, e as App.Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,15 @@
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from "./$types";
|
||||||
import { addToast, delToast } from "$lib/toast";
|
import { addToast, delToast } from "$lib/toast";
|
||||||
import { apiFetchClient, fastFetchClient } from "$lib/api/fetch";
|
import { apiFetchClient, fastFetchClient } from "$lib/api/fetch";
|
||||||
import { Button, ButtonGroup, Modal, ModalBody, ModalFooter, Nav, NavItem } from "@sveltestrap/sveltestrap";
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Nav,
|
||||||
|
NavItem,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||||
import IconButton from "$lib/components/IconButton.svelte";
|
import IconButton from "$lib/components/IconButton.svelte";
|
||||||
|
|
|
@ -41,8 +41,9 @@ export const load = (async ({ params }) => {
|
||||||
pronouns: pronouns.autocomplete,
|
pronouns: pronouns.autocomplete,
|
||||||
flags,
|
flags,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ("code" in e) error(500, e as APIError);
|
} catch (e: any) {
|
||||||
|
if ("code" in e) error(500, e as App.Error);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}) satisfies LayoutLoad;
|
}) satisfies LayoutLoad;
|
||||||
|
|
|
@ -45,9 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button on:click={() => ($member.fields = [...$member.fields, { name: null, entries: [] }])}>
|
||||||
on:click={() => ($member.fields = [...$member.fields, { name: null, entries: [] }])}
|
|
||||||
>
|
|
||||||
<Icon name="plus" aria-hidden /> Add new field
|
<Icon name="plus" aria-hidden /> Add new field
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { PrideFlag, APIError, MeUser, PronounsJson } from "$lib/api/entities";
|
import type { PrideFlag, MeUser, PronounsJson } from "$lib/api/entities";
|
||||||
import { apiFetchClient } from "$lib/api/fetch";
|
import { apiFetchClient } from "$lib/api/fetch";
|
||||||
import { error, redirect, type Redirect } from "@sveltejs/kit";
|
import { error, redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
import pronounsRaw from "$lib/pronouns.json";
|
import pronounsRaw from "$lib/pronouns.json";
|
||||||
const pronouns = pronounsRaw as PronounsJson;
|
const pronouns = pronounsRaw as PronounsJson;
|
||||||
|
@ -21,8 +21,9 @@ export const load = async ({ params }) => {
|
||||||
pronouns: pronouns.autocomplete,
|
pronouns: pronouns.autocomplete,
|
||||||
flags,
|
flags,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ("code" in e) error(500, e as APIError);
|
} catch (e: any) {
|
||||||
|
if ("code" in e) error(500, e as App.Error);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,14 @@
|
||||||
|
|
||||||
import { PreferenceSize, type APIError, type MeUser } from "$lib/api/entities";
|
import { PreferenceSize, type APIError, type MeUser } from "$lib/api/entities";
|
||||||
import IconButton from "$lib/components/IconButton.svelte";
|
import IconButton from "$lib/components/IconButton.svelte";
|
||||||
import { Button, ButtonGroup, FormGroup, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
FormGroup,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { PUBLIC_SHORT_BASE } from "$env/static/public";
|
import { PUBLIC_SHORT_BASE } from "$env/static/public";
|
||||||
import CustomPreference from "./CustomPreference.svelte";
|
import CustomPreference from "./CustomPreference.svelte";
|
||||||
import { DateTime, FixedOffsetZone } from "luxon";
|
import { DateTime, FixedOffsetZone } from "luxon";
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const load = async ({ params }) => {
|
||||||
redirect(303, `/@${resp.name}`);
|
redirect(303, `/@${resp.name}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if ((e as APIError).code === ErrorCode.UserNotFound) {
|
if ((e as APIError).code === ErrorCode.UserNotFound) {
|
||||||
error(404, e as APIError);
|
error(404, e as App.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -64,8 +64,10 @@
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
let captchaToken = "";
|
let captchaToken = "";
|
||||||
|
// svelte-hcaptcha doesn't have types, so we can't use anything except `any` here.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let captcha: any;
|
let captcha: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const captchaSuccess = (token: any) => {
|
const captchaSuccess = (token: any) => {
|
||||||
captchaToken = token.detail.token;
|
captchaToken = token.detail.token;
|
||||||
};
|
};
|
||||||
|
@ -88,6 +90,8 @@
|
||||||
await fastFetch("/auth/force-delete", {
|
await fastFetch("/auth/force-delete", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
// We know for sure this value is non-null if this function is run at all
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
"X-Delete-Token": token!,
|
"X-Delete-Token": token!,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -105,6 +109,8 @@
|
||||||
await fastFetch("/auth/cancel-delete", {
|
await fastFetch("/auth/cancel-delete", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
// We know for sure this value is non-null if this function is run at all
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
"X-Delete-Token": token!,
|
"X-Delete-Token": token!,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let callbackPage: any;
|
let callbackPage: CallbackPage;
|
||||||
|
|
||||||
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let callbackPage: any;
|
let callbackPage: CallbackPage;
|
||||||
|
|
||||||
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let callbackPage: any;
|
let callbackPage: CallbackPage;
|
||||||
|
|
||||||
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let callbackPage: any;
|
let callbackPage: CallbackPage;
|
||||||
|
|
||||||
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let callbackPage: any;
|
let callbackPage: CallbackPage;
|
||||||
|
|
||||||
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
const signupForm = async (username: string, invite: string, captchaToken: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const load = async ({ params }) => {
|
||||||
(e as APIError).code === ErrorCode.InvalidToken ||
|
(e as APIError).code === ErrorCode.InvalidToken ||
|
||||||
(e as APIError).code === ErrorCode.NotOwnMember
|
(e as APIError).code === ErrorCode.NotOwnMember
|
||||||
) {
|
) {
|
||||||
error(403, e as APIError);
|
error(403, e as App.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const load = async () => {
|
||||||
(e as APIError).code === ErrorCode.Forbidden ||
|
(e as APIError).code === ErrorCode.Forbidden ||
|
||||||
(e as APIError).code === ErrorCode.InvalidToken
|
(e as APIError).code === ErrorCode.InvalidToken
|
||||||
) {
|
) {
|
||||||
error(403, e as APIError);
|
error(403, e as App.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Ignoring the TS error here, because this file imports fine, typescript just chokes on markdown files
|
||||||
|
// eslint-disable-next-line
|
||||||
|
//@ts-ignore
|
||||||
import { html } from "./about.md";
|
import { html } from "./about.md";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
// Ignoring the TS error here, because this file imports fine, typescript just chokes on markdown files
|
||||||
|
// eslint-disable-next-line
|
||||||
|
//@ts-ignore
|
||||||
import { html } from "./changelog.md";
|
import { html } from "./changelog.md";
|
||||||
import { CURRENT_CHANGELOG } from "$lib/store";
|
import { CURRENT_CHANGELOG } from "$lib/store";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Ignoring the TS error here, because this file imports fine, typescript just chokes on markdown files
|
||||||
|
// eslint-disable-next-line
|
||||||
|
//@ts-ignore
|
||||||
import { html } from "./privacy.md";
|
import { html } from "./privacy.md";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Ignoring the TS error here, because this file imports fine, typescript just chokes on markdown files
|
||||||
|
// eslint-disable-next-line
|
||||||
|
//@ts-ignore
|
||||||
import { html } from "./terms.md";
|
import { html } from "./terms.md";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PUBLIC_BASE_URL } from "$env/static/public";
|
import { PUBLIC_BASE_URL } from "$env/static/public";
|
||||||
import type { PageData } from "../../$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,14 @@
|
||||||
import { fastFetchClient } from "$lib/api/fetch";
|
import { fastFetchClient } from "$lib/api/fetch";
|
||||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||||
import { addToast } from "$lib/toast";
|
import { addToast } from "$lib/toast";
|
||||||
import { Button, ButtonGroup, FormGroup, Modal, ModalBody, ModalFooter } from "@sveltestrap/sveltestrap";
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
FormGroup,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
import ReportCard from "./ReportCard.svelte";
|
import ReportCard from "./ReportCard.svelte";
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
Table,
|
Table,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
|
@ -10,6 +10,6 @@ export const load = async () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if ((e as APIError).code === ErrorCode.NotFound) return { exportData: null };
|
if ((e as APIError).code === ErrorCode.NotFound) return { exportData: null };
|
||||||
|
|
||||||
error(500, e as APIError);
|
error(500, e as App.Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { MAX_FLAGS, type APIError, type PrideFlag } from "$lib/api/entities";
|
import { MAX_FLAGS, type APIError, type PrideFlag } from "$lib/api/entities";
|
||||||
import { Button, Icon, Input, Modal, ModalBody, ModalFooter, ModalHeader } from "@sveltestrap/sveltestrap";
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
import Flag from "./Flag.svelte";
|
import Flag from "./Flag.svelte";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
|
@ -2,7 +2,14 @@
|
||||||
import { flagURL, type APIError, type PrideFlag } from "$lib/api/entities";
|
import { flagURL, type APIError, type PrideFlag } from "$lib/api/entities";
|
||||||
import { apiFetchClient } from "$lib/api/fetch";
|
import { apiFetchClient } from "$lib/api/fetch";
|
||||||
import { addToast } from "$lib/toast";
|
import { addToast } from "$lib/toast";
|
||||||
import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from "@sveltestrap/sveltestrap";
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
export let flag: PrideFlag;
|
export let flag: PrideFlag;
|
||||||
export let deleteFlag: (id: string) => Promise<void>;
|
export let deleteFlag: (id: string) => Promise<void>;
|
||||||
|
|
18
frontend/src/svelte-hcaptcha.d.ts
vendored
Normal file
18
frontend/src/svelte-hcaptcha.d.ts
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
declare module "svelte-hcaptcha" {
|
||||||
|
import type { SvelteComponent } from "svelte";
|
||||||
|
|
||||||
|
export interface HCaptchaProps {
|
||||||
|
sitekey?: string;
|
||||||
|
apihost?: string;
|
||||||
|
hl?: string;
|
||||||
|
reCaptchaCompat?: boolean;
|
||||||
|
theme?: CaptchaTheme;
|
||||||
|
size?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class HCaptcha extends SvelteComponent {
|
||||||
|
$$prop_def: HCaptchaProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HCaptcha;
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ const config = {
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
version: {
|
version: {
|
||||||
name: child_process.execSync("git describe --tags --long").toString().trim(),
|
name: child_process.execSync("git describe --tags --long --always").toString().trim(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,12 +2,15 @@ package cleandb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
dbpkg "codeberg.org/pronounscc/pronouns.cc/backend/db"
|
dbpkg "codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
"github.com/georgysavva/scany/v2/pgxscan"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
@ -77,7 +80,12 @@ func run(c *cli.Context) error {
|
||||||
fmt.Printf("error starting transaction: %v\n", err)
|
fmt.Printf("error starting transaction: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
inactiveUsers, err := db.InactiveUsers(ctx, tx)
|
inactiveUsers, err := db.InactiveUsers(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package seeddb
|
package seeddb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
@ -82,7 +84,12 @@ func run(c *cli.Context) error {
|
||||||
log.Println("error beginning transaction:", err)
|
log.Println("error beginning transaction:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() {
|
||||||
|
err := tx.Rollback(ctx)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Println("error rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for i, su := range seed.Users {
|
for i, su := range seed.Users {
|
||||||
u, err := pg.CreateUser(ctx, tx, su.Username)
|
u, err := pg.CreateUser(ctx, tx, su.Username)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package snowflakes
|
package snowflakes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,7 +40,12 @@ func run(c *cli.Context) error {
|
||||||
log.Error("creating transaction:", err)
|
log.Error("creating transaction:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer tx.Rollback(c.Context)
|
defer func() {
|
||||||
|
err := tx.Rollback(c.Context)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
|
||||||
|
log.Error("rolling back transaction:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var userIDs []xid.ID
|
var userIDs []xid.ID
|
||||||
err = pgxscan.Select(c.Context, conn, &userIDs, "SELECT id FROM users WHERE snowflake_id IS NULL")
|
err = pgxscan.Select(c.Context, conn, &userIDs, "SELECT id FROM users WHERE snowflake_id IS NULL")
|
||||||
|
|
Loading…
Reference in a new issue