feat: store whether registration is open
This commit is contained in:
1 parent
0bc5568335
commit
4f056e91d4
10 files changed
+127
-55
No files matched your search
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
-- site 表没有行或者 brand_name 为 NULL 时,品牌名是默认的 Alisten;没有行时开放注册。
|
||||
ALTER TABLE site
|
||||
ALTER COLUMN brand_name DROP NOT NULL,
|
||||
ADD COLUMN registration_open boolean NOT NULL DEFAULT true;
|
||||
+6
-2
@@ -1,6 +1,10 @@
|
||||
-- name: BrandName :one
|
||||
SELECT brand_name FROM site;
|
||||
-- name: SiteSettings :one
|
||||
SELECT brand_name, registration_open FROM site;
|
||||
|
||||
-- name: SetBrandName :exec
|
||||
INSERT INTO site (brand_name) VALUES (@brand_name)
|
||||
ON CONFLICT (id) DO UPDATE SET brand_name = excluded.brand_name;
|
||||
|
||||
-- name: SetRegistrationOpen :exec
|
||||
INSERT INTO site (registration_open) VALUES (@registration_open)
|
||||
ON CONFLICT (id) DO UPDATE SET registration_open = excluded.registration_open;
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
)
|
||||
|
||||
func (s *server) getSite(ctx context.Context, _ call) (any, error) {
|
||||
name, err := s.Site.BrandName(ctx)
|
||||
settings, err := s.Site.Settings(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return protocol.GetSiteResponse{BrandName: name}, nil
|
||||
return protocol.GetSiteResponse{BrandName: settings.BrandName}, nil
|
||||
}
|
||||
|
||||
func (s *server) adminUpdateSite(ctx context.Context, c call) (any, error) {
|
||||
|
||||
@@ -9,23 +9,38 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const brandName = `-- name: BrandName :one
|
||||
SELECT brand_name FROM site
|
||||
`
|
||||
|
||||
func (q *Queries) BrandName(ctx context.Context) (string, error) {
|
||||
row := q.db.QueryRow(ctx, brandName)
|
||||
var brand_name string
|
||||
err := row.Scan(&brand_name)
|
||||
return brand_name, err
|
||||
}
|
||||
|
||||
const setBrandName = `-- name: SetBrandName :exec
|
||||
INSERT INTO site (brand_name) VALUES ($1)
|
||||
ON CONFLICT (id) DO UPDATE SET brand_name = excluded.brand_name
|
||||
`
|
||||
|
||||
func (q *Queries) SetBrandName(ctx context.Context, brandName string) error {
|
||||
func (q *Queries) SetBrandName(ctx context.Context, brandName *string) error {
|
||||
_, err := q.db.Exec(ctx, setBrandName, brandName)
|
||||
return err
|
||||
}
|
||||
|
||||
const setRegistrationOpen = `-- name: SetRegistrationOpen :exec
|
||||
INSERT INTO site (registration_open) VALUES ($1)
|
||||
ON CONFLICT (id) DO UPDATE SET registration_open = excluded.registration_open
|
||||
`
|
||||
|
||||
func (q *Queries) SetRegistrationOpen(ctx context.Context, registrationOpen bool) error {
|
||||
_, err := q.db.Exec(ctx, setRegistrationOpen, registrationOpen)
|
||||
return err
|
||||
}
|
||||
|
||||
const siteSettings = `-- name: SiteSettings :one
|
||||
SELECT brand_name, registration_open FROM site
|
||||
`
|
||||
|
||||
type SiteSettingsRow struct {
|
||||
BrandName *string
|
||||
RegistrationOpen bool
|
||||
}
|
||||
|
||||
func (q *Queries) SiteSettings(ctx context.Context) (SiteSettingsRow, error) {
|
||||
row := q.db.QueryRow(ctx, siteSettings)
|
||||
var i SiteSettingsRow
|
||||
err := row.Scan(&i.BrandName, &i.RegistrationOpen)
|
||||
return i, err
|
||||
}
|
||||
@@ -12,20 +12,27 @@ import (
|
||||
|
||||
var _ port.Site = (*Store)(nil)
|
||||
|
||||
func (s *Store) BrandName(ctx context.Context) (string, error) {
|
||||
name, err := s.q.BrandName(ctx)
|
||||
func (s *Store) Settings(ctx context.Context) (port.SiteSettings, error) {
|
||||
row, err := s.q.SiteSettings(ctx)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return "", nil
|
||||
return port.SiteSettings{RegistrationOpen: true}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取品牌名:%w", err)
|
||||
return port.SiteSettings{}, fmt.Errorf("读取站点设置:%w", err)
|
||||
}
|
||||
return name, nil
|
||||
return port.SiteSettings{BrandName: orEmpty(row.BrandName), RegistrationOpen: row.RegistrationOpen}, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetBrandName(ctx context.Context, name string) error {
|
||||
if err := s.q.SetBrandName(ctx, name); err != nil {
|
||||
if err := s.q.SetBrandName(ctx, &name); err != nil {
|
||||
return fmt.Errorf("保存品牌名:%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) SetRegistrationOpen(ctx context.Context, open bool) error {
|
||||
if err := s.q.SetRegistrationOpen(ctx, open); err != nil {
|
||||
return fmt.Errorf("保存注册开关:%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,20 +1,34 @@
|
||||
package postgres
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
func TestBrandName(t *testing.T) {
|
||||
"github.com/dihemoe/alisten/internal/app/port"
|
||||
)
|
||||
|
||||
func TestSiteSettings(t *testing.T) {
|
||||
store, pool := newStore(t)
|
||||
if name, err := store.BrandName(t.Context()); err != nil || name != "" {
|
||||
t.Fatalf("没有设置过时 = %q, %v,期望空字符串", name, err)
|
||||
check := func(want port.SiteSettings, why string) {
|
||||
t.Helper()
|
||||
if got, err := store.Settings(t.Context()); err != nil || got != want {
|
||||
t.Errorf("%s:Settings = %#v, %v,期望 %#v", why, got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{"周末听歌会", "Alisten"} {
|
||||
if err := store.SetBrandName(t.Context(), want); err != nil {
|
||||
check(port.SiteSettings{RegistrationOpen: true}, "没有设置过时没有品牌名、开放注册")
|
||||
if err := store.SetRegistrationOpen(t.Context(), false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(port.SiteSettings{RegistrationOpen: false}, "只关闭注册时品牌名仍未设置")
|
||||
for _, name := range []string{"周末听歌会", "Alisten"} {
|
||||
if err := store.SetBrandName(t.Context(), name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := store.BrandName(t.Context()); err != nil || got != want {
|
||||
t.Errorf("BrandName = %q, %v,期望 %q", got, err, want)
|
||||
}
|
||||
check(port.SiteSettings{BrandName: name, RegistrationOpen: false}, "修改品牌名不影响注册开关")
|
||||
}
|
||||
if err := store.SetRegistrationOpen(t.Context(), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(port.SiteSettings{BrandName: "Alisten", RegistrationOpen: true}, "重新开放注册不影响品牌名")
|
||||
var rows int
|
||||
if err := pool.QueryRow(t.Context(), "SELECT count(*) FROM site").Scan(&rows); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -29,9 +29,10 @@ type Store struct {
|
||||
states map[string]*roomState
|
||||
tickets map[string]storedTicket
|
||||
// 房间 ID 到 bot 成员 ID。
|
||||
bots map[string]string
|
||||
cookies map[string]string
|
||||
brandName string
|
||||
bots map[string]string
|
||||
cookies map[string]string
|
||||
brandName string
|
||||
registrationClosed bool
|
||||
}
|
||||
|
||||
type storedMember struct {
|
||||
@@ -425,10 +426,17 @@ func (s *Store) SetBilibiliCredential(_ context.Context, credential string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) BrandName(context.Context) (string, error) {
|
||||
func (s *Store) Settings(context.Context) (port.SiteSettings, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.brandName, nil
|
||||
return port.SiteSettings{BrandName: s.brandName, RegistrationOpen: !s.registrationClosed}, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetRegistrationOpen(_ context.Context, open bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.registrationClosed = !open
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) SetBrandName(_ context.Context, name string) error {
|
||||
|
||||
@@ -2,8 +2,14 @@ package port
|
||||
|
||||
import "context"
|
||||
|
||||
// 没有设置过品牌名时,BrandName 返回空字符串。
|
||||
type SiteSettings struct {
|
||||
// 没有设置过品牌名时为空字符串。
|
||||
BrandName string
|
||||
RegistrationOpen bool
|
||||
}
|
||||
|
||||
type Site interface {
|
||||
BrandName(ctx context.Context) (string, error)
|
||||
Settings(ctx context.Context) (SiteSettings, error)
|
||||
SetBrandName(ctx context.Context, name string) error
|
||||
SetRegistrationOpen(ctx context.Context, open bool) error
|
||||
}
|
||||
@@ -25,15 +25,25 @@ func New(site port.Site) *Service {
|
||||
return &Service{site: site}
|
||||
}
|
||||
|
||||
func (s *Service) BrandName(ctx context.Context) (string, error) {
|
||||
name, err := s.site.BrandName(ctx)
|
||||
type Settings struct {
|
||||
BrandName string
|
||||
RegistrationOpen bool
|
||||
}
|
||||
|
||||
func (s *Service) Settings(ctx context.Context) (Settings, error) {
|
||||
settings, err := s.site.Settings(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return Settings{}, err
|
||||
}
|
||||
name := settings.BrandName
|
||||
if name == "" {
|
||||
return defaultBrandName, nil
|
||||
name = defaultBrandName
|
||||
}
|
||||
return name, nil
|
||||
return Settings{BrandName: name, RegistrationOpen: settings.RegistrationOpen}, nil
|
||||
}
|
||||
|
||||
func (s *Service) SetRegistrationOpen(ctx context.Context, open bool) error {
|
||||
return s.site.SetRegistrationOpen(ctx, open)
|
||||
}
|
||||
|
||||
func (s *Service) SetBrandName(ctx context.Context, name string) (string, error) {
|
||||
|
||||
@@ -10,16 +10,19 @@ import (
|
||||
"github.com/dihemoe/alisten/internal/app/port/porttest"
|
||||
)
|
||||
|
||||
func TestBrandName(t *testing.T) {
|
||||
func TestSettings(t *testing.T) {
|
||||
s := New(porttest.New())
|
||||
if got, err := s.BrandName(t.Context()); err != nil || got != "Alisten" {
|
||||
t.Fatalf("没有设置过时 = %q, %v,期望默认的 Alisten", got, err)
|
||||
if got, err := s.Settings(t.Context()); err != nil || got != (Settings{BrandName: "Alisten", RegistrationOpen: true}) {
|
||||
t.Fatalf("没有设置过时 = %#v, %v,期望默认的 Alisten、开放注册", got, err)
|
||||
}
|
||||
if saved, err := s.SetBrandName(t.Context(), " 周末听歌会\n"); err != nil || saved != "周末听歌会" {
|
||||
t.Fatalf("SetBrandName = %q, %v,期望返回去掉首尾空白的 %q", saved, err, "周末听歌会")
|
||||
}
|
||||
if got, err := s.BrandName(t.Context()); err != nil || got != "周末听歌会" {
|
||||
t.Errorf("设置之后 = %q, %v,期望去掉首尾空白的 %q", got, err, "周末听歌会")
|
||||
if err := s.SetRegistrationOpen(t.Context(), false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := s.Settings(t.Context()); err != nil || got != (Settings{BrandName: "周末听歌会", RegistrationOpen: false}) {
|
||||
t.Errorf("设置之后 = %#v, %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +45,8 @@ func TestInvalidBrandName(t *testing.T) {
|
||||
t.Errorf("SetBrandName(%q) = %v,期望 ErrInvalidBrandName", name, err)
|
||||
}
|
||||
}
|
||||
if got, err := s.BrandName(t.Context()); err != nil || got != "周末听歌会" {
|
||||
t.Errorf("被拒绝之后 = %q, %v,期望仍是 %q", got, err, "周末听歌会")
|
||||
if got, err := s.Settings(t.Context()); err != nil || got.BrandName != "周末听歌会" {
|
||||
t.Errorf("被拒绝之后 = %q, %v,期望仍是 %q", got.BrandName, err, "周末听歌会")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,12 +66,12 @@ type brokenSite struct{ port.Site }
|
||||
|
||||
var errBroken = errors.New("连接中断")
|
||||
|
||||
func (brokenSite) BrandName(context.Context) (string, error) {
|
||||
return "", errBroken
|
||||
func (brokenSite) Settings(context.Context) (port.SiteSettings, error) {
|
||||
return port.SiteSettings{}, errBroken
|
||||
}
|
||||
|
||||
func TestBrandNameReadFailure(t *testing.T) {
|
||||
if got, err := New(brokenSite{}).BrandName(t.Context()); !errors.Is(err, errBroken) {
|
||||
t.Errorf("读取失败时 = %q, %v,期望返回错误而不是默认的品牌名", got, err)
|
||||
func TestSettingsReadFailure(t *testing.T) {
|
||||
if got, err := New(brokenSite{}).Settings(t.Context()); !errors.Is(err, errBroken) {
|
||||
t.Errorf("读取失败时 = %#v, %v,期望返回错误而不是默认的品牌名", got, err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user