支持项目编码访问单项目公示屏
需求:无需查询公示 Token,直接通过项目编码打开单项目公示页。 实现:规范化项目编码并按 code 查询,同时保留既有 Token 哈希查询;补充前后端及集成回归测试。
This commit is contained in:
@@ -288,14 +288,27 @@ type displaySnapshotDTO struct {
|
||||
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||
}
|
||||
|
||||
func (s *Server) displaySnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
token := strings.TrimSpace(r.PathValue("token"))
|
||||
if len(token) < 40 || len(token) > 128 {
|
||||
writeError(w, &apiError{Status: http.StatusNotFound, Code: "DISPLAY_NOT_FOUND", Message: "公示屏绑定不存在"})
|
||||
return
|
||||
func normalizeDisplayProjectCode(value string) (string, bool) {
|
||||
code := strings.ToUpper(strings.TrimSpace(value))
|
||||
if !projectCodePattern.MatchString(code) {
|
||||
return "", false
|
||||
}
|
||||
return code, true
|
||||
}
|
||||
|
||||
func (s *Server) displaySnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
identifier := strings.TrimSpace(r.PathValue("token"))
|
||||
var project model.Project
|
||||
err := s.db.WithContext(r.Context()).Where("display_token_hash = ?", security.HashToken(token)).First(&project).Error
|
||||
var err error
|
||||
if code, ok := normalizeDisplayProjectCode(identifier); ok {
|
||||
err = s.db.WithContext(r.Context()).Where("code = ?", code).First(&project).Error
|
||||
} else {
|
||||
if len(identifier) < 40 || len(identifier) > 128 {
|
||||
writeError(w, &apiError{Status: http.StatusNotFound, Code: "DISPLAY_NOT_FOUND", Message: "公示屏绑定不存在"})
|
||||
return
|
||||
}
|
||||
err = s.db.WithContext(r.Context()).Where("display_token_hash = ?", security.HashToken(identifier)).First(&project).Error
|
||||
}
|
||||
if err != nil {
|
||||
writeError(w, mapNotFound(err, "DISPLAY_NOT_FOUND", "公示屏绑定不存在"))
|
||||
return
|
||||
|
||||
@@ -58,6 +58,26 @@ func TestPublicDisplayProjectViewOnlySerializesDisplayFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDisplayProjectCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
ok bool
|
||||
}{
|
||||
{input: "raft", want: "RAFT", ok: true},
|
||||
{input: " east-ride ", want: "EAST-RIDE", ok: true},
|
||||
{input: "A", ok: false},
|
||||
{input: "bad code", ok: false},
|
||||
{input: strings.Repeat("a", 40), ok: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got, ok := normalizeDisplayProjectCode(test.input)
|
||||
if got != test.want || ok != test.ok {
|
||||
t.Fatalf("normalizeDisplayProjectCode(%q) = %q, %v; want %q, %v", test.input, got, ok, test.want, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicPhoneLookupIsDisabledInProduction(t *testing.T) {
|
||||
server := &Server{config: config.Config{Environment: "production"}}
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -158,6 +158,22 @@ func TestCurrentDayViewsIgnoreYesterdayRunningSessionPostgresIntegration(t *test
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("display by project code", func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/display/"+strings.ToLower(project.Code)+"/snapshot", nil)
|
||||
server.Handler().ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var response displaySnapshotDTO
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.Project.ID != project.ID {
|
||||
t.Fatalf("project = %q, want %q", response.Project.ID, project.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("admin project", func(t *testing.T) {
|
||||
projection, _, _, _, _, err := server.adminProjectProjection(context.Background(), project)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user