待ち行列への登録と状態表示を追加(新着からの登録、/queue、DATABASE_URL)

This commit is contained in:
2026-10-03 19:07:53 +09:00
parent a71b915c81
commit 4a02526e76
20 changed files with 1146 additions and 14 deletions
+29 -1
View File
@@ -1,17 +1,28 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
_ "time/tzdata"
"gitea.kazu634.com/kazu634/tver/internal/store"
"gitea.kazu634.com/kazu634/tver/internal/tver"
"gitea.kazu634.com/kazu634/tver/internal/web"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
pool, err := openDatabase(context.Background(), os.Getenv("DATABASE_URL"), pgxpool.New, store.Migrate)
if err != nil {
log.Fatal(err)
}
defer pool.Close()
address := os.Getenv("WEB_ADDR")
if address == "" {
address = ":8080"
@@ -22,9 +33,26 @@ func main() {
}
server := &http.Server{
Addr: address,
Handler: web.NewHandler(client, time.Now, log.Default()),
Handler: web.NewHandler(client, store.New(pool), time.Now, log.Default()),
ReadHeaderTimeout: 5 * time.Second,
}
log.Printf("web listening on %s", address)
log.Fatal(server.ListenAndServe())
}
func openDatabase(ctx context.Context, databaseURL string,
connect func(context.Context, string) (*pgxpool.Pool, error),
migrate func(context.Context, *pgxpool.Pool) error) (*pgxpool.Pool, error) {
if strings.TrimSpace(databaseURL) == "" {
return nil, errors.New("DATABASE_URL が設定されていません")
}
pool, err := connect(ctx, databaseURL)
if err != nil {
return nil, errors.New("DATABASE_URL から接続プールを作成できません")
}
if err := migrate(ctx, pool); err != nil {
pool.Close()
return nil, fmt.Errorf("DB マイグレーションに失敗: %w", err)
}
return pool, nil
}
+72
View File
@@ -0,0 +1,72 @@
package main
import (
"context"
"errors"
"strings"
"testing"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestOpenDatabaseRequiresURLAndRunsMigration(t *testing.T) {
ctx := context.Background()
connectCalls, migrateCalls := 0, 0
connect := func(_ context.Context, url string) (*pgxpool.Pool, error) {
connectCalls++
if url != "postgres://test:secret@localhost/test" {
t.Errorf("URL = %q", url)
}
return pgxpool.New(ctx, url)
}
migrate := func(_ context.Context, _ *pgxpool.Pool) error {
migrateCalls++
return nil
}
if _, err := openDatabase(ctx, " ", connect, migrate); err == nil || !strings.Contains(err.Error(), "DATABASE_URL") {
t.Fatalf("未設定 URL のエラー: %v", err)
}
if connectCalls != 0 || migrateCalls != 0 {
t.Fatalf("未設定でも接続・移行された: %d %d", connectCalls, migrateCalls)
}
pool, err := openDatabase(ctx, "postgres://test:secret@localhost/test", connect, migrate)
if err != nil || pool == nil {
t.Fatalf("DB 初期化: %v", err)
}
pool.Close()
if connectCalls != 1 || migrateCalls != 1 {
t.Fatalf("接続・移行回数: %d %d", connectCalls, migrateCalls)
}
}
func TestOpenDatabaseReportsMigrationFailure(t *testing.T) {
ctx := context.Background()
pool, err := openDatabase(ctx, "postgres://test:secret@localhost/test", pgxpool.New,
func(context.Context, *pgxpool.Pool) error { return errors.New("migration failed") })
if pool != nil || err == nil || !strings.Contains(err.Error(), "migration failed") {
t.Fatalf("移行失敗: pool=%v err=%v", pool, err)
}
}
func TestOpenDatabaseReportsConnectionFailureWithoutLeakingURL(t *testing.T) {
migrateCalled := false
pool, err := openDatabase(context.Background(), "postgres://user:secret@localhost/test",
func(context.Context, string) (*pgxpool.Pool, error) {
return nil, errors.New("postgres://user:secret@localhost/test: connection refused")
},
func(context.Context, *pgxpool.Pool) error {
migrateCalled = true
return nil
})
if pool != nil || err == nil || strings.Contains(err.Error(), "secret") || migrateCalled {
t.Fatalf("接続失敗: pool=%v err=%v migrateCalled=%v", pool, err, migrateCalled)
}
}
func TestOpenDatabaseDoesNotExposeInvalidURL(t *testing.T) {
_, err := openDatabase(context.Background(), "postgres://user:secret@%", pgxpool.New,
func(context.Context, *pgxpool.Pool) error { t.Fatal("移行してはいけない"); return nil })
if err == nil || strings.Contains(err.Error(), "secret") {
t.Fatalf("接続 URL の秘匿: %v", err)
}
}