diff --git a/api/swagger/docs.go b/api/swagger/docs.go index d071dd1e..5c604834 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -1357,6 +1357,186 @@ const docTemplate = `{ } } }, + "/organizations/{organizationId}/audits": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get Audits", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Get Audits", + "parameters": [ + { + "type": "string", + "description": "pageSize", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "pageNumber", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "sortColumn", + "name": "soertColumn", + "in": "query" + }, + { + "type": "string", + "description": "sortOrder", + "name": "sortOrder", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "filters", + "name": "filter", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "filters", + "name": "or", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetAuditsResponse" + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Create Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Create Audit", + "parameters": [ + { + "description": "create audit request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateAuditRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CreateAuditResponse" + } + } + } + } + }, + "/organizations/{organizationId}/audits/{auditId}": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Get Audit", + "parameters": [ + { + "type": "string", + "description": "auditId", + "name": "auditId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetAuditResponse" + } + } + } + }, + "delete": { + "security": [ + { + "JWT": [] + } + ], + "description": "Delete Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Delete Audit 'NOT IMPLEMENTED'", + "parameters": [ + { + "type": "string", + "description": "auditId", + "name": "auditId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/organizations/{organizationId}/cloud-accounts": { "get": { "security": [ @@ -5685,6 +5865,44 @@ const docTemplate = `{ "ApplicationType_KUBERNETES_DASHBOARD" ] }, + "domain.AuditResponse": { + "type": "object", + "properties": { + "clientIP": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.SimpleOrganizationResponse" + }, + "organizationId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/domain.SimpleUserResponse" + }, + "userId": { + "type": "string" + } + } + }, "domain.Axis": { "type": "object", "properties": { @@ -6337,6 +6555,12 @@ const docTemplate = `{ } } }, + "domain.CreateAuditRequest": { + "type": "object" + }, + "domain.CreateAuditResponse": { + "type": "object" + }, "domain.CreateBootstrapKubeconfigResponse": { "type": "object", "properties": { @@ -7006,6 +7230,28 @@ const docTemplate = `{ } } }, + "domain.GetAuditResponse": { + "type": "object", + "properties": { + "audit": { + "$ref": "#/definitions/domain.AuditResponse" + } + } + }, + "domain.GetAuditsResponse": { + "type": "object", + "properties": { + "audits": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.AuditResponse" + } + }, + "pagination": { + "$ref": "#/definitions/domain.PaginationResponse" + } + } + }, "domain.GetBootstrapKubeconfigResponse": { "type": "object", "properties": { @@ -8021,6 +8267,34 @@ const docTemplate = `{ } } }, + "domain.SimpleOrganizationResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "domain.SimpleRoleResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "domain.SimpleStackTemplateResponse": { "type": "object", "properties": { @@ -8064,6 +8338,9 @@ const docTemplate = `{ }, "name": { "type": "string" + }, + "role": { + "$ref": "#/definitions/domain.SimpleRoleResponse" } } }, diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 97c10867..6cd5537e 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -1351,6 +1351,186 @@ } } }, + "/organizations/{organizationId}/audits": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get Audits", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Get Audits", + "parameters": [ + { + "type": "string", + "description": "pageSize", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "pageNumber", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "sortColumn", + "name": "soertColumn", + "in": "query" + }, + { + "type": "string", + "description": "sortOrder", + "name": "sortOrder", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "filters", + "name": "filter", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "description": "filters", + "name": "or", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetAuditsResponse" + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Create Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Create Audit", + "parameters": [ + { + "description": "create audit request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateAuditRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CreateAuditResponse" + } + } + } + } + }, + "/organizations/{organizationId}/audits/{auditId}": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Get Audit", + "parameters": [ + { + "type": "string", + "description": "auditId", + "name": "auditId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetAuditResponse" + } + } + } + }, + "delete": { + "security": [ + { + "JWT": [] + } + ], + "description": "Delete Audit", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Audits" + ], + "summary": "Delete Audit 'NOT IMPLEMENTED'", + "parameters": [ + { + "type": "string", + "description": "auditId", + "name": "auditId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/organizations/{organizationId}/cloud-accounts": { "get": { "security": [ @@ -5679,6 +5859,44 @@ "ApplicationType_KUBERNETES_DASHBOARD" ] }, + "domain.AuditResponse": { + "type": "object", + "properties": { + "clientIP": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.SimpleOrganizationResponse" + }, + "organizationId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/domain.SimpleUserResponse" + }, + "userId": { + "type": "string" + } + } + }, "domain.Axis": { "type": "object", "properties": { @@ -6331,6 +6549,12 @@ } } }, + "domain.CreateAuditRequest": { + "type": "object" + }, + "domain.CreateAuditResponse": { + "type": "object" + }, "domain.CreateBootstrapKubeconfigResponse": { "type": "object", "properties": { @@ -7000,6 +7224,28 @@ } } }, + "domain.GetAuditResponse": { + "type": "object", + "properties": { + "audit": { + "$ref": "#/definitions/domain.AuditResponse" + } + } + }, + "domain.GetAuditsResponse": { + "type": "object", + "properties": { + "audits": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.AuditResponse" + } + }, + "pagination": { + "$ref": "#/definitions/domain.PaginationResponse" + } + } + }, "domain.GetBootstrapKubeconfigResponse": { "type": "object", "properties": { @@ -8015,6 +8261,34 @@ } } }, + "domain.SimpleOrganizationResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "domain.SimpleRoleResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "domain.SimpleStackTemplateResponse": { "type": "object", "properties": { @@ -8058,6 +8332,9 @@ }, "name": { "type": "string" + }, + "role": { + "$ref": "#/definitions/domain.SimpleRoleResponse" } } }, diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index f9abcd33..ed26d241 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -309,6 +309,31 @@ definitions: - ApplicationType_HORIZON - ApplicationType_JAEGER - ApplicationType_KUBERNETES_DASHBOARD + domain.AuditResponse: + properties: + clientIP: + type: string + createdAt: + type: string + description: + type: string + group: + type: string + id: + type: string + message: + type: string + organization: + $ref: '#/definitions/domain.SimpleOrganizationResponse' + organizationId: + type: string + updatedAt: + type: string + user: + $ref: '#/definitions/domain.SimpleUserResponse' + userId: + type: string + type: object domain.Axis: properties: data: @@ -750,6 +775,10 @@ definitions: metadata: type: string type: object + domain.CreateAuditRequest: + type: object + domain.CreateAuditResponse: + type: object domain.CreateBootstrapKubeconfigResponse: properties: kubeconfig: @@ -1202,6 +1231,20 @@ definitions: $ref: '#/definitions/domain.ApplicationResponse' type: array type: object + domain.GetAuditResponse: + properties: + audit: + $ref: '#/definitions/domain.AuditResponse' + type: object + domain.GetAuditsResponse: + properties: + audits: + items: + $ref: '#/definitions/domain.AuditResponse' + type: array + pagination: + $ref: '#/definitions/domain.PaginationResponse' + type: object domain.GetBootstrapKubeconfigResponse: properties: kubeconfig: @@ -1869,6 +1912,24 @@ definitions: organizationId: type: string type: object + domain.SimpleOrganizationResponse: + properties: + description: + type: string + id: + type: string + name: + type: string + type: object + domain.SimpleRoleResponse: + properties: + description: + type: string + id: + type: string + name: + type: string + type: object domain.SimpleStackTemplateResponse: properties: cloudService: @@ -1898,6 +1959,8 @@ definitions: type: string name: type: string + role: + $ref: '#/definitions/domain.SimpleRoleResponse' type: object domain.StackConfResponse: properties: @@ -3276,6 +3339,120 @@ paths: summary: Create alert action tags: - Alerts + /organizations/{organizationId}/audits: + get: + consumes: + - application/json + description: Get Audits + parameters: + - description: pageSize + in: query + name: limit + type: string + - description: pageNumber + in: query + name: page + type: string + - description: sortColumn + in: query + name: soertColumn + type: string + - description: sortOrder + in: query + name: sortOrder + type: string + - collectionFormat: csv + description: filters + in: query + items: + type: string + name: filter + type: array + - collectionFormat: csv + description: filters + in: query + items: + type: string + name: or + type: array + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.GetAuditsResponse' + security: + - JWT: [] + summary: Get Audits + tags: + - Audits + post: + consumes: + - application/json + description: Create Audit + parameters: + - description: create audit request + in: body + name: body + required: true + schema: + $ref: '#/definitions/domain.CreateAuditRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.CreateAuditResponse' + security: + - JWT: [] + summary: Create Audit + tags: + - Audits + /organizations/{organizationId}/audits/{auditId}: + delete: + consumes: + - application/json + description: Delete Audit + parameters: + - description: auditId + in: path + name: auditId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + security: + - JWT: [] + summary: Delete Audit 'NOT IMPLEMENTED' + tags: + - Audits + get: + consumes: + - application/json + description: Get Audit + parameters: + - description: auditId + in: path + name: auditId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.GetAuditResponse' + security: + - JWT: [] + summary: Get Audit + tags: + - Audits /organizations/{organizationId}/cloud-accounts: get: consumes: diff --git a/internal/database/database.go b/internal/database/database.go index 6777f915..7d3a295c 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -128,5 +128,10 @@ func migrateSchema(db *gorm.DB) error { return err } + // Audit + if err := db.AutoMigrate(&repository.Audit{}); err != nil { + return err + } + return nil } diff --git a/internal/delivery/api/endpoint.go b/internal/delivery/api/endpoint.go index fe167f60..36edf66a 100644 --- a/internal/delivery/api/endpoint.go +++ b/internal/delivery/api/endpoint.go @@ -151,6 +151,11 @@ const ( UnSetFavoriteProjectNamespace GetProjectKubeconfig GetProjectNamespaceK8sResources + + // Audit + GetAudits + GetAudit + DeleteAudit ) var ApiMap = map[Endpoint]EndpointInfo{ @@ -617,6 +622,18 @@ var ApiMap = map[Endpoint]EndpointInfo{ GetProjectNamespaceK8sResources: { Name: "GetProjectNamespaceK8sResources", Group: "Project", + }, + GetAudits: { + Name: "GetAudits", + Group: "Audit", + }, + GetAudit: { + Name: "GetAudit", + Group: "Audit", + }, + DeleteAudit: { + Name: "DeleteAudit", + Group: "Audit", }, } @@ -854,6 +871,12 @@ func (e Endpoint) String() string { return "GetProjectKubeconfig" case GetProjectNamespaceK8sResources: return "GetProjectNamespaceK8sResources" + case GetAudits: + return "GetAudits" + case GetAudit: + return "GetAudit" + case DeleteAudit: + return "DeleteAudit" default: return "" } @@ -1092,6 +1115,12 @@ func GetEndpoint(name string) Endpoint { return GetProjectKubeconfig case "GetProjectNamespaceK8sResources": return GetProjectNamespaceK8sResources + case "GetAudits": + return GetAudits + case "GetAudit": + return GetAudit + case "DeleteAudit": + return DeleteAudit default: return -1 } diff --git a/internal/delivery/http/audit.go b/internal/delivery/http/audit.go new file mode 100644 index 00000000..8cdb6924 --- /dev/null +++ b/internal/delivery/http/audit.go @@ -0,0 +1,145 @@ +package http + +import ( + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/gorilla/mux" + "github.com/openinfradev/tks-api/internal/pagination" + "github.com/openinfradev/tks-api/internal/serializer" + "github.com/openinfradev/tks-api/internal/usecase" + "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/httpErrors" + "github.com/openinfradev/tks-api/pkg/log" + "github.com/pkg/errors" +) + +type AuditHandler struct { + usecase usecase.IAuditUsecase +} + +func NewAuditHandler(h usecase.Usecase) *AuditHandler { + return &AuditHandler{ + usecase: h.Audit, + } +} + +// CreateAudit godoc +// @Tags Audits +// @Summary Create Audit +// @Description Create Audit +// @Accept json +// @Produce json +// @Param body body domain.CreateAuditRequest true "create audit request" +// @Success 200 {object} domain.CreateAuditResponse +// @Router /organizations/{organizationId}/audits [post] +// @Security JWT +func (h *AuditHandler) CreateAudit(w http.ResponseWriter, r *http.Request) { + ErrorJSON(w, r, fmt.Errorf("need implementation")) +} + +// GetAudit godoc +// @Tags Audits +// @Summary Get Audits +// @Description Get Audits +// @Accept json +// @Produce json +// @Param limit query string false "pageSize" +// @Param page query string false "pageNumber" +// @Param soertColumn query string false "sortColumn" +// @Param sortOrder query string false "sortOrder" +// @Param filter query []string false "filters" +// @Param or query []string false "filters" +// @Success 200 {object} domain.GetAuditsResponse +// @Router /organizations/{organizationId}/audits [get] +// @Security JWT +func (h *AuditHandler) GetAudits(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + organizationId, ok := vars["organizationId"] + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid organizationId"), "C_INVALID_ORGANIZATION_ID", "")) + return + } + + urlParams := r.URL.Query() + pg, err := pagination.NewPagination(&urlParams) + if err != nil { + ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", "")) + return + } + + audits, err := h.usecase.Fetch(r.Context(), organizationId, pg) + if err != nil { + ErrorJSON(w, r, err) + return + } + + var out domain.GetAuditsResponse + out.Audits = make([]domain.AuditResponse, len(audits)) + for i, audit := range audits { + if err := serializer.Map(audit, &out.Audits[i]); err != nil { + log.InfoWithContext(r.Context(), err) + } + } + + if out.Pagination, err = pg.Response(); err != nil { + log.InfoWithContext(r.Context(), err) + } + + ResponseJSON(w, r, http.StatusOK, out) +} + +// GetAudit godoc +// @Tags Audits +// @Summary Get Audit +// @Description Get Audit +// @Accept json +// @Produce json +// @Param auditId path string true "auditId" +// @Success 200 {object} domain.GetAuditResponse +// @Router /organizations/{organizationId}/audits/{auditId} [get] +// @Security JWT +func (h *AuditHandler) GetAudit(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strId, ok := vars["auditId"] + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid auditId"), "C_INVALID_AUDIT_ID", "")) + return + } + + auditId, err := uuid.Parse(strId) + if err != nil { + ErrorJSON(w, r, httpErrors.NewBadRequestError(errors.Wrap(err, "Failed to parse uuid %s"), "C_INVALID_AUDIT_ID", "")) + return + } + + audit, err := h.usecase.Get(r.Context(), auditId) + if err != nil { + ErrorJSON(w, r, err) + return + } + log.Info(audit) + + var out domain.GetAuditResponse + if err := serializer.Map(audit, &out.Audit); err != nil { + log.InfoWithContext(r.Context(), err) + } + + ResponseJSON(w, r, http.StatusOK, out) + +} + +// DeleteAudit godoc +// @Tags Audits +// @Summary Delete Audit 'NOT IMPLEMENTED' +// @Description Delete Audit +// @Accept json +// @Produce json +// @Param auditId path string true "auditId" +// @Success 200 {object} nil +// @Router /organizations/{organizationId}/audits/{auditId} [delete] +// @Security JWT +func (h *AuditHandler) DeleteAudit(w http.ResponseWriter, r *http.Request) { + ErrorJSON(w, r, fmt.Errorf("need implementation")) +} diff --git a/internal/delivery/http/auth.go b/internal/delivery/http/auth.go index 1b84dbde..07c89240 100644 --- a/internal/delivery/http/auth.go +++ b/internal/delivery/http/auth.go @@ -4,7 +4,9 @@ import ( "fmt" "net/http" + "github.com/google/uuid" "github.com/openinfradev/tks-api/internal" + "github.com/openinfradev/tks-api/internal/middleware/audit" "github.com/openinfradev/tks-api/internal/middleware/auth/request" "github.com/openinfradev/tks-api/internal/serializer" "github.com/openinfradev/tks-api/internal/usecase" @@ -27,12 +29,14 @@ type IAuthHandler interface { //Authenticate(next http.Handler) http.Handler } type AuthHandler struct { - usecase usecase.IAuthUsecase + usecase usecase.IAuthUsecase + auditUsecase usecase.IAuditUsecase } func NewAuthHandler(h usecase.Usecase) IAuthHandler { return &AuthHandler{ - usecase: h.Auth, + usecase: h.Auth, + auditUsecase: h.Audit, } } @@ -55,9 +59,28 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { user, err := h.usecase.Login(input.AccountId, input.Password, input.OrganizationId) if err != nil { + errorResponse, _ := httpErrors.ErrorResponse(err) + _, _ = h.auditUsecase.Create(r.Context(), domain.Audit{ + OrganizationId: input.OrganizationId, + Group: "Auth", + Message: fmt.Sprintf("[%s]님이 로그인에 실패하였습니다.", input.AccountId), + Description: errorResponse.Text(), + ClientIP: audit.GetClientIpAddress(w, r), + UserId: nil, + }) log.ErrorfWithContext(r.Context(), "error is :%s(%T)", err.Error(), err) ErrorJSON(w, r, err) return + } else { + userId, _ := uuid.Parse(user.ID) + _, _ = h.auditUsecase.Create(r.Context(), domain.Audit{ + OrganizationId: input.OrganizationId, + Group: "Auth", + Message: fmt.Sprintf("[%s]님이 로그인 하였습니다.", input.AccountId), + Description: "", + ClientIP: audit.GetClientIpAddress(w, r), + UserId: &userId, + }) } var cookies []*http.Cookie diff --git a/internal/delivery/http/handler.go b/internal/delivery/http/handler.go index 46cdf4db..680c338d 100644 --- a/internal/delivery/http/handler.go +++ b/internal/delivery/http/handler.go @@ -3,13 +3,11 @@ package http import ( "encoding/json" "errors" - "fmt" "io" "net/http" ut "github.com/go-playground/universal-translator" validator_ "github.com/go-playground/validator/v10" - "github.com/openinfradev/tks-api/internal/helper" "github.com/openinfradev/tks-api/internal/validator" "github.com/openinfradev/tks-api/pkg/httpErrors" "github.com/openinfradev/tks-api/pkg/log" @@ -41,13 +39,6 @@ func ResponseJSON(w http.ResponseWriter, r *http.Request, httpStatus int, data i w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(httpStatus) - responseStr := helper.ModelToJson(out) - if len(responseStr) > MAX_LOG_LEN { - log.InfoWithContext(r.Context(), fmt.Sprintf("[API_RESPONSE] [%s]", responseStr[:MAX_LOG_LEN-1])) - } else { - log.InfoWithContext(r.Context(), fmt.Sprintf("[API_RESPONSE] [%s]", responseStr)) - } - log.DebugWithContext(r.Context(), fmt.Sprintf("[API_RESPONSE] [%s]", responseStr)) if err := json.NewEncoder(w).Encode(out); err != nil { log.ErrorWithContext(r.Context(), err) } diff --git a/internal/middleware/audit/audit-map.go b/internal/middleware/audit/audit-map.go new file mode 100644 index 00000000..85a80d9d --- /dev/null +++ b/internal/middleware/audit/audit-map.go @@ -0,0 +1,67 @@ +package audit + +import ( + "bytes" + "encoding/json" + "fmt" + + internalApi "github.com/openinfradev/tks-api/internal/delivery/api" + "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/httpErrors" + "github.com/openinfradev/tks-api/pkg/log" +) + +type fnAudit = func(out *bytes.Buffer, in []byte, statusCode int) (message string, description string) + +var auditMap = map[internalApi.Endpoint]fnAudit{ + internalApi.CreateStack: func(out *bytes.Buffer, in []byte, statusCode int) (message string, description string) { + input := domain.CreateStackRequest{} + if err := json.Unmarshal(in, &input); err != nil { + log.Error(err) + } + + if isSuccess(statusCode) { + return fmt.Sprintf("스택 [%s]을 생성하였습니다.", input.Name), "" + } else { + return fmt.Sprintf("스택 [%s]을 생성하는데 실패하였습니다.", input.Name), errorText(out) + } + }, internalApi.CreateProject: func(out *bytes.Buffer, in []byte, statusCode int) (message string, description string) { + input := domain.CreateProjectRequest{} + if err := json.Unmarshal(in, &input); err != nil { + log.Error(err) + } + + if isSuccess(statusCode) { + return fmt.Sprintf("프로젝트 [%s]를 생성하였습니다.", input.Name), "" + } else { + return "프로젝트 [%s]를 생성하는데 실패하였습니다. ", errorText(out) + } + }, internalApi.CreateCloudAccount: func(out *bytes.Buffer, in []byte, statusCode int) (message string, description string) { + input := domain.CreateCloudAccountRequest{} + if err := json.Unmarshal(in, &input); err != nil { + log.Error(err) + } + + if isSuccess(statusCode) { + return fmt.Sprintf("클라우드 어카운트 [%s]를 생성하였습니다.", input.Name), "" + } else { + return "프로젝트 [%s]를 생성하는데 실패하였습니다. ", errorText(out) + } + }, +} + +func errorText(out *bytes.Buffer) string { + var e httpErrors.RestError + if err := json.NewDecoder(out).Decode(&e); err != nil { + log.Error(err) + return "" + } + return e.Text() +} + +func isSuccess(statusCode int) bool { + if statusCode >= 200 && statusCode < 300 { + return true + } + return false +} diff --git a/internal/middleware/audit/audit.go b/internal/middleware/audit/audit.go index 4b2058c5..55d61511 100644 --- a/internal/middleware/audit/audit.go +++ b/internal/middleware/audit/audit.go @@ -1,29 +1,91 @@ package audit import ( - "github.com/openinfradev/tks-api/internal/repository" + "bytes" + "io" + "net" "net/http" + + "github.com/gorilla/mux" + internalApi "github.com/openinfradev/tks-api/internal/delivery/api" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" + "github.com/openinfradev/tks-api/internal/middleware/logging" + "github.com/openinfradev/tks-api/internal/repository" + "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/log" ) type Interface interface { - WithAudit(handler http.Handler) http.Handler + WithAudit(endpoint internalApi.Endpoint, handler http.Handler) http.Handler } type defaultAudit struct { - repo repository.Repository + repo repository.IAuditRepository } func NewDefaultAudit(repo repository.Repository) *defaultAudit { return &defaultAudit{ - repo: repo, + repo: repo.Audit, } } -// TODO: implement audit logic -func (a *defaultAudit) WithAudit(handler http.Handler) http.Handler { +func (a *defaultAudit) WithAudit(endpoint internalApi.Endpoint, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: implement audit logic + user, ok := request.UserFrom(r.Context()) + if !ok { + log.Error("Invalid user token") + return + } + userId := user.GetUserId() + + requestBody := &bytes.Buffer{} + _, _ = io.Copy(requestBody, r.Body) + + lrw := logging.NewLoggingResponseWriter(w) + handler.ServeHTTP(lrw, r) + statusCode := lrw.GetStatusCode() + + vars := mux.Vars(r) + organizationId, ok := vars["organizationId"] + if !ok { + organizationId = user.GetOrganizationId() + } + + message, description := "", "" + if fn, ok := auditMap[endpoint]; ok { + body, err := io.ReadAll(requestBody) + if err != nil { + log.Error(err) + } + message, description = fn(lrw.GetBody(), body, statusCode) + + dto := domain.Audit{ + OrganizationId: organizationId, + Group: internalApi.ApiMap[endpoint].Group, + Message: message, + Description: description, + ClientIP: GetClientIpAddress(w, r), + UserId: &userId, + } + if _, err := a.repo.Create(dto); err != nil { + log.Error(err) + } + } - handler.ServeHTTP(w, r) }) } + +var X_FORWARDED_FOR = "X-Forwarded-For" + +func GetClientIpAddress(w http.ResponseWriter, r *http.Request) string { + xforward := r.Header.Get(X_FORWARDED_FOR) + if xforward != "" { + return xforward + } + + clientAddr, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return clientAddr + } + return "" +} diff --git a/internal/middleware/auth/request/context.go b/internal/middleware/auth/request/context.go index 61efecfa..d1b8c4c8 100644 --- a/internal/middleware/auth/request/context.go +++ b/internal/middleware/auth/request/context.go @@ -2,6 +2,7 @@ package request import ( "context" + internalApi "github.com/openinfradev/tks-api/internal/delivery/api" "github.com/openinfradev/tks-api/internal/middleware/auth/user" ) diff --git a/internal/middleware/logging/logging.go b/internal/middleware/logging/logging.go new file mode 100644 index 00000000..3bda0874 --- /dev/null +++ b/internal/middleware/logging/logging.go @@ -0,0 +1,35 @@ +package logging + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "github.com/google/uuid" + "github.com/openinfradev/tks-api/internal" + "github.com/openinfradev/tks-api/pkg/log" +) + +func LoggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + r = r.WithContext(context.WithValue(ctx, internal.ContextKeyRequestID, uuid.New().String())) + + log.InfoWithContext(r.Context(), fmt.Sprintf("***** START [%s %s] ***** ", r.Method, r.RequestURI)) + + body, err := io.ReadAll(r.Body) + if err == nil { + log.InfoWithContext(r.Context(), fmt.Sprintf("REQUEST BODY : %s", bytes.NewBuffer(body).String())) + } + r.Body = io.NopCloser(bytes.NewBuffer(body)) + lrw := NewLoggingResponseWriter(w) + + next.ServeHTTP(lrw, r) + + statusCode := lrw.GetStatusCode() + log.InfofWithContext(r.Context(), "[API_RESPONSE] [%d][%s][%s]", statusCode, http.StatusText(statusCode), lrw.GetBody().String()) + log.InfofWithContext(r.Context(), "***** END [%s %s] *****", r.Method, r.RequestURI) + }) +} diff --git a/internal/middleware/logging/respose-writer.go b/internal/middleware/logging/respose-writer.go new file mode 100644 index 00000000..97ef7d23 --- /dev/null +++ b/internal/middleware/logging/respose-writer.go @@ -0,0 +1,35 @@ +package logging + +import ( + "bytes" + "net/http" +) + +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int + body bytes.Buffer +} + +func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { + var buf bytes.Buffer + return &loggingResponseWriter{w, http.StatusOK, buf} +} + +func (lrw *loggingResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + +func (lrw *loggingResponseWriter) Write(buf []byte) (int, error) { + lrw.body.Write(buf) + return lrw.ResponseWriter.Write(buf) +} + +func (lrw *loggingResponseWriter) GetBody() *bytes.Buffer { + return &lrw.body +} + +func (lrw *loggingResponseWriter) GetStatusCode() int { + return lrw.statusCode +} diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 86e8899c..554afc2a 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -1,12 +1,13 @@ package middleware import ( + "net/http" + internalApi "github.com/openinfradev/tks-api/internal/delivery/api" "github.com/openinfradev/tks-api/internal/middleware/audit" "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator" "github.com/openinfradev/tks-api/internal/middleware/auth/authorizer" "github.com/openinfradev/tks-api/internal/middleware/auth/requestRecoder" - "net/http" ) type Middleware struct { @@ -30,24 +31,23 @@ func NewMiddleware(authenticator authenticator.Interface, } func (m *Middleware) Handle(endpoint internalApi.Endpoint, handle http.Handler) http.Handler { - // pre-handler preHandler := m.authorizer.WithAuthorization(handle) // TODO: this is a temporary solution. check if this is the right place to put audit middleware - preHandler = m.audit.WithAudit(preHandler) + preHandler = m.audit.WithAudit(endpoint, preHandler) preHandler = m.requestRecoder.WithRequestRecoder(endpoint, preHandler) preHandler = m.authenticator.WithAuthentication(preHandler) // post-handler - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - // append post-handler below // TODO: this is a temporary solution. check if this is the right place to put audit middleware - postHandler := m.audit.WithAudit(emptyHandler) + + // emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + // postHandler := m.audit.WithAudit(endpoint, emptyHandler) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { preHandler.ServeHTTP(w, r) - postHandler.ServeHTTP(w, r) + // postHandler.ServeHTTP(w, r) }) } diff --git a/internal/pagination/pagination.go b/internal/pagination/pagination.go index d7547d70..69975e9c 100644 --- a/internal/pagination/pagination.go +++ b/internal/pagination/pagination.go @@ -21,7 +21,9 @@ const SORT_ORDER = "sortOrder" const PAGE_NUMBER = "pageNumber" const PAGE_SIZE = "pageSize" const FILTER = "filter" +const FILTER_ARRAY = "filter[]" const OR = "or" +const OR_ARRAY = "or[]" const COMBINED_FILTER = "combinedFilter" var DEFAULT_LIMIT = 10 @@ -188,7 +190,7 @@ func NewPagination(urlParams *url.Values) (*Pagination, error) { return nil, fmt.Errorf("Invalid query string : combinedFilter ") } } - case FILTER, OR: + case FILTER, FILTER_ARRAY, OR, OR_ARRAY: for _, filterValue := range value { arr := strings.Split(filterValue, "|") @@ -209,7 +211,7 @@ func NewPagination(urlParams *url.Values) (*Pagination, error) { } or := false - if key == OR { + if key == OR || key == OR_ARRAY { or = true } diff --git a/internal/repository/audit.go b/internal/repository/audit.go new file mode 100644 index 00000000..bb0f97a2 --- /dev/null +++ b/internal/repository/audit.go @@ -0,0 +1,112 @@ +package repository + +import ( + "fmt" + + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" + + "github.com/openinfradev/tks-api/internal/pagination" + "github.com/openinfradev/tks-api/internal/serializer" + "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/log" +) + +// Interfaces +type IAuditRepository interface { + Get(auditId uuid.UUID) (domain.Audit, error) + Fetch(organizationId string, pg *pagination.Pagination) ([]domain.Audit, error) + Create(dto domain.Audit) (auditId uuid.UUID, err error) + Delete(auditId uuid.UUID) (err error) +} + +type AuditRepository struct { + db *gorm.DB +} + +func NewAuditRepository(db *gorm.DB) IAuditRepository { + return &AuditRepository{ + db: db, + } +} + +// Models +type Audit struct { + gorm.Model + + ID uuid.UUID `gorm:"primarykey"` + OrganizationId string + Organization Organization `gorm:"foreignKey:OrganizationId"` + Group string + Message string + Description string + ClientIP string + UserId *uuid.UUID `gorm:"type:uuid"` + User User `gorm:"foreignKey:UserId"` +} + +func (c *Audit) BeforeCreate(tx *gorm.DB) (err error) { + c.ID = uuid.New() + return nil +} + +// Logics +func (r *AuditRepository) Get(auditId uuid.UUID) (out domain.Audit, err error) { + var audit Audit + res := r.db.Preload(clause.Associations).First(&audit, "id = ?", auditId) + if res.Error != nil { + return + } + out = reflectAudit(audit) + return +} + +func (r *AuditRepository) Fetch(organizationId string, pg *pagination.Pagination) (out []domain.Audit, err error) { + var audits []Audit + + if pg == nil { + pg = pagination.NewDefaultPagination() + } + + db := r.db.Model(&Audit{}).Preload(clause.Associations).Where("organization_id = ?", organizationId) + _, res := pg.Fetch(db, &audits) + if res.Error != nil { + return nil, res.Error + } + + for _, audit := range audits { + out = append(out, reflectAudit(audit)) + } + + return +} + +func (r *AuditRepository) Create(dto domain.Audit) (auditId uuid.UUID, err error) { + audit := Audit{ + OrganizationId: dto.OrganizationId, + Group: dto.Group, + Message: dto.Message, + Description: dto.Description, + ClientIP: dto.ClientIP, + UserId: dto.UserId} + res := r.db.Create(&audit) + if res.Error != nil { + return uuid.Nil, res.Error + } + return audit.ID, nil +} + +func (r *AuditRepository) Delete(auditId uuid.UUID) (err error) { + return fmt.Errorf("to be implemented") +} + +func reflectAudit(audit Audit) (out domain.Audit) { + if err := serializer.Map(audit.Model, &out); err != nil { + log.Error(err) + } + if err := serializer.Map(audit, &out); err != nil { + log.Error(err) + } + return +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 46c56cef..09ab4035 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -22,6 +22,7 @@ type Repository struct { StackTemplate IStackTemplateRepository Alert IAlertRepository Project IProjectRepository + Audit IAuditRepository } func CombinedGormFilter(table string, filters []pagination.Filter, combinedFilter pagination.CombinedFilter) FilterFunc { diff --git a/internal/repository/stack-template.go b/internal/repository/stack-template.go index dd7be17f..54f52b79 100644 --- a/internal/repository/stack-template.go +++ b/internal/repository/stack-template.go @@ -6,7 +6,6 @@ import ( "gorm.io/gorm" "gorm.io/gorm/clause" - "github.com/openinfradev/tks-api/internal/helper" "github.com/openinfradev/tks-api/internal/pagination" "github.com/openinfradev/tks-api/internal/serializer" "github.com/openinfradev/tks-api/pkg/domain" @@ -97,7 +96,7 @@ func (r *StackTemplateRepository) Fetch(pg *pagination.Pagination) (out []domain */ // paginator, res := filter.Scope(r.db.Order("kube_type DESC,template_type ASC"), pg.GetPaginationRequest(), &stackTemplates) - paginator, res := pg.Fetch(r.db, &stackTemplates) + _, res := pg.Fetch(r.db, &stackTemplates) if res.Error != nil { return nil, res.Error } @@ -106,9 +105,6 @@ func (r *StackTemplateRepository) Fetch(pg *pagination.Pagination) (out []domain out = append(out, reflectStackTemplate(stackTemplate)) } - log.Info(helper.ModelToJson(paginator.Total)) - //log.Info(helper.ModelToJson(stackTemplates)) - return } diff --git a/internal/route/route.go b/internal/route/route.go index a12b209c..cdb36272 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -1,10 +1,6 @@ package route import ( - "bytes" - "context" - "fmt" - "io" "net/http" "time" @@ -12,7 +8,6 @@ import ( "github.com/openinfradev/tks-api/internal/middleware/audit" "github.com/openinfradev/tks-api/internal/middleware/auth/requestRecoder" - "github.com/google/uuid" "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/openinfradev/tks-api/internal" @@ -22,10 +17,10 @@ import ( "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator" authKeycloak "github.com/openinfradev/tks-api/internal/middleware/auth/authenticator/keycloak" "github.com/openinfradev/tks-api/internal/middleware/auth/authorizer" + "github.com/openinfradev/tks-api/internal/middleware/logging" "github.com/openinfradev/tks-api/internal/repository" "github.com/openinfradev/tks-api/internal/usecase" argowf "github.com/openinfradev/tks-api/pkg/argo-client" - "github.com/openinfradev/tks-api/pkg/log" gcache "github.com/patrickmn/go-cache" "github.com/swaggo/http-swagger" "gorm.io/gorm" @@ -40,16 +35,6 @@ var ( SYSTEM_API_PREFIX = internal.SYSTEM_API_PREFIX ) -type StatusRecorder struct { - http.ResponseWriter - Status int -} - -func (r *StatusRecorder) WriteHeader(status int) { - r.Status = status - r.ResponseWriter.WriteHeader(status) -} - func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloak, asset http.Handler) http.Handler { r := mux.NewRouter() @@ -66,6 +51,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa StackTemplate: repository.NewStackTemplateRepository(db), Alert: repository.NewAlertRepository(db), Project: repository.NewProjectRepository(db), + Audit: repository.NewAuditRepository(db), } usecaseFactory := usecase.Usecase{ @@ -89,7 +75,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa requestRecoder.NewDefaultRequestRecoder(), audit.NewDefaultAudit(repoFactory)) - r.Use(loggingMiddleware) + r.Use(logging.LoggingMiddleware) // [TODO] Transaction //r.Use(transactionMiddleware(db)) @@ -239,6 +225,11 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/namespaces/{projectNamespace}/stacks/{stackId}", customMiddleware.Handle(internalApi.DeleteProjectNamespace, http.HandlerFunc(projectHandler.DeleteProjectNamespace))).Methods(http.MethodDelete) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/kubeconfig", customMiddleware.Handle(internalApi.GetProjectKubeconfig, http.HandlerFunc(projectHandler.GetProjectKubeconfig))).Methods(http.MethodGet) + auditHandler := delivery.NewAuditHandler(usecaseFactory) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/audits", customMiddleware.Handle(internalApi.GetAudits, http.HandlerFunc(auditHandler.GetAudits))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/audits/{auditId}", customMiddleware.Handle(internalApi.GetAudit, http.HandlerFunc(auditHandler.GetAudit))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/audits/{auditId}", customMiddleware.Handle(internalApi.DeleteAudit, http.HandlerFunc(auditHandler.DeleteAudit))).Methods(http.MethodDelete) + r.HandleFunc(API_PREFIX+API_VERSION+"/alerttest", alertHandler.CreateAlert).Methods(http.MethodPost) // assets r.PathPrefix("/api/").HandlerFunc(http.NotFound) @@ -254,25 +245,6 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa return handlers.CORS(credentials, headersOk, originsOk, methodsOk)(r) } -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - r = r.WithContext(context.WithValue(ctx, internal.ContextKeyRequestID, uuid.New().String())) - - log.InfoWithContext(r.Context(), fmt.Sprintf("***** START [%s %s] ***** ", r.Method, r.RequestURI)) - - body, err := io.ReadAll(r.Body) - if err == nil { - log.InfoWithContext(r.Context(), fmt.Sprintf("REQUEST BODY : %s", bytes.NewBuffer(body).String())) - } - r.Body = io.NopCloser(bytes.NewBuffer(body)) - - next.ServeHTTP(w, r) - - log.InfofWithContext(r.Context(), "***** END [%s %s] *****", r.Method, r.RequestURI) - }) -} - /* func transactionMiddleware(db *gorm.DB) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { diff --git a/internal/usecase/audit.go b/internal/usecase/audit.go new file mode 100644 index 00000000..7461cf05 --- /dev/null +++ b/internal/usecase/audit.go @@ -0,0 +1,68 @@ +package usecase + +import ( + "context" + + "github.com/google/uuid" + "github.com/openinfradev/tks-api/internal/middleware/auth/request" + "github.com/openinfradev/tks-api/internal/pagination" + "github.com/openinfradev/tks-api/internal/repository" + "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/httpErrors" +) + +type IAuditUsecase interface { + Get(ctx context.Context, auditId uuid.UUID) (domain.Audit, error) + Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Audit, error) + Create(ctx context.Context, dto domain.Audit) (auditId uuid.UUID, err error) + Delete(ctx context.Context, dto domain.Audit) error +} + +type AuditUsecase struct { + repo repository.IAuditRepository +} + +func NewAuditUsecase(r repository.Repository) IAuditUsecase { + return &AuditUsecase{ + repo: r.Audit, + } +} + +func (u *AuditUsecase) Create(ctx context.Context, dto domain.Audit) (auditId uuid.UUID, err error) { + if dto.UserId == nil { + user, ok := request.UserFrom(ctx) + if ok { + userId := user.GetUserId() + dto.UserId = &userId + } + } + auditId, err = u.repo.Create(dto) + if err != nil { + return uuid.Nil, httpErrors.NewInternalServerError(err, "", "") + } + return auditId, nil +} + +func (u *AuditUsecase) Get(ctx context.Context, auditId uuid.UUID) (res domain.Audit, err error) { + res, err = u.repo.Get(auditId) + if err != nil { + return domain.Audit{}, err + } + return +} + +func (u *AuditUsecase) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) (audits []domain.Audit, err error) { + audits, err = u.repo.Fetch(organizationId, pg) + if err != nil { + return nil, err + } + return +} + +func (u *AuditUsecase) Delete(ctx context.Context, dto domain.Audit) (err error) { + err = u.repo.Delete(dto.ID) + if err != nil { + return httpErrors.NewNotFoundError(err, "", "") + } + return nil +} diff --git a/internal/usecase/usecase.go b/internal/usecase/usecase.go index b8838933..66111e99 100644 --- a/internal/usecase/usecase.go +++ b/internal/usecase/usecase.go @@ -13,4 +13,5 @@ type Usecase struct { Alert IAlertUsecase Stack IStackUsecase Project IProjectUsecase + Audit IAuditUsecase } diff --git a/pkg/domain/audit.go b/pkg/domain/audit.go new file mode 100644 index 00000000..9599ccae --- /dev/null +++ b/pkg/domain/audit.go @@ -0,0 +1,49 @@ +package domain + +import ( + "time" + + "github.com/google/uuid" +) + +// 내부 +type Audit struct { + ID uuid.UUID + OrganizationId string + Organization Organization + Group string + Message string + Description string + ClientIP string + UserId *uuid.UUID + User User + CreatedAt time.Time + UpdatedAt time.Time +} + +type AuditResponse struct { + ID string `json:"id"` + OrganizationId string `json:"organizationId"` + Organization SimpleOrganizationResponse `json:"organization"` + Description string `json:"description"` + Group string `json:"group"` + Message string `json:"message"` + ClientIP string `json:"clientIP"` + UserId string `json:"userId"` + User SimpleUserResponse `json:"user"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type CreateAuditRequest struct { +} +type CreateAuditResponse struct { +} + +type GetAuditResponse struct { + Audit AuditResponse `json:"audit"` +} +type GetAuditsResponse struct { + Audits []AuditResponse `json:"audits"` + Pagination PaginationResponse `json:"pagination"` +} diff --git a/pkg/domain/organization.go b/pkg/domain/organization.go index 6e3706da..31667dc3 100644 --- a/pkg/domain/organization.go +++ b/pkg/domain/organization.go @@ -61,6 +61,12 @@ type Organization = struct { UpdatedAt time.Time `json:"updatedAt"` } +type SimpleOrganizationResponse = struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} + type CreateOrganizationRequest struct { Name string `json:"name" validate:"required,name"` Description string `json:"description" validate:"omitempty,min=0,max=100"` diff --git a/pkg/domain/user.go b/pkg/domain/user.go index fbea7635..0e79216f 100644 --- a/pkg/domain/user.go +++ b/pkg/domain/user.go @@ -32,6 +32,12 @@ type Role = struct { UpdatedAt time.Time `json:"updatedAt"` } +type SimpleRoleResponse = struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} + type Policy = struct { ID string `json:"id"` Name string `json:"name"` @@ -59,9 +65,10 @@ type CreateUserRequest struct { } type SimpleUserResponse struct { - ID string `json:"id"` - AccountId string `json:"accountId"` - Name string `json:"name"` + ID string `json:"id"` + AccountId string `json:"accountId"` + Name string `json:"name"` + Role SimpleRoleResponse `json:"role"` } type CreateUserResponse struct { diff --git a/pkg/httpErrors/errorCode.go b/pkg/httpErrors/errorCode.go index b6c91281..b43f22f8 100644 --- a/pkg/httpErrors/errorCode.go +++ b/pkg/httpErrors/errorCode.go @@ -16,6 +16,7 @@ var errorMap = map[ErrorCode]string{ "C_INVALID_ASA_ID": "유효하지 않은 앱서빙앱 아이디입니다. 앱서빙앱 아이디를 확인하세요.", "C_INVALID_ASA_TASK_ID": "유효하지 않은 테스크 아이디입니다. 테스크 아이디를 확인하세요.", "C_INVALID_CLOUD_SERVICE": "유효하지 않은 클라우드서비스입니다.", + "C_INVALID_AUDIT_ID": "유효하지 않은 로그 아이디입니다. 로그 아이디를 확인하세요.", "C_FAILED_TO_CALL_WORKFLOW": "워크플로우 호출에 실패했습니다.", // Auth