1+ package httpserver
2+
3+ import (
4+ "net/http"
5+ "strconv"
6+ "time"
7+
8+ "github.com/gin-gonic/gin"
9+ "gorm.io/gorm"
10+
11+ "AIBackend/internal/provider"
12+ "AIBackend/internal/services"
13+ "AIBackend/pkg/middleware"
14+ )
15+
16+ type Server struct {
17+ Auth * services.AuthService
18+ Chat * services.ChatService
19+ }
20+
21+ // NewRouter 创建并返回已配置的 Gin 引擎,注册健康检查、认证相关路由、带鉴权的会话与聊天 API(包含可选的 SSE 流式聊天)并提供前端静态文件服务。
22+ func NewRouter (db * gorm.DB , llm provider.LLMProvider ) * gin.Engine {
23+ g := gin .Default ()
24+
25+ server := & Server {
26+ Auth : services .NewAuthService (db ),
27+ Chat : services .NewChatService (db , llm ),
28+ }
29+
30+ g .GET ("/health" , func (c * gin.Context ) { c .JSON (http .StatusOK , gin.H {"status" : "ok" }) })
31+
32+ api := g .Group ("/api" )
33+ {
34+ auth := api .Group ("/auth" )
35+ auth .POST ("/register" , server .handleRegister )
36+ auth .POST ("/login" , server .handleLogin )
37+ }
38+
39+ protected := api .Group ("" )
40+ protected .Use (middleware .AuthRequired ())
41+ {
42+ protected .GET ("/me" , server .handleMe )
43+ protected .GET ("/conversations" , server .handleListConversations )
44+ protected .GET ("/conversations/:id/messages" , server .handleGetMessages )
45+ protected .POST ("/chat" , middleware .ModelAccess (), server .handleChat )
46+ }
47+
48+ // Serve static frontend files without conflicting wildcard
49+ g .StaticFile ("/" , "./web/index.html" )
50+ g .Static ("/css" , "./web/css" )
51+ g .Static ("/js" , "./web/js" )
52+
53+ return g
54+ }
55+
56+ type registerReq struct {
57+ Email string `json:"email" binding:"required"`
58+ Password string `json:"password" binding:"required"`
59+ Role string `json:"role"`
60+ }
61+
62+ type loginReq struct {
63+ Email string `json:"email" binding:"required"`
64+ Password string `json:"password" binding:"required"`
65+ }
66+
67+ func (s * Server ) handleRegister (c * gin.Context ) {
68+ var req registerReq
69+ if err := c .ShouldBindJSON (& req ); err != nil {
70+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
71+ return
72+ }
73+ user , token , err := s .Auth .Register (req .Email , req .Password , req .Role )
74+ if err != nil {
75+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
76+ return
77+ }
78+ c .JSON (http .StatusOK , gin.H {"user" : user , "token" : token })
79+ }
80+
81+ func (s * Server ) handleLogin (c * gin.Context ) {
82+ var req loginReq
83+ if err := c .ShouldBindJSON (& req ); err != nil {
84+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
85+ return
86+ }
87+ user , token , err := s .Auth .Login (req .Email , req .Password )
88+ if err != nil {
89+ c .JSON (http .StatusUnauthorized , gin.H {"error" : err .Error ()})
90+ return
91+ }
92+ c .JSON (http .StatusOK , gin.H {"user" : user , "token" : token })
93+ }
94+
95+ func (s * Server ) handleMe (c * gin.Context ) {
96+ c .JSON (http .StatusOK , gin.H {
97+ "user_id" : c .GetUint ("user_id" ),
98+ "user_email" : c .GetString ("user_email" ),
99+ "user_role" : c .GetString ("user_role" ),
100+ })
101+ }
102+
103+ func (s * Server ) handleListConversations (c * gin.Context ) {
104+ uid := c .GetUint ("user_id" )
105+ convs , err := s .Chat .ListConversations (uid )
106+ if err != nil {
107+ c .JSON (http .StatusInternalServerError , gin.H {"error" : err .Error ()})
108+ return
109+ }
110+ c .JSON (http .StatusOK , gin.H {"conversations" : convs })
111+ }
112+
113+ func (s * Server ) handleGetMessages (c * gin.Context ) {
114+ uid := c .GetUint ("user_id" )
115+ idStr := c .Param ("id" )
116+ id64 , _ := strconv .ParseUint (idStr , 10 , 64 )
117+ msgs , err := s .Chat .GetMessages (uid , uint (id64 ))
118+ if err != nil {
119+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
120+ return
121+ }
122+ c .JSON (http .StatusOK , gin.H {"messages" : msgs })
123+ }
124+
125+ type chatReq struct {
126+ ConversationID uint `json:"conversation_id"`
127+ Model string `json:"model"`
128+ Message string `json:"message" binding:"required"`
129+ Stream * bool `json:"stream"`
130+ }
131+
132+ func (s * Server ) handleChat (c * gin.Context ) {
133+ uid := c .GetUint ("user_id" )
134+ var req chatReq
135+ if err := c .ShouldBindJSON (& req ); err != nil {
136+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
137+ return
138+ }
139+ // Fallback to query param model for middleware check compatibility
140+ if req .Model == "" {
141+ req .Model = c .Query ("model" )
142+ }
143+ // Enforce model access if provided in body
144+ role := c .GetString ("user_role" )
145+ if ! middleware .CheckModelAccess (role , req .Model ) {
146+ c .JSON (http .StatusForbidden , gin.H {"error" : "model access denied for role" })
147+ return
148+ }
149+ streaming := false
150+ if req .Stream != nil {
151+ streaming = * req .Stream
152+ }
153+ if c .Query ("stream" ) == "1" || c .Query ("stream" ) == "true" {
154+ streaming = true
155+ }
156+ if ! streaming {
157+ convID , reply , err := s .Chat .SendMessage (c .Request .Context (), uid , req .ConversationID , req .Model , req .Message , nil )
158+ if err != nil {
159+ c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
160+ return
161+ }
162+ c .JSON (http .StatusOK , gin.H {"conversation_id" : convID , "reply" : reply })
163+ return
164+ }
165+ // Streaming via SSE
166+ w := c .Writer
167+ c .Header ("Content-Type" , "text/event-stream" )
168+ c .Header ("Cache-Control" , "no-cache" )
169+ c .Header ("Connection" , "keep-alive" )
170+ c .Status (http .StatusOK )
171+ flusher , _ := w .(http.Flusher )
172+ sentAny := false
173+ convID , _ , err := s .Chat .SendMessage (c .Request .Context (), uid , req .ConversationID , req .Model , req .Message , func (chunk string ) error {
174+ sentAny = true
175+ _ , err := w .Write ([]byte ("data: " + chunk + "\n \n " ))
176+ if err == nil && flusher != nil {
177+ flusher .Flush ()
178+ }
179+ return err
180+ })
181+ if err != nil {
182+ // send error as SSE comment and 0-length event end
183+ _ , _ = w .Write ([]byte (": error: " + err .Error () + "\n \n " ))
184+ if flusher != nil {
185+ flusher .Flush ()
186+ }
187+ return
188+ }
189+ if ! sentAny {
190+ // send at least one empty event to keep clients happy
191+ _ , _ = w .Write ([]byte ("data: \n \n " ))
192+ }
193+ // end event
194+ _ , _ = w .Write ([]byte ("event: done\n " + "data: {\" conversation_id\" : " + strconv .FormatUint (uint64 (convID ), 10 ) + "}\n \n " ))
195+ if flusher != nil {
196+ flusher .Flush ()
197+ }
198+ // allow connection to close shortly after
199+ time .Sleep (50 * time .Millisecond )
200+ }
0 commit comments