feat: store whether registration is open

This commit is contained in:
DiheChen committed 2026-11-08 21:48:23 +08:00
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
View File
@@ -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;
+2 -2
View File
@@ -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
}
+13 -6
View File
@@ -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
}
+23 -9
View File
@@ -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)
+13 -5
View File
@@ -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 {
+8 -2
View File
@@ -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
}
+15 -5
View File
@@ -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) {
+15 -12
View File
@@ -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)
}
}