package service import ( "context" "encoding/json" "errors" "fmt" "log/slog" "time" "github.com/SecurityBrewery/catalyst/app/auth" "github.com/SecurityBrewery/catalyst/app/auth/password" "github.com/SecurityBrewery/catalyst/app/database" "github.com/SecurityBrewery/catalyst/app/database/sqlc" "github.com/SecurityBrewery/catalyst/app/hook" "github.com/SecurityBrewery/catalyst/app/openapi" "github.com/SecurityBrewery/catalyst/app/pointer" "github.com/SecurityBrewery/catalyst/app/reaction/schedule" "github.com/SecurityBrewery/catalyst/app/settings" "github.com/SecurityBrewery/catalyst/app/upload" ) const ( defaultLimit = 100 defaultOffset = 0 ) var _ openapi.StrictServerInterface = (*Service)(nil) type Service struct { queries *sqlc.Queries hooks *hook.Hooks uploader *upload.Uploader scheduler *schedule.Scheduler } func New(queries *sqlc.Queries, hooks *hook.Hooks, uploader *upload.Uploader, scheduler *schedule.Scheduler) *Service { return &Service{ queries: queries, hooks: hooks, uploader: uploader, scheduler: scheduler, } } func (s *Service) ListComments(ctx context.Context, request openapi.ListCommentsRequestObject) (openapi.ListCommentsResponseObject, error) { comments, err := s.queries.ListComments(ctx, sqlc.ListCommentsParams{ Ticket: toString(request.Params.Ticket, ""), Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.ExtendedComment, 0, len(comments)) for _, comment := range comments { response = append(response, openapi.ExtendedComment{ Author: comment.Author, Created: comment.Created, Id: comment.ID, Message: comment.Message, Ticket: comment.Ticket, Updated: comment.Updated, AuthorName: pointer.Dereference(comment.AuthorName), }) } totalCount := 0 if len(comments) > 0 { totalCount = int(comments[0].TotalCount) } s.hooks.OnRecordsListRequest.Publish(ctx, database.CommentsTable.ID, response) return openapi.ListComments200JSONResponse{ Body: response, Headers: openapi.ListComments200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateComment(ctx context.Context, request openapi.CreateCommentRequestObject) (openapi.CreateCommentResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.CommentsTable.ID, request.Body) comment, err := s.queries.CreateComment(ctx, sqlc.CreateCommentParams{ Author: request.Body.Author, Message: request.Body.Message, Ticket: request.Body.Ticket, }) if err != nil { return nil, err } response := openapi.Comment{ Author: comment.Author, Created: comment.Created, Id: comment.ID, Message: comment.Message, Ticket: comment.Ticket, Updated: comment.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.CommentsTable.ID, response) return openapi.CreateComment200JSONResponse(response), nil } func (s *Service) DeleteComment(ctx context.Context, request openapi.DeleteCommentRequestObject) (openapi.DeleteCommentResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.CommentsTable.ID, request.Id) err := s.queries.DeleteComment(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.CommentsTable.ID, request.Id) return openapi.DeleteComment204Response{}, nil } func (s *Service) GetComment(ctx context.Context, request openapi.GetCommentRequestObject) (openapi.GetCommentResponseObject, error) { comment, err := s.queries.GetComment(ctx, request.Id) if err != nil { return nil, err } response := openapi.ExtendedComment{ Author: comment.Author, AuthorName: pointer.Dereference(comment.AuthorName), Created: comment.Created, Id: comment.ID, Message: comment.Message, Ticket: comment.Ticket, Updated: comment.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.CommentsTable.ID, response) return openapi.GetComment200JSONResponse(response), nil } func (s *Service) UpdateComment(ctx context.Context, request openapi.UpdateCommentRequestObject) (openapi.UpdateCommentResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.CommentsTable.ID, request.Body) comment, err := s.queries.UpdateComment(ctx, sqlc.UpdateCommentParams{ Message: request.Body.Message, ID: request.Id, }) if err != nil { return nil, err } response := openapi.Comment{ Author: comment.Author, Created: comment.Created, Id: comment.ID, Message: comment.Message, Ticket: comment.Ticket, Updated: comment.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.CommentsTable.ID, response) return openapi.UpdateComment200JSONResponse(response), nil } func (s *Service) GetDashboardCounts(ctx context.Context, _ openapi.GetDashboardCountsRequestObject) (openapi.GetDashboardCountsResponseObject, error) { counts, err := s.queries.GetDashboardCounts(ctx) if err != nil { return nil, err } response := make([]openapi.DashboardCounts, 0, len(counts)) for _, count := range counts { response = append(response, openapi.DashboardCounts{ Id: count.ID, Count: int(count.Count), }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.DashboardCountsTable.ID, response) return openapi.GetDashboardCounts200JSONResponse(response), nil } func (s *Service) ListFiles(ctx context.Context, request openapi.ListFilesRequestObject) (openapi.ListFilesResponseObject, error) { files, err := s.queries.ListFiles(ctx, sqlc.ListFilesParams{ Ticket: toString(request.Params.Ticket, ""), Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.File, 0, len(files)) for _, file := range files { response = append(response, openapi.File{ Created: file.Created, Id: file.ID, Name: file.Name, Size: file.Size, Ticket: file.Ticket, Updated: file.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.FilesTable.ID, response) totalCount := 0 if len(files) > 0 { totalCount = int(files[0].TotalCount) } return openapi.ListFiles200JSONResponse{ Body: response, Headers: openapi.ListFiles200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateFile(ctx context.Context, request openapi.CreateFileRequestObject) (openapi.CreateFileResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.FilesTable.ID, request.Body) id := database.GenerateID("b") uniqName, err := s.uploader.CreateFile(id, request.Body.Name, []byte(request.Body.Blob)) if err != nil { return nil, err } file, err := s.queries.InsertFile(ctx, sqlc.InsertFileParams{ ID: id, Name: request.Body.Name, Blob: uniqName, Size: float64(len(request.Body.Blob)), Ticket: request.Body.Ticket, Created: time.Now().UTC(), Updated: time.Now().UTC(), }) if err != nil { return nil, err } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.FilesTable.ID, file) return openapi.CreateFile200JSONResponse(openapi.File{ Created: file.Created, Id: file.ID, Name: file.Name, Size: file.Size, Ticket: file.Ticket, Updated: file.Updated, }), nil } func (s *Service) DeleteFile(ctx context.Context, request openapi.DeleteFileRequestObject) (openapi.DeleteFileResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.FilesTable.ID, request.Id) f, err := s.queries.GetFile(ctx, request.Id) if err != nil { return nil, err } if err := s.uploader.DeleteFile(f.ID, f.Blob); err != nil { return nil, fmt.Errorf("failed to delete file from uploader: %w", err) } if err := s.queries.DeleteFile(ctx, request.Id); err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.FilesTable.ID, request.Id) return openapi.DeleteFile204Response{}, nil } func (s *Service) GetFile(ctx context.Context, request openapi.GetFileRequestObject) (openapi.GetFileResponseObject, error) { file, err := s.queries.GetFile(ctx, request.Id) if err != nil { return nil, err } response := openapi.File{ Created: file.Created, Id: file.ID, Name: file.Name, Size: file.Size, Ticket: file.Ticket, Updated: file.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.FilesTable.ID, response) return openapi.GetFile200JSONResponse(response), nil } func (s *Service) ListLinks(ctx context.Context, request openapi.ListLinksRequestObject) (openapi.ListLinksResponseObject, error) { links, err := s.queries.ListLinks(ctx, sqlc.ListLinksParams{ Ticket: toString(request.Params.Ticket, ""), Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.Link, 0, len(links)) for _, link := range links { response = append(response, openapi.Link{ Id: link.ID, Name: link.Name, Created: link.Created, Updated: link.Updated, Url: link.Url, Ticket: link.Ticket, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.LinksTable.ID, response) totalCount := 0 if len(links) > 0 { totalCount = int(links[0].TotalCount) } return openapi.ListLinks200JSONResponse{ Body: response, Headers: openapi.ListLinks200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) DownloadFile(ctx context.Context, request openapi.DownloadFileRequestObject) (openapi.DownloadFileResponseObject, error) { file, err := s.queries.GetFile(ctx, request.Id) if err != nil { return nil, err } f, contentType, size, err := s.uploader.File(file.ID, file.Blob) if err != nil { return nil, fmt.Errorf("failed to get file from uploader: %w", err) } return openapi.DownloadFile200ApplicationoctetStreamResponse{ Body: f, ContentLength: size, Headers: openapi.DownloadFile200ResponseHeaders{ ContentDisposition: "attachment; filename=\"" + file.Name + "\"", ContentType: contentType, }, }, nil } func (s *Service) CreateLink(ctx context.Context, request openapi.CreateLinkRequestObject) (openapi.CreateLinkResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.LinksTable.ID, request.Body) link, err := s.queries.CreateLink(ctx, sqlc.CreateLinkParams{ Name: request.Body.Name, Url: request.Body.Url, Ticket: request.Body.Ticket, }) if err != nil { return nil, err } response := openapi.Link{ Created: link.Created, Id: link.ID, Name: link.Name, Ticket: link.Ticket, Updated: link.Updated, Url: link.Url, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.LinksTable.ID, response) return openapi.CreateLink200JSONResponse(response), nil } func (s *Service) DeleteLink(ctx context.Context, request openapi.DeleteLinkRequestObject) (openapi.DeleteLinkResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.LinksTable.ID, request.Id) err := s.queries.DeleteLink(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.LinksTable.ID, request.Id) return openapi.DeleteLink204Response{}, nil } func (s *Service) GetLink(ctx context.Context, request openapi.GetLinkRequestObject) (openapi.GetLinkResponseObject, error) { link, err := s.queries.GetLink(ctx, request.Id) if err != nil { return nil, err } response := openapi.Link{ Id: link.ID, Name: link.Name, Created: link.Created, Updated: link.Updated, Url: link.Url, Ticket: link.Ticket, } s.hooks.OnRecordViewRequest.Publish(ctx, database.LinksTable.ID, response) return openapi.GetLink200JSONResponse(response), nil } func (s *Service) UpdateLink(ctx context.Context, request openapi.UpdateLinkRequestObject) (openapi.UpdateLinkResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.LinksTable.ID, request.Body) link, err := s.queries.UpdateLink(ctx, sqlc.UpdateLinkParams{ ID: request.Id, Name: request.Body.Name, Url: request.Body.Url, }) if err != nil { return nil, err } response := openapi.Link{ Created: link.Created, Id: link.ID, Name: link.Name, Ticket: link.Ticket, Updated: link.Updated, Url: link.Url, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.LinksTable.ID, response) return openapi.UpdateLink200JSONResponse(response), nil } func (s *Service) ListReactions(ctx context.Context, request openapi.ListReactionsRequestObject) (openapi.ListReactionsResponseObject, error) { reactions, err := s.queries.ListReactions(ctx, sqlc.ListReactionsParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.Reaction, 0, len(reactions)) for _, reaction := range reactions { response = append(response, openapi.Reaction{ Action: reaction.Action, Actiondata: unmarshal(reaction.Actiondata), Created: reaction.Created, Id: reaction.ID, Name: reaction.Name, Trigger: reaction.Trigger, Triggerdata: unmarshal(reaction.Triggerdata), Updated: reaction.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.ReactionsTable.ID, response) totalCount := 0 if len(reactions) > 0 { totalCount = int(reactions[0].TotalCount) } return openapi.ListReactions200JSONResponse{ Body: response, Headers: openapi.ListReactions200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateReaction(ctx context.Context, request openapi.CreateReactionRequestObject) (openapi.CreateReactionResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.ReactionsTable.ID, request.Body) reaction, err := s.queries.CreateReaction(ctx, sqlc.CreateReactionParams{ Name: request.Body.Name, Action: request.Body.Action, Trigger: request.Body.Trigger, Actiondata: marshal(request.Body.Actiondata), Triggerdata: marshal(request.Body.Triggerdata), }) if err != nil { return nil, err } if err := s.scheduler.AddReaction(&reaction); err != nil { slog.ErrorContext(ctx, "Failed to add reaction to scheduler", "error", err) } response := openapi.Reaction{ Action: reaction.Action, Actiondata: unmarshal(reaction.Actiondata), Created: reaction.Created, Id: reaction.ID, Name: reaction.Name, Trigger: reaction.Trigger, Triggerdata: unmarshal(reaction.Triggerdata), Updated: reaction.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.ReactionsTable.ID, response) return openapi.CreateReaction200JSONResponse(response), nil } func (s *Service) DeleteReaction(ctx context.Context, request openapi.DeleteReactionRequestObject) (openapi.DeleteReactionResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.ReactionsTable.ID, request.Id) err := s.queries.DeleteReaction(ctx, request.Id) if err != nil { return nil, err } s.scheduler.RemoveReaction(request.Id) s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.ReactionsTable.ID, request.Id) return openapi.DeleteReaction204Response{}, nil } func (s *Service) GetReaction(ctx context.Context, request openapi.GetReactionRequestObject) (openapi.GetReactionResponseObject, error) { reaction, err := s.queries.GetReaction(ctx, request.Id) if err != nil { return nil, err } response := openapi.Reaction{ Action: reaction.Action, Actiondata: unmarshal(reaction.Actiondata), Created: reaction.Created, Id: reaction.ID, Name: reaction.Name, Trigger: reaction.Trigger, Triggerdata: unmarshal(reaction.Triggerdata), Updated: reaction.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.ReactionsTable.ID, response) return openapi.GetReaction200JSONResponse(response), nil } func (s *Service) UpdateReaction(ctx context.Context, request openapi.UpdateReactionRequestObject) (openapi.UpdateReactionResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.ReactionsTable.ID, request.Body) reaction, err := s.queries.UpdateReaction(ctx, sqlc.UpdateReactionParams{ ID: request.Id, Name: request.Body.Name, Action: request.Body.Action, Trigger: request.Body.Trigger, Actiondata: marshalPointer(request.Body.Actiondata), Triggerdata: marshalPointer(request.Body.Triggerdata), }) if err != nil { return nil, err } s.scheduler.RemoveReaction(request.Id) if err := s.scheduler.AddReaction(&reaction); err != nil { slog.ErrorContext(ctx, "Failed to add reaction to scheduler", "error", err) } response := openapi.Reaction{ Action: reaction.Action, Actiondata: unmarshal(reaction.Actiondata), Created: reaction.Created, Id: reaction.ID, Name: reaction.Name, Trigger: reaction.Trigger, Triggerdata: unmarshal(reaction.Triggerdata), Updated: reaction.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.ReactionsTable.ID, response) return openapi.UpdateReaction200JSONResponse(response), nil } func (s *Service) GetSidebar(ctx context.Context, _ openapi.GetSidebarRequestObject) (openapi.GetSidebarResponseObject, error) { sidebar, err := s.queries.GetSidebar(ctx) if err != nil { return nil, err } response := make([]openapi.Sidebar, 0, len(sidebar)) for _, s := range sidebar { response = append(response, openapi.Sidebar{ Id: s.ID, Singular: s.Singular, Plural: s.Plural, Icon: s.Icon, Count: int(s.Count), }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.SidebarTable.ID, response) return openapi.GetSidebar200JSONResponse(response), nil } func (s *Service) ListTasks(ctx context.Context, request openapi.ListTasksRequestObject) (openapi.ListTasksResponseObject, error) { tasks, err := s.queries.ListTasks(ctx, sqlc.ListTasksParams{ Ticket: toString(request.Params.Ticket, ""), Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.ExtendedTask, 0, len(tasks)) for _, task := range tasks { response = append(response, openapi.ExtendedTask{ Id: task.ID, Name: task.Name, Created: task.Created, Updated: task.Updated, Open: task.Open, Owner: task.Owner, Ticket: task.Ticket, OwnerName: task.OwnerName, TicketName: pointer.Dereference(task.TicketName), TicketType: pointer.Dereference(task.TicketType), }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.TasksTable.ID, response) totalCount := 0 if len(tasks) > 0 { totalCount = int(tasks[0].TotalCount) } return openapi.ListTasks200JSONResponse{ Body: response, Headers: openapi.ListTasks200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateTask(ctx context.Context, request openapi.CreateTaskRequestObject) (openapi.CreateTaskResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.TasksTable.ID, request.Body) task, err := s.queries.CreateTask(ctx, sqlc.CreateTaskParams{ Name: request.Body.Name, Open: request.Body.Open, Owner: request.Body.Owner, Ticket: request.Body.Ticket, }) if err != nil { return nil, err } response := openapi.Task{ Created: task.Created, Id: task.ID, Name: task.Name, Open: task.Open, Owner: task.Owner, Ticket: task.Ticket, Updated: task.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.TasksTable.ID, response) return openapi.CreateTask200JSONResponse(response), nil } func (s *Service) DeleteTask(ctx context.Context, request openapi.DeleteTaskRequestObject) (openapi.DeleteTaskResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.TasksTable.ID, request.Id) err := s.queries.DeleteTask(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.TasksTable.ID, request.Id) return openapi.DeleteTask204Response{}, nil } func (s *Service) GetTask(ctx context.Context, request openapi.GetTaskRequestObject) (openapi.GetTaskResponseObject, error) { task, err := s.queries.GetTask(ctx, request.Id) if err != nil { return nil, err } response := openapi.ExtendedTask{ Id: task.ID, Name: task.Name, Created: task.Created, Updated: task.Updated, Open: task.Open, Owner: task.Owner, Ticket: task.Ticket, OwnerName: task.OwnerName, TicketName: pointer.Dereference(task.TicketName), TicketType: pointer.Dereference(task.TicketType), } s.hooks.OnRecordViewRequest.Publish(ctx, database.TasksTable.ID, response) return openapi.GetTask200JSONResponse(response), nil } func (s *Service) UpdateTask(ctx context.Context, request openapi.UpdateTaskRequestObject) (openapi.UpdateTaskResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.TasksTable.ID, request.Body) task, err := s.queries.UpdateTask(ctx, sqlc.UpdateTaskParams{ ID: request.Id, Name: request.Body.Name, Open: request.Body.Open, Owner: request.Body.Owner, }) if err != nil { return nil, err } response := openapi.Task{ Created: task.Created, Id: task.ID, Name: task.Name, Open: task.Open, Owner: task.Owner, Ticket: task.Ticket, Updated: task.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.TasksTable.ID, response) return openapi.UpdateTask200JSONResponse(response), nil } func (s *Service) SearchTickets(ctx context.Context, request openapi.SearchTicketsRequestObject) (openapi.SearchTicketsResponseObject, error) { tickets, err := s.queries.SearchTickets(ctx, sqlc.SearchTicketsParams{ Query: request.Params.Query, Type: request.Params.Type, Open: request.Params.Open, Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.TicketSearch, 0, len(tickets)) for _, ticket := range tickets { response = append(response, openapi.TicketSearch{ Created: ticket.Created, Description: ticket.Description, Id: ticket.ID, Name: ticket.Name, Open: ticket.Open, OwnerName: pointer.Dereference(ticket.OwnerName), State: unmarshal(ticket.State), Type: ticket.Type, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.TicketsTable.ID, response) totalCount := 0 if len(tickets) > 0 { totalCount = int(tickets[0].TotalCount) } return openapi.SearchTickets200JSONResponse{ Body: response, Headers: openapi.SearchTickets200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) ListTickets(ctx context.Context, request openapi.ListTicketsRequestObject) (openapi.ListTicketsResponseObject, error) { tickets, err := s.queries.ListTickets(ctx, sqlc.ListTicketsParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.ExtendedTicket, 0, len(tickets)) for _, ticket := range tickets { response = append(response, openapi.ExtendedTicket{ Created: ticket.Created, Description: ticket.Description, Id: ticket.ID, Name: ticket.Name, Open: ticket.Open, Owner: ticket.Owner, OwnerName: ticket.OwnerName, Resolution: ticket.Resolution, Type: ticket.Type, Schema: unmarshal(ticket.Schema), State: unmarshal(ticket.State), TypePlural: pointer.Dereference(ticket.TypePlural), TypeSingular: pointer.Dereference(ticket.TypeSingular), Updated: ticket.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, "tickets", response) totalCount := 0 if len(tickets) > 0 { totalCount = int(tickets[0].TotalCount) } return openapi.ListTickets200JSONResponse{ Body: response, Headers: openapi.ListTickets200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateTicket(ctx context.Context, request openapi.CreateTicketRequestObject) (openapi.CreateTicketResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.TicketsTable.ID, request.Body) ticket, err := s.queries.CreateTicket(ctx, sqlc.CreateTicketParams{ Name: request.Body.Name, Description: request.Body.Description, Owner: request.Body.Owner, Open: request.Body.Open, Resolution: request.Body.Resolution, Type: request.Body.Type, State: marshal(request.Body.State), }) if err != nil { return nil, err } response := openapi.Ticket{ Created: ticket.Created, Description: ticket.Description, Id: ticket.ID, Name: ticket.Name, Open: ticket.Open, Owner: ticket.Owner, Resolution: ticket.Resolution, Schema: unmarshal(ticket.Schema), State: unmarshal(ticket.State), Type: ticket.Type, Updated: ticket.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.TicketsTable.ID, response) return openapi.CreateTicket200JSONResponse(response), nil } func (s *Service) DeleteTicket(ctx context.Context, request openapi.DeleteTicketRequestObject) (openapi.DeleteTicketResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.TicketsTable.ID, request.Id) err := s.queries.DeleteTicket(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.TicketsTable.ID, request.Id) return openapi.DeleteTicket204Response{}, nil } func (s *Service) GetTicket(ctx context.Context, request openapi.GetTicketRequestObject) (openapi.GetTicketResponseObject, error) { ticket, err := s.queries.Ticket(ctx, request.Id) if err != nil { return nil, err } response := openapi.ExtendedTicket{ Created: ticket.Created, Description: ticket.Description, Id: ticket.ID, Name: ticket.Name, Open: ticket.Open, Owner: ticket.Owner, OwnerName: ticket.OwnerName, Resolution: ticket.Resolution, Schema: unmarshal(ticket.Schema), State: unmarshal(ticket.State), Type: ticket.Type, TypePlural: pointer.Dereference(ticket.TypePlural), TypeSingular: pointer.Dereference(ticket.TypeSingular), Updated: ticket.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.TicketsTable.ID, response) return openapi.GetTicket200JSONResponse(response), nil } func (s *Service) UpdateTicket(ctx context.Context, request openapi.UpdateTicketRequestObject) (openapi.UpdateTicketResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.TicketsTable.ID, request.Body) ticket, err := s.queries.UpdateTicket(ctx, sqlc.UpdateTicketParams{ Name: request.Body.Name, Description: request.Body.Description, Open: request.Body.Open, Owner: request.Body.Owner, Resolution: request.Body.Resolution, Schema: marshalPointer(request.Body.Schema), State: marshalPointer(request.Body.State), Type: request.Body.Type, ID: request.Id, }) if err != nil { return nil, err } response := openapi.Ticket{ Created: ticket.Created, Description: ticket.Description, Id: ticket.ID, Name: ticket.Name, Open: ticket.Open, Owner: ticket.Owner, Resolution: ticket.Resolution, Schema: unmarshal(ticket.Schema), State: unmarshal(ticket.State), Type: ticket.Type, Updated: ticket.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.TicketsTable.ID, response) return openapi.UpdateTicket200JSONResponse(response), nil } func (s *Service) ListTimeline(ctx context.Context, request openapi.ListTimelineRequestObject) (openapi.ListTimelineResponseObject, error) { timeline, err := s.queries.ListTimeline(ctx, sqlc.ListTimelineParams{ Ticket: toString(request.Params.Ticket, ""), Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.TimelineEntry, 0, len(timeline)) for _, timeline := range timeline { response = append(response, openapi.TimelineEntry{ Id: timeline.ID, Message: timeline.Message, Created: timeline.Created, Updated: timeline.Updated, Time: timeline.Time, Ticket: timeline.Ticket, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.TimelinesTable.ID, response) totalCount := 0 if len(timeline) > 0 { totalCount = int(timeline[0].TotalCount) } return openapi.ListTimeline200JSONResponse{ Body: response, Headers: openapi.ListTimeline200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateTimeline(ctx context.Context, request openapi.CreateTimelineRequestObject) (openapi.CreateTimelineResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.TimelinesTable.ID, request.Body) timeline, err := s.queries.CreateTimeline(ctx, sqlc.CreateTimelineParams{ Message: request.Body.Message, Time: request.Body.Time, Ticket: request.Body.Ticket, }) if err != nil { return nil, err } response := openapi.TimelineEntry{ Created: timeline.Created, Id: timeline.ID, Message: timeline.Message, Ticket: timeline.Ticket, Time: timeline.Time, Updated: timeline.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.TimelinesTable.ID, response) return openapi.CreateTimeline200JSONResponse(response), nil } func (s *Service) DeleteTimeline(ctx context.Context, request openapi.DeleteTimelineRequestObject) (openapi.DeleteTimelineResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.TimelinesTable.ID, request.Id) err := s.queries.DeleteTimeline(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.TimelinesTable.ID, request.Id) return openapi.DeleteTimeline204Response{}, nil } func (s *Service) GetTimeline(ctx context.Context, request openapi.GetTimelineRequestObject) (openapi.GetTimelineResponseObject, error) { timeline, err := s.queries.GetTimeline(ctx, request.Id) if err != nil { return nil, err } response := openapi.TimelineEntry{ Id: timeline.ID, Message: timeline.Message, Created: timeline.Created, Updated: timeline.Updated, Time: timeline.Time, Ticket: timeline.Ticket, } s.hooks.OnRecordViewRequest.Publish(ctx, database.TimelinesTable.ID, response) return openapi.GetTimeline200JSONResponse(response), nil } func (s *Service) UpdateTimeline(ctx context.Context, request openapi.UpdateTimelineRequestObject) (openapi.UpdateTimelineResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.TimelinesTable.ID, request.Body) timeline, err := s.queries.UpdateTimeline(ctx, sqlc.UpdateTimelineParams{ ID: request.Id, Message: request.Body.Message, Time: request.Body.Time, }) if err != nil { return nil, err } response := openapi.TimelineEntry{ Created: timeline.Created, Id: timeline.ID, Message: timeline.Message, Ticket: timeline.Ticket, Time: timeline.Time, Updated: timeline.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.TimelinesTable.ID, response) return openapi.UpdateTimeline200JSONResponse(response), nil } func (s *Service) ListTypes(ctx context.Context, request openapi.ListTypesRequestObject) (openapi.ListTypesResponseObject, error) { types, err := s.queries.ListTypes(ctx, sqlc.ListTypesParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.Type, 0, len(types)) for _, t := range types { response = append(response, openapi.Type{ Created: t.Created, Icon: t.Icon, Id: t.ID, Plural: t.Plural, Schema: unmarshal(t.Schema), Singular: t.Singular, Updated: t.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.TypesTable.ID, response) totalCount := 0 if len(types) > 0 { totalCount = int(types[0].TotalCount) } return openapi.ListTypes200JSONResponse{ Body: response, Headers: openapi.ListTypes200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateType(ctx context.Context, request openapi.CreateTypeRequestObject) (openapi.CreateTypeResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.TypesTable.ID, request.Body) t, err := s.queries.CreateType(ctx, sqlc.CreateTypeParams{ Icon: request.Body.Icon, Plural: request.Body.Plural, Singular: request.Body.Singular, Schema: marshal(request.Body.Schema), }) if err != nil { return nil, err } response := openapi.Type{ Created: t.Created, Icon: t.Icon, Id: t.ID, Plural: t.Plural, Schema: unmarshal(t.Schema), Singular: t.Singular, Updated: t.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.TypesTable.ID, response) return openapi.CreateType200JSONResponse(response), nil } func (s *Service) DeleteType(ctx context.Context, request openapi.DeleteTypeRequestObject) (openapi.DeleteTypeResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.TypesTable.ID, request.Id) if err := s.queries.DeleteType(ctx, request.Id); err != nil { return nil, fmt.Errorf("failed to delete type: %w", err) } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.TypesTable.ID, request.Id) return openapi.DeleteType204Response{}, nil } func (s *Service) GetType(ctx context.Context, request openapi.GetTypeRequestObject) (openapi.GetTypeResponseObject, error) { t, err := s.queries.GetType(ctx, request.Id) if err != nil { return nil, err } response := openapi.Type{ Created: t.Created, Icon: t.Icon, Id: t.ID, Plural: t.Plural, Schema: unmarshal(t.Schema), Singular: t.Singular, Updated: t.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.TypesTable.ID, response) return openapi.GetType200JSONResponse(response), nil } func (s *Service) UpdateType(ctx context.Context, request openapi.UpdateTypeRequestObject) (openapi.UpdateTypeResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.TypesTable.ID, request.Body) t, err := s.queries.UpdateType(ctx, sqlc.UpdateTypeParams{ ID: request.Id, Icon: request.Body.Icon, Plural: request.Body.Plural, Singular: request.Body.Singular, Schema: marshalPointer(request.Body.Schema), }) if err != nil { return nil, err } response := openapi.Type{ Created: t.Created, Icon: t.Icon, Id: t.ID, Plural: t.Plural, Schema: unmarshal(t.Schema), Singular: t.Singular, Updated: t.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.TypesTable.ID, response) return openapi.UpdateType200JSONResponse(response), nil } func (s *Service) ListUsers(ctx context.Context, request openapi.ListUsersRequestObject) (openapi.ListUsersResponseObject, error) { users, err := s.queries.ListUsers(ctx, sqlc.ListUsersParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.User, 0, len(users)) for _, user := range users { response = append(response, openapi.User{ Avatar: user.Avatar, Created: user.Created, Email: user.Email, Id: user.ID, LastResetSentAt: user.Lastresetsentat, LastVerificationSentAt: user.Lastverificationsentat, Name: user.Name, Updated: user.Updated, Username: user.Username, Active: user.Active, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.UsersTable.ID, response) totalCount := 0 if len(users) > 0 { totalCount = int(users[0].TotalCount) } return openapi.ListUsers200JSONResponse{ Body: response, Headers: openapi.ListUsers200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateUser(ctx context.Context, request openapi.CreateUserRequestObject) (openapi.CreateUserResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.UsersTable.ID, request.Body) tokenKey, err := password.GenerateTokenKey() if err != nil { return nil, fmt.Errorf("failed to generate token key: %w", err) } user, err := s.queries.CreateUser(ctx, sqlc.CreateUserParams{ Name: request.Body.Name, Email: request.Body.Email, Username: request.Body.Username, PasswordHash: "", TokenKey: tokenKey, Avatar: request.Body.Avatar, Active: request.Body.Active, }) if err != nil { return nil, err } response := openapi.User{ Avatar: user.Avatar, Created: user.Created, Email: user.Email, Id: user.ID, LastResetSentAt: user.Lastresetsentat, LastVerificationSentAt: user.Lastverificationsentat, Name: user.Name, Updated: user.Updated, Username: user.Username, Active: user.Active, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.UsersTable.ID, response) return openapi.CreateUser200JSONResponse(response), nil } func (s *Service) DeleteUser(ctx context.Context, request openapi.DeleteUserRequestObject) (openapi.DeleteUserResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.UsersTable.ID, request.Id) err := s.queries.DeleteUser(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.UsersTable.ID, request.Id) return openapi.DeleteUser204Response{}, nil } func (s *Service) GetUser(ctx context.Context, request openapi.GetUserRequestObject) (openapi.GetUserResponseObject, error) { user, err := s.queries.GetUser(ctx, request.Id) if err != nil { return nil, err } response := openapi.User{ Avatar: user.Avatar, Created: user.Created, Email: user.Email, Id: user.ID, LastResetSentAt: user.Lastresetsentat, LastVerificationSentAt: user.Lastverificationsentat, Name: user.Name, Updated: user.Updated, Username: user.Username, Active: user.Active, } s.hooks.OnRecordViewRequest.Publish(ctx, database.UsersTable.ID, response) return openapi.GetUser200JSONResponse(response), nil } func (s *Service) UpdateUser(ctx context.Context, request openapi.UpdateUserRequestObject) (openapi.UpdateUserResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.UsersTable.ID, request.Body) var passwordHash, tokenHash *string switch { case request.Body.Password == nil && request.Body.PasswordConfirm == nil: case request.Body.Password != nil && request.Body.PasswordConfirm != nil: if *request.Body.Password != *request.Body.PasswordConfirm { return nil, errors.New("passwords do not match") } passwordHashS, tokenHashS, err := password.Hash(*request.Body.Password) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } passwordHash = &passwordHashS tokenHash = &tokenHashS default: return nil, errors.New("password and password confirm must be provided together") } user, err := s.queries.UpdateUser(ctx, sqlc.UpdateUserParams{ Name: request.Body.Name, Email: request.Body.Email, Username: request.Body.Username, PasswordHash: passwordHash, TokenKey: tokenHash, Avatar: request.Body.Avatar, Active: request.Body.Active, ID: request.Id, }) if err != nil { return nil, err } response := openapi.User{ Avatar: user.Avatar, Created: user.Created, Email: user.Email, Id: user.ID, LastResetSentAt: user.Lastresetsentat, LastVerificationSentAt: user.Lastverificationsentat, Name: user.Name, Updated: user.Updated, Username: user.Username, Active: user.Active, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, "users", response) return openapi.UpdateUser200JSONResponse(response), nil } func (s *Service) ListGroups(ctx context.Context, request openapi.ListGroupsRequestObject) (openapi.ListGroupsResponseObject, error) { groups, err := s.queries.ListGroups(ctx, sqlc.ListGroupsParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.Group, 0, len(groups)) for _, group := range groups { response = append(response, openapi.Group{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Updated: group.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.GroupsTable.ID, response) totalCount := 0 if len(groups) > 0 { totalCount = int(groups[0].TotalCount) } return openapi.ListGroups200JSONResponse{ Body: response, Headers: openapi.ListGroups200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateGroup(ctx context.Context, request openapi.CreateGroupRequestObject) (openapi.CreateGroupResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.GroupsTable.ID, request.Body) group, err := s.queries.CreateGroup(ctx, sqlc.CreateGroupParams{ Name: request.Body.Name, Permissions: auth.ToJSONArray(ctx, request.Body.Permissions), }) if err != nil { return nil, err } response := openapi.Group{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Updated: group.Updated, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.GroupsTable.ID, response) return openapi.CreateGroup200JSONResponse(response), nil } func (s *Service) DeleteGroup(ctx context.Context, request openapi.DeleteGroupRequestObject) (openapi.DeleteGroupResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.GroupsTable.ID, request.Id) if request.Id == "admin" { return nil, errors.New("cannot delete the admin group") } err := s.queries.DeleteGroup(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.GroupsTable.ID, request.Id) return openapi.DeleteGroup204Response{}, nil } func (s *Service) GetGroup(ctx context.Context, request openapi.GetGroupRequestObject) (openapi.GetGroupResponseObject, error) { group, err := s.queries.GetGroup(ctx, request.Id) if err != nil { return nil, err } response := openapi.Group{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Updated: group.Updated, } s.hooks.OnRecordViewRequest.Publish(ctx, database.GroupsTable.ID, response) return openapi.GetGroup200JSONResponse(response), nil } func (s *Service) UpdateGroup(ctx context.Context, request openapi.UpdateGroupRequestObject) (openapi.UpdateGroupResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.GroupsTable.ID, request.Body) if request.Id == "admin" { return nil, errors.New("cannot update the admin group") } var permissions *string if request.Body.Permissions != nil { p := auth.ToJSONArray(ctx, *request.Body.Permissions) permissions = &p } group, err := s.queries.UpdateGroup(ctx, sqlc.UpdateGroupParams{ Name: request.Body.Name, Permissions: permissions, ID: request.Id, }) if err != nil { return nil, err } response := openapi.Group{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Updated: group.Updated, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.GroupsTable.ID, response) return openapi.UpdateGroup200JSONResponse(response), nil } func (s *Service) AddGroupParent(ctx context.Context, request openapi.AddGroupParentRequestObject) (openapi.AddGroupParentResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.GroupParentTable.ID, request.Body) err := s.queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{ ChildGroupID: request.Id, ParentGroupID: request.Body.GroupId, }) if err != nil { return nil, err } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.GroupParentTable.ID, request.Body) return openapi.AddGroupParent201Response{}, nil } func (s *Service) RemoveGroupParent(ctx context.Context, request openapi.RemoveGroupParentRequestObject) (openapi.RemoveGroupParentResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.GroupParentTable.ID, request.Id) err := s.queries.RemoveParentGroup(ctx, sqlc.RemoveParentGroupParams{ ChildGroupID: request.Id, ParentGroupID: request.ParentGroupId, }) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.GroupParentTable.ID, request.Id) return openapi.RemoveGroupParent204Response{}, nil } func (s *Service) AddUserGroup(ctx context.Context, request openapi.AddUserGroupRequestObject) (openapi.AddUserGroupResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.UserGroupTable.ID, request.Body) err := s.queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{ UserID: request.Id, GroupID: request.Body.GroupId, }) if err != nil { return nil, err } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.UserGroupTable.ID, request.Body) return openapi.AddUserGroup201Response{}, nil } func (s *Service) RemoveUserGroup(ctx context.Context, request openapi.RemoveUserGroupRequestObject) (openapi.RemoveUserGroupResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.UserGroupTable.ID, request.Id) err := s.queries.RemoveGroupFromUser(ctx, sqlc.RemoveGroupFromUserParams{ UserID: request.Id, GroupID: request.GroupId, }) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.UserGroupTable.ID, request.Id) return openapi.RemoveUserGroup204Response{}, nil } func (s *Service) ListUserPermissions(ctx context.Context, request openapi.ListUserPermissionsRequestObject) (openapi.ListUserPermissionsResponseObject, error) { permissions, err := s.queries.ListUserPermissions(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordsListRequest.Publish(ctx, database.UserPermissionTable.ID, permissions) return openapi.ListUserPermissions200JSONResponse(permissions), nil } func (s *Service) ListUserGroups(ctx context.Context, request openapi.ListUserGroupsRequestObject) (openapi.ListUserGroupsResponseObject, error) { groups, err := s.queries.ListUserGroups(ctx, request.Id) if err != nil { return nil, err } response := make([]openapi.UserGroup, 0, len(groups)) for _, group := range groups { response = append(response, openapi.UserGroup{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Type: group.GroupType, Updated: group.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.UserGroupTable.ID, response) return openapi.ListUserGroups200JSONResponse(response), nil } func (s *Service) ListGroupUsers(ctx context.Context, request openapi.ListGroupUsersRequestObject) (openapi.ListGroupUsersResponseObject, error) { users, err := s.queries.ListGroupUsers(ctx, request.Id) if err != nil { return nil, err } response := make([]openapi.GroupUser, 0, len(users)) for _, user := range users { response = append(response, openapi.GroupUser{ Avatar: user.Avatar, Created: user.Created, Email: user.Email, Id: user.ID, LastResetSentAt: user.Lastresetsentat, LastVerificationSentAt: user.Lastverificationsentat, Name: user.Name, Updated: user.Updated, Username: user.Username, Active: user.Active, Type: user.GroupType, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.GroupUserTable.ID, response) return openapi.ListGroupUsers200JSONResponse(response), nil } func (s *Service) ListParentPermissions(ctx context.Context, request openapi.ListParentPermissionsRequestObject) (openapi.ListParentPermissionsResponseObject, error) { permissions, err := s.queries.ListParentPermissions(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordsListRequest.Publish(ctx, database.GroupPermissionTable.ID, permissions) return openapi.ListParentPermissions200JSONResponse(permissions), nil } func (s *Service) ListParentGroups(ctx context.Context, request openapi.ListParentGroupsRequestObject) (openapi.ListParentGroupsResponseObject, error) { groups, err := s.queries.ListParentGroups(ctx, request.Id) if err != nil { return nil, err } response := make([]openapi.UserGroup, 0, len(groups)) for _, group := range groups { response = append(response, openapi.UserGroup{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Type: group.GroupType, Updated: group.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.GroupParentTable.ID, response) return openapi.ListParentGroups200JSONResponse(response), nil } func (s *Service) ListChildGroups(ctx context.Context, request openapi.ListChildGroupsRequestObject) (openapi.ListChildGroupsResponseObject, error) { groups, err := s.queries.ListChildGroups(ctx, request.Id) if err != nil { return nil, err } response := make([]openapi.UserGroup, 0, len(groups)) for _, group := range groups { response = append(response, openapi.UserGroup{ Created: group.Created, Id: group.ID, Name: group.Name, Permissions: auth.FromJSONArray(ctx, group.Permissions), Type: group.GroupType, Updated: group.Updated, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.GroupChildTable.ID, response) return openapi.ListChildGroups200JSONResponse(response), nil } func (s *Service) ListWebhooks(ctx context.Context, request openapi.ListWebhooksRequestObject) (openapi.ListWebhooksResponseObject, error) { webhooks, err := s.queries.ListWebhooks(ctx, sqlc.ListWebhooksParams{ Offset: toInt64(request.Params.Offset, defaultOffset), Limit: toInt64(request.Params.Limit, defaultLimit), }) if err != nil { return nil, err } response := make([]openapi.Webhook, 0, len(webhooks)) for _, webhook := range webhooks { response = append(response, openapi.Webhook{ Id: webhook.ID, Name: webhook.Name, }) } s.hooks.OnRecordsListRequest.Publish(ctx, database.WebhooksTable.ID, response) totalCount := 0 if len(webhooks) > 0 { totalCount = int(webhooks[0].TotalCount) } return openapi.ListWebhooks200JSONResponse{ Body: response, Headers: openapi.ListWebhooks200ResponseHeaders{ XTotalCount: totalCount, }, }, nil } func (s *Service) CreateWebhook(ctx context.Context, request openapi.CreateWebhookRequestObject) (openapi.CreateWebhookResponseObject, error) { s.hooks.OnRecordBeforeCreateRequest.Publish(ctx, database.WebhooksTable.ID, request.Body) webhook, err := s.queries.CreateWebhook(ctx, sqlc.CreateWebhookParams{ Name: request.Body.Name, Destination: request.Body.Destination, Collection: request.Body.Collection, }) if err != nil { return nil, err } response := openapi.Webhook{ Id: webhook.ID, Name: webhook.Name, Created: webhook.Created, Updated: webhook.Updated, Destination: webhook.Destination, Collection: webhook.Collection, } s.hooks.OnRecordAfterCreateRequest.Publish(ctx, database.WebhooksTable.ID, response) return openapi.CreateWebhook200JSONResponse(response), nil } func (s *Service) DeleteWebhook(ctx context.Context, request openapi.DeleteWebhookRequestObject) (openapi.DeleteWebhookResponseObject, error) { s.hooks.OnRecordBeforeDeleteRequest.Publish(ctx, database.WebhooksTable.ID, request.Id) err := s.queries.DeleteWebhook(ctx, request.Id) if err != nil { return nil, err } s.hooks.OnRecordAfterDeleteRequest.Publish(ctx, database.WebhooksTable.ID, request.Id) return openapi.DeleteWebhook204Response{}, nil } func (s *Service) GetWebhook(ctx context.Context, request openapi.GetWebhookRequestObject) (openapi.GetWebhookResponseObject, error) { webhook, err := s.queries.GetWebhook(ctx, request.Id) if err != nil { return nil, err } response := openapi.Webhook{ Id: webhook.ID, Name: webhook.Name, Created: webhook.Created, Updated: webhook.Updated, Destination: webhook.Destination, Collection: webhook.Collection, } s.hooks.OnRecordViewRequest.Publish(ctx, database.WebhooksTable.ID, response) return openapi.GetWebhook200JSONResponse(response), nil } func (s *Service) UpdateWebhook(ctx context.Context, request openapi.UpdateWebhookRequestObject) (openapi.UpdateWebhookResponseObject, error) { s.hooks.OnRecordBeforeUpdateRequest.Publish(ctx, database.WebhooksTable.ID, request.Body) webhook, err := s.queries.UpdateWebhook(ctx, sqlc.UpdateWebhookParams{ ID: request.Id, Name: request.Body.Name, Destination: request.Body.Destination, Collection: request.Body.Collection, }) if err != nil { return nil, err } response := openapi.Webhook{ Id: webhook.ID, Name: webhook.Name, Created: webhook.Created, Updated: webhook.Updated, Destination: webhook.Destination, Collection: webhook.Collection, } s.hooks.OnRecordAfterUpdateRequest.Publish(ctx, database.WebhooksTable.ID, response) return openapi.UpdateWebhook200JSONResponse(response), nil } func (s *Service) GetConfig(ctx context.Context, _ openapi.GetConfigRequestObject) (openapi.GetConfigResponseObject, error) { flags := []string{} features, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListFeaturesRow, error) { return s.queries.ListFeatures(ctx, sqlc.ListFeaturesParams{Limit: limit, Offset: offset}) }) if err != nil { return nil, err } for _, feature := range features { flags = append(flags, feature.Key) } tables := []openapi.Table{} for _, table := range database.Tables() { tables = append(tables, openapi.Table{ Id: table.ID, Name: table.Name, }) } response := openapi.Config{ Flags: flags, Permissions: auth.All(), Tables: tables, } return openapi.GetConfig200JSONResponse(response), nil } func (s *Service) GetSettings(ctx context.Context, _ openapi.GetSettingsRequestObject) (openapi.GetSettingsResponseObject, error) { settings, err := settings.Load(ctx, s.queries) if err != nil { return nil, err } return openapi.GetSettings200JSONResponse(mapSettings(settings)), nil } func (s *Service) UpdateSettings(ctx context.Context, request openapi.UpdateSettingsRequestObject) (openapi.UpdateSettingsResponseObject, error) { se, err := settings.Update(ctx, s.queries, func(settings *settings.Settings) { settings.Meta.AppName = request.Body.Meta.AppName settings.Meta.AppURL = request.Body.Meta.AppUrl settings.Meta.SenderAddress = request.Body.Meta.SenderAddress settings.Meta.SenderName = request.Body.Meta.SenderName settings.SMTP.Enabled = request.Body.Smtp.Enabled settings.SMTP.Host = request.Body.Smtp.Host settings.SMTP.Port = request.Body.Smtp.Port settings.SMTP.Username = request.Body.Smtp.Username settings.SMTP.Password = request.Body.Smtp.Password settings.SMTP.AuthMethod = request.Body.Smtp.AuthMethod settings.SMTP.TLS = request.Body.Smtp.Tls settings.SMTP.LocalName = request.Body.Smtp.LocalName }) if err != nil { return nil, fmt.Errorf("failed to save settings: %w", err) } return openapi.UpdateSettings200JSONResponse(mapSettings(se)), err } func toString(value *string, defaultValue string) string { if value == nil { return defaultValue } return *value } func toInt64(value *int, defaultValue int64) int64 { if value == nil { return defaultValue } return int64(*value) } func marshal(state map[string]any) json.RawMessage { b, _ := json.Marshal(state) //nolint:errchkjson return b } func marshalPointer(state *map[string]any) json.RawMessage { if state == nil { return json.RawMessage("{}") } b, _ := json.Marshal(*state) //nolint:errchkjson return b } func unmarshal(data json.RawMessage) map[string]any { var m map[string]any if err := json.Unmarshal([]byte(data), &m); err != nil { return nil } return m } func mapSettings(settings *settings.Settings) openapi.Settings { return openapi.Settings{ Meta: openapi.SettingsMeta{ AppName: settings.Meta.AppName, AppUrl: settings.Meta.AppURL, SenderAddress: settings.Meta.SenderAddress, SenderName: settings.Meta.SenderName, ResetPasswordTemplate: openapi.EmailTemplate{ Body: settings.Meta.ResetPasswordTemplate.Body, Subject: settings.Meta.ResetPasswordTemplate.Subject, }, }, Smtp: openapi.SettingsSmtp{ AuthMethod: settings.SMTP.AuthMethod, Enabled: settings.SMTP.Enabled, Host: settings.SMTP.Host, LocalName: settings.SMTP.LocalName, Password: settings.SMTP.Password, Port: settings.SMTP.Port, Tls: settings.SMTP.TLS, Username: settings.SMTP.Username, }, } }