diff --git a/config/config.go b/config/config.go index 9b69de5..769d515 100644 --- a/config/config.go +++ b/config/config.go @@ -37,5 +37,5 @@ type Cache struct { type Captcha struct { Type string `yaml:"type"` SiteKey string `yaml:"siteKey"` - Secret string `yaml:"ecret"` + Secret string `yaml:"secret"` } diff --git a/db/ent/for_update.tmpl b/db/ent/for_update.tmpl new file mode 100644 index 0000000..1507d0c --- /dev/null +++ b/db/ent/for_update.tmpl @@ -0,0 +1,40 @@ +{{/* +Copyright 2019-present Facebook Inc. All rights reserved. +This source code is licensed under the Apache 2.0 license found +in the LICENSE file in the root directory of this source tree. +*/}} + +{{/* gotype: entgo.io/ent/entc/gen.Type */}} + +{{/* Templates used by the "sql/lock" feature-flag to add "SELECT ... FOR UPDATE/SHARE" capabilities. */}} + +{{ define "dialect/sql/query/additional/locking_sqlite" }} + {{ if $.FeatureEnabled "sql/lock" }} + {{ template "helper/sqlock_sqlite" $ }} + {{ end }} +{{ end }} + +{{ define "helper/sqlock_sqlite" }} +{{ $builder := pascal $.Scope.Builder }} +{{ $receiver := receiver $builder }} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func ({{ $receiver }} *{{ $builder }}) ForUpdateA(opts ...sql.LockOption) *{{ $builder }} { + if {{ $receiver }}.driver.Dialect() == dialect.SQLite { + return {{ $receiver }} + } + return {{ $receiver }}.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func ({{ $receiver }} *{{ $builder }}) ForShareA(opts ...sql.LockOption) *{{ $builder }} { + if {{ $receiver }}.driver.Dialect() == dialect.SQLite { + return {{ $receiver }} + } + return {{ $receiver }}.ForShare(opts...) +} +{{ end }} \ No newline at end of file diff --git a/db/ent/generate.go b/db/ent/generate.go index cea615c..6dc7a9e 100644 --- a/db/ent/generate.go +++ b/db/ent/generate.go @@ -1,3 +1,3 @@ package ent -//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/lock ./schema +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/lock --template for_update.tmpl ./schema diff --git a/db/ent/migrate/schema.go b/db/ent/migrate/schema.go index e28d71b..032788f 100644 --- a/db/ent/migrate/schema.go +++ b/db/ent/migrate/schema.go @@ -89,6 +89,16 @@ var ( Unique: false, Columns: []*schema.Column{UserProfilesColumns[3]}, }, + { + Name: "userprofile_name", + Unique: false, + Columns: []*schema.Column{UserProfilesColumns[1]}, + }, + { + Name: "userprofile_uuid", + Unique: false, + Columns: []*schema.Column{UserProfilesColumns[2]}, + }, }, } // UserTexturesColumns holds the columns for the "user_textures" table. diff --git a/db/ent/texture_query.go b/db/ent/texture_query.go index afd3fcd..cf8b3eb 100644 --- a/db/ent/texture_query.go +++ b/db/ent/texture_query.go @@ -739,6 +739,26 @@ func (tq *TextureQuery) ForShare(opts ...sql.LockOption) *TextureQuery { return tq } +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (tq *TextureQuery) ForUpdateA(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.SQLite { + return tq + } + return tq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (tq *TextureQuery) ForShareA(opts ...sql.LockOption) *TextureQuery { + if tq.driver.Dialect() == dialect.SQLite { + return tq + } + return tq.ForShare(opts...) +} + // TextureGroupBy is the group-by builder for Texture entities. type TextureGroupBy struct { selector diff --git a/db/ent/user_query.go b/db/ent/user_query.go index 6b2a663..fb2b507 100644 --- a/db/ent/user_query.go +++ b/db/ent/user_query.go @@ -694,6 +694,26 @@ func (uq *UserQuery) ForShare(opts ...sql.LockOption) *UserQuery { return uq } +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (uq *UserQuery) ForUpdateA(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.SQLite { + return uq + } + return uq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (uq *UserQuery) ForShareA(opts ...sql.LockOption) *UserQuery { + if uq.driver.Dialect() == dialect.SQLite { + return uq + } + return uq.ForShare(opts...) +} + // UserGroupBy is the group-by builder for User entities. type UserGroupBy struct { selector diff --git a/db/ent/userprofile_query.go b/db/ent/userprofile_query.go index 7ad3037..41ab268 100644 --- a/db/ent/userprofile_query.go +++ b/db/ent/userprofile_query.go @@ -739,6 +739,26 @@ func (upq *UserProfileQuery) ForShare(opts ...sql.LockOption) *UserProfileQuery return upq } +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (upq *UserProfileQuery) ForUpdateA(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.SQLite { + return upq + } + return upq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (upq *UserProfileQuery) ForShareA(opts ...sql.LockOption) *UserProfileQuery { + if upq.driver.Dialect() == dialect.SQLite { + return upq + } + return upq.ForShare(opts...) +} + // UserProfileGroupBy is the group-by builder for UserProfile entities. type UserProfileGroupBy struct { selector diff --git a/db/ent/usertexture_query.go b/db/ent/usertexture_query.go index 2ea3e9f..4ce98f9 100644 --- a/db/ent/usertexture_query.go +++ b/db/ent/usertexture_query.go @@ -626,6 +626,26 @@ func (utq *UserTextureQuery) ForShare(opts ...sql.LockOption) *UserTextureQuery return utq } +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTextureQuery) ForUpdateA(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTextureQuery) ForShareA(opts ...sql.LockOption) *UserTextureQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForShare(opts...) +} + // UserTextureGroupBy is the group-by builder for UserTexture entities. type UserTextureGroupBy struct { selector diff --git a/db/ent/usertoken_query.go b/db/ent/usertoken_query.go index 3421b65..6a6bc9d 100644 --- a/db/ent/usertoken_query.go +++ b/db/ent/usertoken_query.go @@ -559,6 +559,26 @@ func (utq *UserTokenQuery) ForShare(opts ...sql.LockOption) *UserTokenQuery { return utq } +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (utq *UserTokenQuery) ForUpdateA(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForUpdate(opts...) +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (utq *UserTokenQuery) ForShareA(opts ...sql.LockOption) *UserTokenQuery { + if utq.driver.Dialect() == dialect.SQLite { + return utq + } + return utq.ForShare(opts...) +} + // UserTokenGroupBy is the group-by builder for UserToken entities. type UserTokenGroupBy struct { selector diff --git a/frontend/src/components/CaptchaWidget.tsx b/frontend/src/components/CaptchaWidget.tsx index 5941fce..d670767 100644 --- a/frontend/src/components/CaptchaWidget.tsx +++ b/frontend/src/components/CaptchaWidget.tsx @@ -44,7 +44,7 @@ const CaptchaWidget = forwardRef(({ onSuccess }, ref) => { console.warn(error) return {String(error)} } - if (loading) { + if (!data && loading) { return } diff --git a/service/user.go b/service/user.go index f28b39f..171269a 100644 --- a/service/user.go +++ b/service/user.go @@ -51,14 +51,14 @@ func (w *WebService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip stri p, s := utils.Argon2ID(u.Password) err = utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { - count, err := tx.User.Query().Where(user.EmailEQ(u.Email)).ForUpdate().Count(ctx) + count, err := tx.User.Query().Where(user.EmailEQ(u.Email)).ForUpdateA().Count(ctx) if err != nil { return err } if count != 0 { return ErrExistUser } - nameCount, err := tx.UserProfile.Query().Where(userprofile.NameEQ(u.Name)).ForUpdate().Count(ctx) + nameCount, err := tx.UserProfile.Query().Where(userprofile.NameEQ(u.Name)).ForUpdateA().Count(ctx) if err != nil { return err } diff --git a/service/utils/auth.go b/service/utils/auth.go index ce61daa..92e3051 100644 --- a/service/utils/auth.go +++ b/service/utils/auth.go @@ -89,7 +89,7 @@ func CreateToken(ctx context.Context, u *ent.User, client *ent.Client, cache cac var utoken *ent.UserToken err := utils.WithTx(ctx, client, func(tx *ent.Tx) error { var err error - utoken, err = tx.User.QueryToken(u).ForUpdate().First(ctx) + utoken, err = tx.User.QueryToken(u).ForUpdateA().First(ctx) if err != nil { var nf *ent.NotFoundError if !errors.As(err, &nf) {