diff --git a/api/event/info.go b/api/event/info.go index 6f63a6a..e0d143c 100644 --- a/api/event/info.go +++ b/api/event/info.go @@ -16,11 +16,11 @@ import ( // @Tags Event // @Accept json // @Produce json -// @Param event_id query string true "Event UUID" -// @Success 200 {object} utils.RespStatus{data=service_event.InfoResponse} "Successful retrieval" -// @Failure 400 {object} utils.RespStatus{data=nil} "Invalid Input" -// @Failure 404 {object} utils.RespStatus{data=nil} "Event Not Found" -// @Failure 500 {object} utils.RespStatus{data=nil} "Internal Server Error" +// @Param event_id query string true "Event UUID" +// @Success 200 {object} utils.RespStatus{data=service_event.EventInfoResponse} "Successful retrieval" +// @Failure 400 {object} utils.RespStatus{data=nil} "Invalid Input" +// @Failure 404 {object} utils.RespStatus{data=nil} "Event Not Found" +// @Failure 500 {object} utils.RespStatus{data=nil} "Internal Server Error" // @Security ApiKeyAuth // @Router /event/info [get] func (self *EventHandler) Info(c *gin.Context) { @@ -41,9 +41,9 @@ func (self *EventHandler) Info(c *gin.Context) { return } - result := self.svc.Info(&service_event.InfoPayload{ + result := self.svc.GetEventInfo(&service_event.EventInfoPayload{ Context: c, - Data: &service_event.InfoData{ + Data: &service_event.EventInfoData{ EventId: eventId, }, }) diff --git a/api/event/list.go b/api/event/list.go new file mode 100644 index 0000000..fa92c9f --- /dev/null +++ b/api/event/list.go @@ -0,0 +1,65 @@ +package event + +import ( + "nixcn-cms/internal/exception" + "nixcn-cms/service/service_event" + "nixcn-cms/utils" + + "github.com/gin-gonic/gin" +) + +// List retrieves a paginated list of events from the database. +// +// @Summary List Events +// @Description Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency. +// @Tags Event +// @Accept json +// @Produce json +// @Param limit query string false "Maximum number of events to return (default 20)" +// @Param offset query string true "Number of events to skip" +// @Success 200 {object} utils.RespStatus{data=[]data.EventIndexDoc} "Successful paginated list retrieval" +// @Failure 400 {object} utils.RespStatus{data=nil} "Invalid Input (Missing offset or malformed parameters)" +// @Failure 500 {object} utils.RespStatus{data=nil} "Internal Server Error (Database query failed)" +// @Security ApiKeyAuth +// @Router /event/list [get] +func (self *EventHandler) List(c *gin.Context) { + type ListQuery struct { + Limit *string `form:"limit"` + Offset *string `form:"offset"` + } + + var query ListQuery + if err := c.ShouldBindQuery(&query); err != nil { + // Handle binding error (e.g., syntax errors in query string) + exc := new(exception.Builder). + SetStatus(exception.StatusClient). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeCommon). + SetOriginal(exception.CommonErrorInvalidInput). + Throw(c). + String() + + utils.HttpResponse(c, 400, exc) + return + } + + // Prepare payload for the service layer + eventListPayload := &service_event.EventListPayload{ + Context: c, + Limit: query.Limit, + Offset: query.Offset, + } + + // Call the service implementation + result := self.svc.ListEvents(eventListPayload) + + // Check if the service returned any exception + if result.Common.Exception.Original != exception.CommonSuccess { + utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String()) + return + } + + // Return successful response with event data + utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data) +} diff --git a/api/user/full.go b/api/user/full.go deleted file mode 100644 index b376836..0000000 --- a/api/user/full.go +++ /dev/null @@ -1,35 +0,0 @@ -package user - -import ( - "nixcn-cms/internal/exception" - "nixcn-cms/service/service_user" - "nixcn-cms/utils" - - "github.com/gin-gonic/gin" -) - -// Full retrieves the complete list of users directly from the database table. -// -// @Summary Get Full User Table -// @Description Fetches all user records without pagination. This is typically used for administrative overview or data export. -// @Tags User -// @Accept json -// @Produce json -// @Success 200 {object} utils.RespStatus{data=service_user.UserTableResponse} "Successful retrieval of full user table" -// @Failure 500 {object} utils.RespStatus{data=nil} "Internal Server Error (Database Error)" -// @Security ApiKeyAuth -// @Router /user/full [get] -func (self *UserHandler) Full(c *gin.Context) { - userTablePayload := &service_user.UserTablePayload{ - Context: c, - } - - result := self.svc.GetUserFullTable(userTablePayload) - - if result.Common.Exception.Original != exception.CommonSuccess { - utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String()) - return - } - - utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data) -} diff --git a/api/user/handler.go b/api/user/handler.go index 0ccd3e5..9327ab1 100644 --- a/api/user/handler.go +++ b/api/user/handler.go @@ -19,6 +19,5 @@ func ApiHandler(r *gin.RouterGroup) { r.GET("/info", userHandler.Info) r.PATCH("/update", userHandler.Update) r.GET("/list", middleware.Permission(20), userHandler.List) - r.POST("/full", middleware.Permission(40), userHandler.Full) r.POST("/create", middleware.Permission(50), userHandler.Create) } diff --git a/api/user/list.go b/api/user/list.go index 8baf8df..b6b9679 100644 --- a/api/user/list.go +++ b/api/user/list.go @@ -17,7 +17,7 @@ import ( // @Produce json // @Param limit query string false "Maximum number of users to return (default 0)" // @Param offset query string true "Number of users to skip" -// @Success 200 {object} utils.RespStatus{data=[]data.UserSearchDoc} "Successful paginated list retrieval" +// @Success 200 {object} utils.RespStatus{data=[]data.UserIndexDoc} "Successful paginated list retrieval" // @Failure 400 {object} utils.RespStatus{data=nil} "Invalid Input (Format Error)" // @Failure 500 {object} utils.RespStatus{data=nil} "Internal Server Error (Search Engine or Missing Offset)" // @Security ApiKeyAuth diff --git a/cmd/gen_exception/definitions/endpoint.yaml b/cmd/gen_exception/definitions/endpoint.yaml index 86be040..34324dc 100644 --- a/cmd/gen_exception/definitions/endpoint.yaml +++ b/cmd/gen_exception/definitions/endpoint.yaml @@ -12,6 +12,7 @@ endpoint: checkin: "02" checkin_query: "03" checkin_submit: "04" + list: "05" user: service: info: "01" diff --git a/cmd/gen_exception/definitions/specific.yaml b/cmd/gen_exception/definitions/specific.yaml index a162fe8..350ce9c 100644 --- a/cmd/gen_exception/definitions/specific.yaml +++ b/cmd/gen_exception/definitions/specific.yaml @@ -24,7 +24,7 @@ auth: invalid_redirect_uri: "00003" user: list: - meilisearch_failed: "00001" + database_failed: "00001" event: info: not_found: "00001" @@ -32,3 +32,5 @@ event: gen_code_failed: "00001" checkin_query: record_not_found: "00001" + list: + database_failed: "00001" diff --git a/config.default.yaml b/config.default.yaml index acfcd01..a7b897f 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -19,10 +19,6 @@ cache: password: "" db: 0 service_name: nixcn-cms-redis -search: - host: http://127.0.0.1:7700 - api_key: "" - service_name: nixcn-cms-meilisearch email: host: port: diff --git a/config/types.go b/config/types.go index db035dc..afdb35f 100644 --- a/config/types.go +++ b/config/types.go @@ -4,7 +4,6 @@ type config struct { Server server `yaml:"server"` Database database `yaml:"database"` Cache cache `yaml:"cache"` - Search search `yaml:"search"` Email email `yaml:"email"` Secrets secrets `yaml:"secrets"` TTL ttl `yaml:"ttl"` @@ -39,12 +38,6 @@ type cache struct { ServiceName string `yaml:"service_name"` } -type search struct { - Host string `yaml:"host"` - ApiKey string `yaml:"api_key"` - ServiceName string `yaml:"service_name"` -} - type email struct { Host string `yaml:"host"` Port string `yaml:"port"` diff --git a/data/attendance.go b/data/attendance.go index 09d7e1c..201beec 100644 --- a/data/attendance.go +++ b/data/attendance.go @@ -9,7 +9,6 @@ import ( "time" "github.com/google/uuid" - "github.com/meilisearch/meilisearch-go" "github.com/spf13/viper" "gorm.io/gorm" ) @@ -103,10 +102,6 @@ func (self *Attendance) Create(ctx context.Context) error { return err } - if err := self.UpdateSearchIndex(ctx); err != nil { - return err - } - return nil } @@ -147,60 +142,9 @@ func (self *Attendance) Update(ctx context.Context, attendanceId uuid.UUID, chec return nil, err } - // Sync to MeiliSearch (eventual consistency) - if err := attendance.UpdateSearchIndex(ctx); err != nil { - return nil, err - } - return &attendance, nil } -func (self *Attendance) SearchUsersByEvent(ctx context.Context, eventID string) (*meilisearch.SearchResponse, error) { - index := MeiliSearch.Index("attendance") - - return index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{ - Filter: "event_id = \"" + eventID + "\"", - Sort: []string{"checkin_at:asc"}, - }) -} - -func (self *Attendance) SearchEventsByUser(ctx context.Context, userID string) (*meilisearch.SearchResponse, error) { - index := MeiliSearch.Index("attendance") - - return index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{ - Filter: "user_id = \"" + userID + "\"", - Sort: []string{"checkin_at:asc"}, - }) -} - -func (self *Attendance) UpdateSearchIndex(ctx context.Context) error { - doc := AttendanceSearchDoc{ - AttendanceId: self.AttendanceId.String(), - EventId: self.EventId.String(), - UserId: self.UserId.String(), - CheckinAt: self.CheckinAt, - } - - index := MeiliSearch.Index("attendance") - - primaryKey := "attendance_id" - opts := &meilisearch.DocumentOptions{ - PrimaryKey: &primaryKey, - } - - if _, err := index.UpdateDocumentsWithContext(ctx, []AttendanceSearchDoc{doc}, opts); err != nil { - return err - } - - return nil -} - -func (self *Attendance) DeleteSearchIndex(ctx context.Context) error { - index := MeiliSearch.Index("attendance") - _, err := index.DeleteDocumentWithContext(ctx, self.AttendanceId.String(), nil) - return err -} - func (self *Attendance) GenCheckinCode(ctx context.Context, eventId uuid.UUID) (*string, error) { ttl := viper.GetDuration("ttl.checkin_code_ttl") rng := rand.New(rand.NewSource(time.Now().UnixNano())) diff --git a/data/data.go b/data/data.go index a1562a6..452b70a 100644 --- a/data/data.go +++ b/data/data.go @@ -7,7 +7,6 @@ import ( "log/slog" - "github.com/meilisearch/meilisearch-go" "github.com/redis/go-redis/v9" "github.com/spf13/viper" "gorm.io/gorm" @@ -15,7 +14,6 @@ import ( var Database *gorm.DB var Redis redis.UniversalClient -var MeiliSearch meilisearch.ServiceManager func Init(ctx context.Context) { // Init database @@ -62,12 +60,4 @@ func Init(ctx context.Context) { os.Exit(1) } Redis = rdb - - // Init meilisearch - mDSN := drivers.MeiliDSN{ - Host: viper.GetString("search.host"), - ApiKey: viper.GetString("search.api_key"), - } - mdb := drivers.MeiliSearch(mDSN) - MeiliSearch = mdb } diff --git a/data/drivers/meilisearch.go b/data/drivers/meilisearch.go deleted file mode 100644 index eb50daf..0000000 --- a/data/drivers/meilisearch.go +++ /dev/null @@ -1,34 +0,0 @@ -package drivers - -import ( - "fmt" - "net/http" - - "github.com/meilisearch/meilisearch-go" - "github.com/spf13/viper" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func MeiliSearch(dsn MeiliDSN) meilisearch.ServiceManager { - serviceName := viper.GetString("search.service_name") - - otelTransport := otelhttp.NewTransport( - http.DefaultTransport, - otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { - return fmt.Sprintf("%s %s", serviceName, r.Method) - }), - ) - - httpClient := &http.Client{ - Transport: otelTransport, - } - - return meilisearch.New(dsn.Host, - meilisearch.WithAPIKey(dsn.ApiKey), - meilisearch.WithCustomClient(httpClient), - meilisearch.WithContentEncoding( - meilisearch.GzipEncoding, - meilisearch.BestCompression, - ), - ) -} diff --git a/data/event.go b/data/event.go index 37b8e6b..674d39e 100644 --- a/data/event.go +++ b/data/event.go @@ -4,9 +4,7 @@ import ( "context" "time" - "github.com/go-viper/mapstructure/v2" "github.com/google/uuid" - "github.com/meilisearch/meilisearch-go" "gorm.io/gorm" ) @@ -23,7 +21,7 @@ type Event struct { EnableKYC bool `json:"enable_kyc" gorm:"not null"` } -type EventSearchDoc struct { +type EventIndexDoc struct { EventId string `json:"event_id"` Name string `json:"name"` Type string `json:"type"` @@ -68,11 +66,6 @@ func (self *Event) UpdateEventById(ctx context.Context, eventId uuid.UUID) error return err } - // Sync search index - if err := self.UpdateSearchIndex(ctx); err != nil { - return err - } - return nil } @@ -90,12 +83,6 @@ func (self *Event) Create(ctx context.Context) error { return err } - // Search index (eventual consistency) - if err := self.UpdateSearchIndex(ctx); err != nil { - // TODO: async retry / log - return err - } - return nil } @@ -108,51 +95,19 @@ func (self *Event) GetFullTable(ctx context.Context) (*[]Event, error) { return &events, err } -func (self *Event) FastListEvents(ctx context.Context, limit, offset int64) (*[]EventSearchDoc, error) { - index := MeiliSearch.Index("event") +func (self *Event) FastListEvents(ctx context.Context, limit, offset int64) (*[]EventIndexDoc, error) { + var results []EventIndexDoc + + err := Database.WithContext(ctx). + Model(&Event{}). + Select("event_id", "name", "type", "description", "start_time", "end_time"). + Limit(int(limit)). + Offset(int(offset)). + Scan(&results).Error - // Fast read from MeiliSearch (no DB involved) - result, err := index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{ - Limit: limit, - Offset: offset, - }) if err != nil { return nil, err } - var list []EventSearchDoc - if err := mapstructure.Decode(result.Hits, &list); err != nil { - return nil, err - } - - return &list, nil -} - -func (self *Event) UpdateSearchIndex(ctx context.Context) error { - doc := EventSearchDoc{ - EventId: self.EventId.String(), - Name: self.Name, - Type: self.Type, - Description: self.Description, - StartTime: self.StartTime, - EndTime: self.EndTime, - } - index := MeiliSearch.Index("event") - - primaryKey := "event_id" - opts := &meilisearch.DocumentOptions{ - PrimaryKey: &primaryKey, - } - - if _, err := index.UpdateDocumentsWithContext(ctx, []EventSearchDoc{doc}, opts); err != nil { - return err - } - - return nil -} - -func (self *Event) DeleteSearchIndex(ctx context.Context) error { - index := MeiliSearch.Index("event") - _, err := index.DeleteDocumentWithContext(ctx, self.EventId.String(), nil) - return err + return &results, nil } diff --git a/data/user.go b/data/user.go index c5da81e..5ce477b 100644 --- a/data/user.go +++ b/data/user.go @@ -3,9 +3,7 @@ package data import ( "context" - "github.com/go-viper/mapstructure/v2" "github.com/google/uuid" - "github.com/meilisearch/meilisearch-go" "gorm.io/gorm" ) @@ -23,7 +21,7 @@ type User struct { AllowPublic bool `json:"allow_public" gorm:"default:false;not null"` } -type UserSearchDoc struct { +type UserIndexDoc struct { UserId string `json:"user_id"` Email string `json:"email"` Username string `json:"username"` @@ -118,12 +116,6 @@ func (self *User) Create(ctx context.Context) error { return err } - // Search index (eventual consistency) - if err := self.UpdateSearchIndex(&ctx); err != nil { - // TODO: async retry / log - return err - } - return nil } @@ -139,8 +131,7 @@ func (self *User) UpdateByUserID(ctx context.Context, userId *uuid.UUID, updates if err := tx.Where("user_id = ?", userId).First(&updatedUser).Error; err != nil { return err } - - return updatedUser.UpdateSearchIndex(&ctx) + return nil }) } @@ -153,55 +144,19 @@ func (self *User) GetFullTable(ctx context.Context) (*[]User, error) { return &users, nil } -func (self *User) FastListUsers(ctx context.Context, limit, offset *int64) (*[]UserSearchDoc, error) { - index := MeiliSearch.Index("user") +func (self *User) FastListUsers(ctx context.Context, limit, offset *int) (*[]UserIndexDoc, error) { + var results []UserIndexDoc + + query := Database.WithContext(ctx).Model(&User{}) + + err := query.Select("user_id", "email", "username", "nickname", "subtitle", "avatar"). + Limit(*limit). + Offset(*offset). + Scan(&results).Error - // Fast read from MeiliSearch, no DB involved - result, err := index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{ - Limit: *limit, - Offset: *offset, - }) if err != nil { return nil, err } - var list []UserSearchDoc - if err := mapstructure.Decode(result.Hits, &list); err != nil { - return nil, err - } - - return &list, nil -} - -func (self *User) UpdateSearchIndex(ctx *context.Context) error { - doc := UserSearchDoc{ - UserId: self.UserId.String(), - Email: self.Email, - Username: self.Username, - Nickname: self.Nickname, - Subtitle: self.Subtitle, - Avatar: self.Avatar, - } - index := MeiliSearch.Index("user") - - primaryKey := "user_id" - opts := &meilisearch.DocumentOptions{ - PrimaryKey: &primaryKey, - } - - if _, err := index.UpdateDocumentsWithContext( - *ctx, - []UserSearchDoc{doc}, - opts, - ); err != nil { - return err - } - - return nil -} - -func (self *User) DeleteSearchIndex(ctx *context.Context) error { - index := MeiliSearch.Index("user") - _, err := index.DeleteDocumentWithContext(*ctx, self.UserId.String(), nil) - return err + return &results, nil } diff --git a/deploy/compose.yaml b/deploy/compose.yaml index 922b82e..780790e 100644 --- a/deploy/compose.yaml +++ b/deploy/compose.yaml @@ -27,20 +27,6 @@ services: timeout: 3s retries: 5 - meilisearch: - image: getmeili/meilisearch:v1.34.3 - container_name: cms-search - environment: - - MEILI_MASTER_KEY=meilisearch - volumes: - - ./data/meilisearch:/meili_data - healthcheck: - test: - ["CMD-SHELL", "curl -f http://localhost:7700/health || exit 1"] - interval: 5s - timeout: 3s - retries: 10 - lgtm: image: grafana/otel-lgtm:latest container_name: lgtm-stack diff --git a/devenv.nix b/devenv.nix index f1aff93..3f7360c 100644 --- a/devenv.nix +++ b/devenv.nix @@ -63,8 +63,5 @@ } ]; }; - meilisearch = { - enable = true; - }; }; } diff --git a/docs/docs.go b/docs/docs.go index b72cea5..44a733c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -809,7 +809,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/service_event.InfoResponse" + "$ref": "#/definitions/service_event.EventInfoResponse" } } } @@ -873,14 +873,14 @@ const docTemplate = `{ } } }, - "/user/full": { + "/event/list": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Fetches all user records without pagination. This is typically used for administrative overview or data export.", + "description": "Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency.", "consumes": [ "application/json" ], @@ -888,12 +888,27 @@ const docTemplate = `{ "application/json" ], "tags": [ - "User" + "Event" + ], + "summary": "List Events", + "parameters": [ + { + "type": "string", + "description": "Maximum number of events to return (default 20)", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Number of events to skip", + "name": "offset", + "in": "query", + "required": true + } ], - "summary": "Get Full User Table", "responses": { "200": { - "description": "Successful retrieval of full user table", + "description": "Successful paginated list retrieval", "schema": { "allOf": [ { @@ -903,7 +918,28 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/service_user.UserTableResponse" + "type": "array", + "items": { + "$ref": "#/definitions/data.EventIndexDoc" + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid Input (Missing offset or malformed parameters)", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.RespStatus" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" } } } @@ -911,7 +947,7 @@ const docTemplate = `{ } }, "500": { - "description": "Internal Server Error (Database Error)", + "description": "Internal Server Error (Database query failed)", "schema": { "allOf": [ { @@ -1072,7 +1108,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/data.UserSearchDoc" + "$ref": "#/definitions/data.UserIndexDoc" } } } @@ -1226,45 +1262,30 @@ const docTemplate = `{ } }, "definitions": { - "data.User": { + "data.EventIndexDoc": { "type": "object", "properties": { - "allow_public": { - "type": "boolean" - }, - "avatar": { + "description": { "type": "string" }, - "bio": { + "end_time": { "type": "string" }, - "email": { + "event_id": { "type": "string" }, - "id": { - "type": "integer" - }, - "nickname": { + "name": { "type": "string" }, - "permission_level": { - "type": "integer" - }, - "subtitle": { + "start_time": { "type": "string" }, - "user_id": { - "type": "string" - }, - "username": { - "type": "string" - }, - "uuid": { + "type": { "type": "string" } } }, - "data.UserSearchDoc": { + "data.UserIndexDoc": { "type": "object", "properties": { "avatar": { @@ -1394,7 +1415,7 @@ const docTemplate = `{ } } }, - "service_event.InfoResponse": { + "service_event.EventInfoResponse": { "type": "object", "properties": { "end_time": { @@ -1440,17 +1461,6 @@ const docTemplate = `{ } } }, - "service_user.UserTableResponse": { - "type": "object", - "properties": { - "user_table": { - "type": "array", - "items": { - "$ref": "#/definitions/data.User" - } - } - } - }, "utils.RespStatus": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 89415d4..43bbf68 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -807,7 +807,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/service_event.InfoResponse" + "$ref": "#/definitions/service_event.EventInfoResponse" } } } @@ -871,14 +871,14 @@ } } }, - "/user/full": { + "/event/list": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Fetches all user records without pagination. This is typically used for administrative overview or data export.", + "description": "Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency.", "consumes": [ "application/json" ], @@ -886,12 +886,27 @@ "application/json" ], "tags": [ - "User" + "Event" + ], + "summary": "List Events", + "parameters": [ + { + "type": "string", + "description": "Maximum number of events to return (default 20)", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Number of events to skip", + "name": "offset", + "in": "query", + "required": true + } ], - "summary": "Get Full User Table", "responses": { "200": { - "description": "Successful retrieval of full user table", + "description": "Successful paginated list retrieval", "schema": { "allOf": [ { @@ -901,7 +916,28 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/service_user.UserTableResponse" + "type": "array", + "items": { + "$ref": "#/definitions/data.EventIndexDoc" + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid Input (Missing offset or malformed parameters)", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.RespStatus" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" } } } @@ -909,7 +945,7 @@ } }, "500": { - "description": "Internal Server Error (Database Error)", + "description": "Internal Server Error (Database query failed)", "schema": { "allOf": [ { @@ -1070,7 +1106,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/data.UserSearchDoc" + "$ref": "#/definitions/data.UserIndexDoc" } } } @@ -1224,45 +1260,30 @@ } }, "definitions": { - "data.User": { + "data.EventIndexDoc": { "type": "object", "properties": { - "allow_public": { - "type": "boolean" - }, - "avatar": { + "description": { "type": "string" }, - "bio": { + "end_time": { "type": "string" }, - "email": { + "event_id": { "type": "string" }, - "id": { - "type": "integer" - }, - "nickname": { + "name": { "type": "string" }, - "permission_level": { - "type": "integer" - }, - "subtitle": { + "start_time": { "type": "string" }, - "user_id": { - "type": "string" - }, - "username": { - "type": "string" - }, - "uuid": { + "type": { "type": "string" } } }, - "data.UserSearchDoc": { + "data.UserIndexDoc": { "type": "object", "properties": { "avatar": { @@ -1392,7 +1413,7 @@ } } }, - "service_event.InfoResponse": { + "service_event.EventInfoResponse": { "type": "object", "properties": { "end_time": { @@ -1438,17 +1459,6 @@ } } }, - "service_user.UserTableResponse": { - "type": "object", - "properties": { - "user_table": { - "type": "array", - "items": { - "$ref": "#/definitions/data.User" - } - } - } - }, "utils.RespStatus": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fe26179..885b4e4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,31 +1,21 @@ basePath: /api/v1 definitions: - data.User: + data.EventIndexDoc: properties: - allow_public: - type: boolean - avatar: + description: type: string - bio: + end_time: type: string - email: + event_id: type: string - id: - type: integer - nickname: + name: type: string - permission_level: - type: integer - subtitle: + start_time: type: string - user_id: - type: string - username: - type: string - uuid: + type: type: string type: object - data.UserSearchDoc: + data.UserIndexDoc: properties: avatar: type: string @@ -108,7 +98,7 @@ definitions: checkin_code: type: string type: object - service_event.InfoResponse: + service_event.EventInfoResponse: properties: end_time: type: string @@ -138,13 +128,6 @@ definitions: username: type: string type: object - service_user.UserTableResponse: - properties: - user_table: - items: - $ref: '#/definitions/data.User' - type: array - type: object utils.RespStatus: properties: code: @@ -608,7 +591,7 @@ paths: - $ref: '#/definitions/utils.RespStatus' - properties: data: - $ref: '#/definitions/service_event.InfoResponse' + $ref: '#/definitions/service_event.EventInfoResponse' type: object "400": description: Invalid Input @@ -642,26 +625,47 @@ paths: summary: Get Event Information tags: - Event - /user/full: + /event/list: get: consumes: - application/json - description: Fetches all user records without pagination. This is typically - used for administrative overview or data export. + description: Fetches a list of events with support for pagination via limit + and offset. Data is retrieved directly from the database for consistency. + parameters: + - description: Maximum number of events to return (default 20) + in: query + name: limit + type: string + - description: Number of events to skip + in: query + name: offset + required: true + type: string produces: - application/json responses: "200": - description: Successful retrieval of full user table + description: Successful paginated list retrieval schema: allOf: - $ref: '#/definitions/utils.RespStatus' - properties: data: - $ref: '#/definitions/service_user.UserTableResponse' + items: + $ref: '#/definitions/data.EventIndexDoc' + type: array + type: object + "400": + description: Invalid Input (Missing offset or malformed parameters) + schema: + allOf: + - $ref: '#/definitions/utils.RespStatus' + - properties: + data: + type: object type: object "500": - description: Internal Server Error (Database Error) + description: Internal Server Error (Database query failed) schema: allOf: - $ref: '#/definitions/utils.RespStatus' @@ -671,9 +675,9 @@ paths: type: object security: - ApiKeyAuth: [] - summary: Get Full User Table + summary: List Events tags: - - User + - Event /user/info: get: consumes: @@ -751,7 +755,7 @@ paths: - properties: data: items: - $ref: '#/definitions/data.UserSearchDoc' + $ref: '#/definitions/data.UserIndexDoc' type: array type: object "400": diff --git a/justfile b/justfile index 79e84cf..ccd4b83 100644 --- a/justfile +++ b/justfile @@ -48,4 +48,4 @@ dev-client-cms: install-cms devenv up client-cms --verbose dev-back: clean install-back gen-back - devenv up backend postgres redis meilisearch lgtm --verbose + devenv up postgres redis meilisearch lgtm --verbose diff --git a/service/service_event/info.go b/service/service_event/get_event_info.go similarity index 78% rename from service/service_event/info.go rename to service/service_event/get_event_info.go index 06c7c1f..87bfd8d 100644 --- a/service/service_event/info.go +++ b/service/service_event/get_event_info.go @@ -10,27 +10,27 @@ import ( "github.com/google/uuid" ) -type InfoData struct { +type EventInfoData struct { EventId uuid.UUID `json:"event_id"` } -type InfoPayload struct { +type EventInfoPayload struct { Context context.Context - Data *InfoData + Data *EventInfoData } -type InfoResponse struct { +type EventInfoResponse struct { Name string `json:"name"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` } -type InfoResult struct { +type EventInfoResult struct { Common shared.CommonResult - Data *InfoResponse + Data *EventInfoResponse } -func (self *EventServiceImpl) Info(payload *InfoPayload) (result *InfoResult) { +func (self *EventServiceImpl) GetEventInfo(payload *EventInfoPayload) (result *EventInfoResult) { event, err := new(data.Event).GetEventById(payload.Context, payload.Data.EventId) if err != nil { exception := new(exception.Builder). @@ -42,7 +42,7 @@ func (self *EventServiceImpl) Info(payload *InfoPayload) (result *InfoResult) { SetError(err). Throw(payload.Context) - result = &InfoResult{ + result = &EventInfoResult{ Common: shared.CommonResult{ HttpCode: 404, Exception: exception, @@ -52,7 +52,7 @@ func (self *EventServiceImpl) Info(payload *InfoPayload) (result *InfoResult) { return } - result = &InfoResult{ + result = &EventInfoResult{ Common: shared.CommonResult{ HttpCode: 200, Exception: new(exception.Builder). @@ -63,7 +63,7 @@ func (self *EventServiceImpl) Info(payload *InfoPayload) (result *InfoResult) { SetOriginal(exception.CommonSuccess). Throw(payload.Context), }, - Data: &InfoResponse{ + Data: &EventInfoResponse{ Name: event.Name, StartTime: event.StartTime, EndTime: event.EndTime, diff --git a/service/service_event/list_events.go b/service/service_event/list_events.go new file mode 100644 index 0000000..ece5b08 --- /dev/null +++ b/service/service_event/list_events.go @@ -0,0 +1,131 @@ +package service_event + +import ( + "context" + "nixcn-cms/data" + "nixcn-cms/internal/exception" + "nixcn-cms/service/shared" + "strconv" +) + +type EventListPayload struct { + Context context.Context + Limit *string + Offset *string +} + +type EventListResult struct { + Common shared.CommonResult + Data *[]data.EventIndexDoc `json:"event_list"` +} + +func (self *EventServiceImpl) ListEvents(payload *EventListPayload) (result *EventListResult) { + var limit string + if payload.Limit == nil || *payload.Limit == "" { + limit = "20" + } else { + limit = *payload.Limit + } + + var offset string + if payload.Offset == nil || *payload.Offset == "" { + exc := new(exception.Builder). + SetStatus(exception.StatusUser). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeCommon). + SetOriginal(exception.CommonErrorInvalidInput). + SetError(nil). + Throw(payload.Context) + + return &EventListResult{ + Common: shared.CommonResult{ + HttpCode: 400, + Exception: exc, + }, + Data: nil, + } + } else { + offset = *payload.Offset + } + + limitNum, err := strconv.Atoi(limit) + if err != nil { + exc := new(exception.Builder). + SetStatus(exception.StatusUser). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeCommon). + SetOriginal(exception.CommonErrorInvalidInput). + SetError(err). + Throw(payload.Context) + + return &EventListResult{ + Common: shared.CommonResult{ + HttpCode: 400, + Exception: exc, + }, + Data: nil, + } + } + + offsetNum, err := strconv.Atoi(offset) + if err != nil { + exc := new(exception.Builder). + SetStatus(exception.StatusUser). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeCommon). + SetOriginal(exception.CommonErrorInvalidInput). + SetError(err). + Throw(payload.Context) + + return &EventListResult{ + Common: shared.CommonResult{ + HttpCode: 400, + Exception: exc, + }, + Data: nil, + } + } + + eventList, err := new(data.Event). + FastListEvents(payload.Context, int64(limitNum), int64(offsetNum)) + if err != nil { + exc := new(exception.Builder). + SetStatus(exception.StatusServer). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeSpecific). + SetOriginal(exception.EventListDatabaseFailed). + SetError(err). + Throw(payload.Context) + + return &EventListResult{ + Common: shared.CommonResult{ + HttpCode: 500, + Exception: exc, + }, + Data: nil, + } + } + + successExc := new(exception.Builder). + SetStatus(exception.StatusSuccess). + SetService(exception.ServiceEvent). + SetEndpoint(exception.EndpointEventServiceList). + SetType(exception.TypeCommon). + SetOriginal(exception.CommonSuccess). + SetError(nil). + Throw(payload.Context) + + result = &EventListResult{ + Common: shared.CommonResult{ + HttpCode: 200, + Exception: successExc, + }, + Data: eventList, + } + + return +} diff --git a/service/service_event/service.go b/service/service_event/service.go index 74bcf5a..6d46708 100644 --- a/service/service_event/service.go +++ b/service/service_event/service.go @@ -4,7 +4,8 @@ type EventService interface { Checkin(*CheckinPayload) *CheckinResult CheckinSubmit(*CheckinSubmitPayload) *CheckinSubmitResult CheckinQuery(*CheckinQueryPayload) *CheckinQueryResult - Info(*InfoPayload) *InfoResult + GetEventInfo(*EventInfoPayload) *EventInfoResult + ListEvents(*EventListPayload) *EventListResult } type EventServiceImpl struct{} diff --git a/service/service_user/list_user_full_table.go b/service/service_user/list_user_full_table.go deleted file mode 100644 index 31553ef..0000000 --- a/service/service_user/list_user_full_table.go +++ /dev/null @@ -1,69 +0,0 @@ -package service_user - -import ( - "context" - "nixcn-cms/data" - "nixcn-cms/internal/exception" - "nixcn-cms/service/shared" -) - -type UserTablePayload struct { - Context context.Context -} - -type UserTableResponse struct { - UserTable *[]data.User `json:"user_table"` -} - -type UserTableResult struct { - Common shared.CommonResult - Data *UserTableResponse -} - -// ListUserFullTable -func (self *UserServiceImpl) GetUserFullTable(payload *UserTablePayload) (result *UserTableResult) { - var err error - - userFullTable, err := new(data.User). - GetFullTable(payload.Context) - - if err != nil { - exception := new(exception.Builder). - SetStatus(exception.StatusServer). - SetService(exception.ServiceUser). - SetEndpoint(exception.EndpointUserServiceFull). - SetType(exception.TypeCommon). - SetOriginal(exception.CommonErrorDatabase). - SetError(err). - Throw(payload.Context) - - result = &UserTableResult{ - Common: shared.CommonResult{ - HttpCode: 500, - Exception: exception, - }, - Data: nil, - } - - return - } - - exception := new(exception.Builder). - SetStatus(exception.StatusServer). - SetService(exception.ServiceUser). - SetEndpoint(exception.EndpointUserServiceFull). - SetType(exception.TypeCommon). - SetOriginal(exception.CommonSuccess). - SetError(nil). - Throw(payload.Context) - - result = &UserTableResult{ - Common: shared.CommonResult{ - HttpCode: 200, - Exception: exception, - }, - Data: &UserTableResponse{userFullTable}, - } - - return -} diff --git a/service/service_user/list_users.go b/service/service_user/list_users.go index 4001cf4..3c210aa 100644 --- a/service/service_user/list_users.go +++ b/service/service_user/list_users.go @@ -16,16 +16,18 @@ type UserListPayload struct { type UserListResult struct { Common shared.CommonResult - Data *[]data.UserSearchDoc `json:"user_list"` + Data *[]data.UserIndexDoc `json:"user_list"` } func (self *UserServiceImpl) ListUsers(payload *UserListPayload) (result *UserListResult) { - var limit string + var limit string = *payload.Limit if payload.Limit == nil || *payload.Limit == "" { - limit = "0" + limit = "20" + } else { + limit = *payload.Limit } - var offset string + var offset string = *payload.Offset if payload.Offset == nil || *payload.Offset == "" { exception := new(exception.Builder). SetStatus(exception.StatusUser). @@ -50,7 +52,7 @@ func (self *UserServiceImpl) ListUsers(payload *UserListPayload) (result *UserLi } // Parse string to int64 - limitNum, err := strconv.ParseInt(limit, 10, 64) + limitNum, err := strconv.Atoi(limit) if err != nil { exception := new(exception.Builder). SetStatus(exception.StatusUser). @@ -72,7 +74,7 @@ func (self *UserServiceImpl) ListUsers(payload *UserListPayload) (result *UserLi return } - offsetNum, err := strconv.ParseInt(offset, 10, 64) + offsetNum, err := strconv.Atoi(offset) if err != nil { exception := new(exception.Builder). SetStatus(exception.StatusUser). @@ -103,7 +105,7 @@ func (self *UserServiceImpl) ListUsers(payload *UserListPayload) (result *UserLi SetService(exception.ServiceUser). SetEndpoint(exception.EndpointUserServiceList). SetType(exception.TypeSpecific). - SetOriginal(exception.UserListMeilisearchFailed). + SetOriginal(exception.UserListDatabaseFailed). SetError(err). Throw(payload.Context) diff --git a/service/service_user/service.go b/service/service_user/service.go index 5b39ed3..83960de 100644 --- a/service/service_user/service.go +++ b/service/service_user/service.go @@ -4,7 +4,6 @@ type UserService interface { GetUserInfo(*UserInfoPayload) *UserInfoResult UpdateUserInfo(*UserInfoPayload) *UserInfoResult ListUsers(*UserListPayload) *UserListResult - GetUserFullTable(*UserTablePayload) *UserTableResult CreateUser() }