Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,3 @@ func Auth(acceptableRoles ...Role) func(c *gin.Context) {
Build())
}
}

2 changes: 1 addition & 1 deletion middleware/event_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func GetClientFromContext(c *gin.Context) (clientId int64, err error) {
}

/*
Get event and put to context.
Get event and put to context.
This middleware should be added to any route that performs event action.
*/
func EventActionPreProcess(c *gin.Context) {
Expand Down
6 changes: 3 additions & 3 deletions middleware/huma_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func authenticateToken(authHeader string) (*AuthContext, error) {
if err != nil || !tokenParsed.Valid {
return nil, err
}

return &AuthContext{
ID: claims.Who,
Member: claims.Member,
Expand Down Expand Up @@ -153,7 +153,7 @@ func authenticateToken(authHeader string) (*AuthContext, error) {
Role: userRoles,
UserInfo: userinfo,
}

return &AuthContext{
User: user,
ID: member.MemberId,
Expand Down Expand Up @@ -196,4 +196,4 @@ func AuthenticateUserWithContext(ctx context.Context, authHeader string, accepta

// Fallback to original implementation for backward compatibility
return AuthenticateUser(authHeader, acceptableRoles...)
}
}
22 changes: 11 additions & 11 deletions middleware/huma_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func TestHumaAuthMiddleware(t *testing.T) {
ShouldFail: false,
},
{
CaseID: "1.2",
CaseID: "1.2",
Description: "Valid legacy JWT token with admin role",
Authorization: util.GenToken("admin", "2333333333"),
ExpectedAuth: true,
Expand Down Expand Up @@ -253,21 +253,21 @@ func TestHumaAuthMiddleware(t *testing.T) {
if capturedCtx != nil {
authCtx = middleware.GetAuthContextFromHuma(capturedCtx)
}

if tc.ExpectedAuth {
if authCtx == nil {
t.Errorf("Expected auth context to be set for case %s", tc.CaseID)
return
}

if authCtx.Role != tc.ExpectedRole {
t.Errorf("Expected role %s, got %s", tc.ExpectedRole, authCtx.Role)
}

if authCtx.ID == "" {
t.Error("Expected user ID to be set")
}

if !authCtx.IsLegacyJWT {
t.Error("Expected legacy JWT flag to be true")
}
Expand Down Expand Up @@ -302,7 +302,7 @@ func TestRequireAuthMiddleware(t *testing.T) {
ExpectedContinue: true,
},
{
CaseID: "2.2",
CaseID: "2.2",
Description: "Member access with member or admin role required",
AuthContext: &middleware.AuthContext{
ID: "member123",
Expand All @@ -326,7 +326,7 @@ func TestRequireAuthMiddleware(t *testing.T) {
Description: "Client access with admin role required",
AuthContext: &middleware.AuthContext{
ID: "client123",
Role: "client",
Role: "client",
IsLegacyJWT: true,
},
RequiredRoles: []middleware.Role{"admin"},
Expand All @@ -351,7 +351,7 @@ func TestRequireAuthMiddleware(t *testing.T) {
t.Run(tc.CaseID+"_"+tc.Description, func(t *testing.T) {
// Create mock context
mockCtx := NewMockHumaContext()

// Set auth context if provided
if tc.AuthContext != nil {
mockCtx.ctx = context.WithValue(mockCtx.ctx, middleware.GetAuthContextKey(), tc.AuthContext)
Expand Down Expand Up @@ -408,7 +408,7 @@ func TestGetAuthContextFromHuma(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.CaseID+"_"+tc.Description, func(t *testing.T) {
mockCtx := NewMockHumaContext()

if tc.SetContext && tc.AuthContext != nil {
mockCtx.ctx = context.WithValue(mockCtx.ctx, middleware.GetAuthContextKey(), tc.AuthContext)
}
Expand Down Expand Up @@ -484,7 +484,7 @@ func TestAuthenticateUserWithContext(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.CaseID+"_"+tc.Description, func(t *testing.T) {
ctx := context.Background()

if tc.ContextAuth != nil {
ctx = context.WithValue(ctx, middleware.GetAuthContextKey(), tc.ContextAuth)
}
Expand All @@ -510,4 +510,4 @@ func TestAuthenticateUserWithContext(t *testing.T) {
}
})
}
}
}
12 changes: 6 additions & 6 deletions middleware/huma_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ import (
func HumaLogger() func(ctx huma.Context, next func(huma.Context)) {
return func(ctx huma.Context, next func(huma.Context)) {
startTime := time.Now()

// Process request
next(ctx)

endTime := time.Now()
latencyTime := endTime.Sub(startTime).Microseconds()

// Extract user ID from auth context if available
var userID string
if auth := GetAuthContext(ctx.Context()); auth != nil {
userID = auth.ID
}

// Get response status from context
status := ctx.Status()
if status == 0 {
status = 200 // Default to 200 if not set
}

util.Logger.WithFields(logrus.Fields{
"status_code": status,
"latency": latencyTime,
Expand All @@ -43,4 +43,4 @@ func HumaLogger() func(ctx huma.Context, next func(huma.Context)) {
"userId": userID,
}).Info("HTTP Request")
}
}
}
2 changes: 1 addition & 1 deletion middleware/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Logger(c *gin.Context) {

endTime := time.Now()

latencyTime := endTime.Sub(startTime).Microseconds()
latencyTime := endTime.Sub(startTime).Microseconds()

util.Logger.WithFields(logrus.Fields{
"status_code": c.Writer.Status(),
Expand Down
40 changes: 40 additions & 0 deletions migrations/000008_add_images_to_event.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Recreate event_log_view without images column
DROP VIEW public.event_log_view;
CREATE VIEW public.event_log_view AS
SELECT event_log.event_log_id,
event_log.event_id,
event_log.description,
event_log.member_id,
event_log.gmt_create,
event_action.action
FROM ((public.event_log
LEFT JOIN public.event_event_action_relation ON ((event_log.event_log_id = event_event_action_relation.event_log_id)))
LEFT JOIN public.event_action ON ((event_event_action_relation.event_action_id = event_action.event_action_id)));

-- Recreate event_view without images column
DROP VIEW public.event_view;
CREATE VIEW public.event_view AS
SELECT event.event_id,
event.client_id,
event.model,
event.phone,
event.qq,
event.contact_preference,
event.problem,
event.member_id,
event.closed_by,
event.gmt_create,
event.gmt_modified,
event.size,
COALESCE(event_status.status, ''::character varying) AS status,
event.github_issue_id,
event.github_issue_number
FROM ((public.event
LEFT JOIN public.event_event_status_relation ON ((event.event_id = event_event_status_relation.event_id)))
LEFT JOIN public.event_status ON ((event_event_status_relation.event_status_id = event_status.event_status_id)));

-- Remove images field from event_log table
ALTER TABLE event_log DROP COLUMN images;

-- Remove images field from event table
ALTER TABLE event DROP COLUMN images;
44 changes: 44 additions & 0 deletions migrations/000008_add_images_to_event.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
-- Add images field to event table (client's problem images)
ALTER TABLE event ADD COLUMN images TEXT;
COMMENT ON COLUMN event.images IS '事件图片(JSON数组)';

-- Add images field to event_log table (member's repair images)
ALTER TABLE event_log ADD COLUMN images TEXT;
COMMENT ON COLUMN event_log.images IS '维修记录图片(JSON数组)';

-- Recreate event_view to include the new images column
DROP VIEW public.event_view;
CREATE VIEW public.event_view AS
SELECT event.event_id,
event.client_id,
event.model,
event.phone,
event.qq,
event.contact_preference,
event.problem,
event.images,
event.member_id,
event.closed_by,
event.gmt_create,
event.gmt_modified,
event.size,
COALESCE(event_status.status, ''::character varying) AS status,
event.github_issue_id,
event.github_issue_number
FROM ((public.event
LEFT JOIN public.event_event_status_relation ON ((event.event_id = event_event_status_relation.event_id)))
LEFT JOIN public.event_status ON ((event_event_status_relation.event_status_id = event_status.event_status_id)));

-- Recreate event_log_view to include the new images column
DROP VIEW public.event_log_view;
CREATE VIEW public.event_log_view AS
SELECT event_log.event_log_id,
event_log.event_id,
event_log.description,
event_log.images,
event_log.member_id,
event_log.gmt_create,
event_action.action
FROM ((public.event_log
LEFT JOIN public.event_event_action_relation ON ((event_log.event_log_id = event_event_action_relation.event_log_id)))
LEFT JOIN public.event_action ON ((event_event_action_relation.event_action_id = event_action.event_action_id)));
48 changes: 48 additions & 0 deletions model/db_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package model

import (
"database/sql/driver"
"encoding/json"
"fmt"
)

// StringSlice is a custom type for []string that can be scanned from JSON in database
type StringSlice []string

// Scan implements sql.Scanner interface
func (s *StringSlice) Scan(value interface{}) error {
if value == nil {
*s = []string{}
return nil
}

var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return fmt.Errorf("unsupported type for StringSlice: %T", value)
}

// Handle empty or null JSON
if len(bytes) == 0 || string(bytes) == "null" {
*s = []string{}
return nil
}

return json.Unmarshal(bytes, s)
}

// Value implements driver.Valuer interface
func (s StringSlice) Value() (driver.Value, error) {
if s == nil {
return "[]", nil
}
bytes, err := json.Marshal(s)
if err != nil {
return nil, err
}
return string(bytes), nil
}
36 changes: 20 additions & 16 deletions model/dto/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ type EventID struct {
}

type CommitRequest struct {
Content string `json:"content"`
Size string `json:"size"`
Content string `json:"content"`
Size string `json:"size"`
Images []string `json:"images,omitempty"`
}

type AlterCommitRequest struct {
Content string `json:"content"`
Size string `json:"size"`
Content string `json:"content"`
Size string `json:"size"`
Images []string `json:"images,omitempty"`
}

type UpdateRequest struct {
Phone string `json:"phone" binding:"omitempty,len=11,numeric"`
QQ string `json:"qq" binding:"omitempty,min=5,max=20,numeric"`
Problem string `json:"problem" db:"problem" binding:"omitempty,max=1000"`
Model string `json:"model" binding:"omitempty,max=40"`
ContactPreference string `json:"contactPreference" db:"contact_preference" `
Size string `json:"size" db:"size" `
Phone string `json:"phone" binding:"omitempty,len=11,numeric"`
QQ string `json:"qq" binding:"omitempty,min=5,max=20,numeric"`
Problem string `json:"problem" db:"problem" binding:"omitempty,max=1000"`
Images []string `json:"images,omitempty"`
Model string `json:"model" binding:"omitempty,max=40"`
ContactPreference string `json:"contactPreference" db:"contact_preference" `
Size string `json:"size" db:"size" `
}
type CreateEventRequest struct {
ClientId int64 `json:"clientId" db:"client_id"`
Model string `json:"model" binding:"omitempty,max=40"`
Phone string `json:"phone" binding:"required,omitempty,len=11,numeric"`
QQ string `json:"qq" binding:"omitempty,min=5,max=20,numeric"`
ContactPreference string `json:"contactPreference" db:"contact_preference" `
Problem string `json:"problem" db:"problem" binding:"required,omitempty,max=1000"`
ClientId int64 `json:"clientId" db:"client_id"`
Model string `json:"model" binding:"omitempty,max=40"`
Phone string `json:"phone" binding:"required,omitempty,len=11,numeric"`
QQ string `json:"qq" binding:"omitempty,min=5,max=20,numeric"`
ContactPreference string `json:"contactPreference" db:"contact_preference" `
Problem string `json:"problem" db:"problem" binding:"required,omitempty,max=1000"`
Images []string `json:"images,omitempty"`
}
Loading