mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-13 15:37:26 +02:00
Add files via upload
This commit is contained in:
@@ -111,19 +111,43 @@ func (db *DB) GetProject(id string) (*Project, error) {
|
|||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountProjects 统计项目数量。
|
func projectListSearchPattern(q string) string {
|
||||||
func (db *DB) CountProjects(status, search string) (int, error) {
|
q = strings.TrimSpace(q)
|
||||||
query := `SELECT COUNT(*) FROM projects WHERE 1=1`
|
if q == "" {
|
||||||
args := []interface{}{}
|
return ""
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteByte('%')
|
||||||
|
for _, r := range q {
|
||||||
|
switch r {
|
||||||
|
case '%', '_', '\\':
|
||||||
|
b.WriteByte('\\')
|
||||||
|
b.WriteRune(r)
|
||||||
|
default:
|
||||||
|
b.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.WriteByte('%')
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendProjectListFilters(query string, args []interface{}, status, search string) (string, []interface{}) {
|
||||||
if s := strings.TrimSpace(status); s != "" {
|
if s := strings.TrimSpace(status); s != "" {
|
||||||
query += " AND status = ?"
|
query += " AND status = ?"
|
||||||
args = append(args, s)
|
args = append(args, s)
|
||||||
}
|
}
|
||||||
if q := strings.TrimSpace(search); q != "" {
|
if pattern := projectListSearchPattern(search); pattern != "" {
|
||||||
pattern := "%" + q + "%"
|
query += ` AND (LOWER(name) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(description,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(id) LIKE LOWER(?) ESCAPE '\')`
|
||||||
query += " AND (name LIKE ? OR COALESCE(description,'') LIKE ?)"
|
args = append(args, pattern, pattern, pattern)
|
||||||
args = append(args, pattern, pattern)
|
|
||||||
}
|
}
|
||||||
|
return query, args
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountProjects 统计项目数量。
|
||||||
|
func (db *DB) CountProjects(status, search string) (int, error) {
|
||||||
|
query := `SELECT COUNT(*) FROM projects WHERE 1=1`
|
||||||
|
args := []interface{}{}
|
||||||
|
query, args = appendProjectListFilters(query, args, status, search)
|
||||||
var count int
|
var count int
|
||||||
if err := db.QueryRow(query, args...).Scan(&count); err != nil {
|
if err := db.QueryRow(query, args...).Scan(&count); err != nil {
|
||||||
return 0, fmt.Errorf("统计项目失败: %w", err)
|
return 0, fmt.Errorf("统计项目失败: %w", err)
|
||||||
@@ -139,15 +163,7 @@ func (db *DB) ListProjects(status, search string, limit, offset int) ([]*Project
|
|||||||
query := `SELECT id, name, COALESCE(description,''), COALESCE(scope_json,''), status, pinned, created_at, updated_at
|
query := `SELECT id, name, COALESCE(description,''), COALESCE(scope_json,''), status, pinned, created_at, updated_at
|
||||||
FROM projects WHERE 1=1`
|
FROM projects WHERE 1=1`
|
||||||
args := []interface{}{}
|
args := []interface{}{}
|
||||||
if s := strings.TrimSpace(status); s != "" {
|
query, args = appendProjectListFilters(query, args, status, search)
|
||||||
query += " AND status = ?"
|
|
||||||
args = append(args, s)
|
|
||||||
}
|
|
||||||
if q := strings.TrimSpace(search); q != "" {
|
|
||||||
pattern := "%" + q + "%"
|
|
||||||
query += " AND (name LIKE ? OR COALESCE(description,'') LIKE ?)"
|
|
||||||
args = append(args, pattern, pattern)
|
|
||||||
}
|
|
||||||
query += " ORDER BY pinned DESC, updated_at DESC LIMIT ? OFFSET ?"
|
query += " ORDER BY pinned DESC, updated_at DESC LIMIT ? OFFSET ?"
|
||||||
args = append(args, limit, offset)
|
args = append(args, limit, offset)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListProjectsSearchCaseInsensitive(t *testing.T) {
|
||||||
|
dbPath := filepath.Join(t.TempDir(), "projects-search.db")
|
||||||
|
db, err := NewDB(dbPath, zap.NewNop())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
p1, err := db.CreateProject(&Project{Name: "Alpha Security Review", Status: "active"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p2, err := db.CreateProject(&Project{Name: "beta-scan", Status: "active"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := db.CreateProject(&Project{Name: "Other", Status: "archived"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
search string
|
||||||
|
status string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{name: "case insensitive name", search: "alpha", status: "active", want: []string{p1.ID}},
|
||||||
|
{name: "upper query", search: "BETA", status: "active", want: []string{p2.ID}},
|
||||||
|
{name: "search by id substring", search: p1.ID[:8], status: "", want: []string{p1.ID}},
|
||||||
|
{name: "status filter", search: "alpha", status: "archived", want: nil},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
list, err := db.ListProjects(tc.status, tc.search, 50, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := make([]string, 0, len(list))
|
||||||
|
for _, p := range list {
|
||||||
|
got = append(got, p.ID)
|
||||||
|
}
|
||||||
|
if len(got) != len(tc.want) {
|
||||||
|
t.Fatalf("got %v want %v", got, tc.want)
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
if got[i] != tc.want[i] {
|
||||||
|
t.Fatalf("got %v want %v", got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectListSearchPatternEscapesWildcards(t *testing.T) {
|
||||||
|
dbPath := filepath.Join(t.TempDir(), "projects-like.db")
|
||||||
|
db, err := NewDB(dbPath, zap.NewNop())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
p, err := db.CreateProject(&Project{Name: "100% coverage", Status: "active"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
list, err := db.ListProjects("active", "100%", 50, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(list) != 1 || list[0].ID != p.ID {
|
||||||
|
t.Fatalf("expected exact match for literal %% query, got %#v", list)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user