package dto import ( "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/domain/entities" ) // ========== AUTH DTOs ========== // RegisterRequest represents a registration request type RegisterRequest struct { Email string `json:"email" validate:"required,email"` Username string `json:"username" validate:"required,min=3,max=20"` Password string `json:"password" validate:"required,min=8"` PasswordConfirm string `json:"password_confirm" validate:"required,eqfield=Password"` FirstName string `json:"first_name" validate:"max=50"` LastName string `json:"last_name" validate:"max=50"` } // LoginRequest represents a login request type LoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } // LoginResponse represents a login response type LoginResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token,omitempty"` User *UserDTO `json:"user"` ExpiresIn int `json:"expires_in"` } // AuthProviderDTO represents an OAuth/OIDC provider in API responses. type AuthProviderDTO struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` AuthorizationURL string `json:"authorization_url,omitempty"` TokenURL string `json:"token_url,omitempty"` UserInfoURL string `json:"userinfo_url,omitempty"` Scopes []string `json:"scopes"` IDTokenClaim string `json:"id_token_claim,omitempty"` IsActive bool `json:"is_active"` } // CreateAuthProviderRequest represents an OAuth/OIDC provider creation request. type CreateAuthProviderRequest struct { Name string `json:"name"` Type string `json:"type"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` AuthorizationURL string `json:"authorization_url"` TokenURL string `json:"token_url"` UserInfoURL string `json:"userinfo_url"` Scopes []string `json:"scopes"` IDTokenClaim string `json:"id_token_claim,omitempty"` IsActive bool `json:"is_active"` } // UpdateAuthProviderRequest represents an OAuth/OIDC provider update request. // ClientSecret may be empty to keep the existing secret. type UpdateAuthProviderRequest struct { Name string `json:"name"` Type string `json:"type"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` AuthorizationURL string `json:"authorization_url"` TokenURL string `json:"token_url"` UserInfoURL string `json:"userinfo_url"` Scopes []string `json:"scopes"` IDTokenClaim string `json:"id_token_claim,omitempty"` IsActive bool `json:"is_active"` } // FeatureFlagsDTO represents app-wide feature flags in API responses. type FeatureFlagsDTO struct { RegistrationEnabled bool `json:"registration_enabled"` ProviderLoginEnabled bool `json:"provider_login_enabled"` PublicSharingEnabled bool `json:"public_sharing_enabled"` FileExplorerEnabled bool `json:"file_explorer_enabled"` S3Endpoint string `json:"s3_endpoint,omitempty"` S3Bucket string `json:"s3_bucket,omitempty"` S3Region string `json:"s3_region,omitempty"` S3AccessKey string `json:"s3_access_key,omitempty"` S3SecretKeySet bool `json:"s3_secret_key_set"` } // UpdateFeatureFlagsRequest represents admin payload for feature flag updates. type UpdateFeatureFlagsRequest struct { RegistrationEnabled bool `json:"registration_enabled"` ProviderLoginEnabled bool `json:"provider_login_enabled"` PublicSharingEnabled bool `json:"public_sharing_enabled"` FileExplorerEnabled bool `json:"file_explorer_enabled"` S3Endpoint string `json:"s3_endpoint"` S3Bucket string `json:"s3_bucket"` S3Region string `json:"s3_region"` S3AccessKey string `json:"s3_access_key"` S3SecretKey string `json:"s3_secret_key"` // empty = keep existing encrypted value } // UserDTO represents a user in API responses type UserDTO struct { ID string `json:"id"` Email string `json:"email"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Avatar string `json:"avatar,omitempty"` GroupIDs []string `json:"group_ids,omitempty"` Permissions []string `json:"permissions,omitempty"` EmailVerified bool `json:"email_verified"` } // NewUserDTO creates a DTO from a user entity func NewUserDTO(user *entities.User) *UserDTO { groupIDs := make([]string, 0, len(user.GroupIDs)) for _, groupID := range user.GroupIDs { groupIDs = append(groupIDs, groupID.Hex()) } return &UserDTO{ ID: user.ID.Hex(), Email: user.Email, Username: user.Username, FirstName: user.FirstName, LastName: user.LastName, Avatar: user.Avatar, GroupIDs: groupIDs, Permissions: user.Permissions, EmailVerified: user.EmailVerified, } } // AdminUserDTO extends UserDTO with admin-visible fields type AdminUserDTO struct { *UserDTO IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` } // PermissionGroupDTO represents a permission group in API responses. type PermissionGroupDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` IsSystem bool `json:"is_system"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // CreatePermissionGroupRequest represents group creation input. type CreatePermissionGroupRequest struct { Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` } // UpdatePermissionGroupRequest represents group update input. type UpdatePermissionGroupRequest struct { Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` } // UpdateUserGroupsRequest represents user group assignment input. type UpdateUserGroupsRequest struct { GroupIDs []string `json:"group_ids"` } // NewAdminUserDTO creates an admin DTO from a user entity func NewAdminUserDTO(user *entities.User) *AdminUserDTO { return &AdminUserDTO{ UserDTO: NewUserDTO(user), IsActive: user.IsActive, CreatedAt: user.CreatedAt.Format("2006-01-02T15:04:05Z"), } } // NewPermissionGroupDTO creates a DTO from a permission group entity. func NewPermissionGroupDTO(group *entities.PermissionGroup) *PermissionGroupDTO { return &PermissionGroupDTO{ ID: group.ID.Hex(), Name: group.Name, Description: group.Description, Permissions: group.Permissions, IsSystem: group.IsSystem, CreatedAt: group.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: group.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // AddSpaceMemberRequest represents a request to add a member to a space type AddSpaceMemberRequest struct { UserID string `json:"user_id"` } // SpaceMemberDTO represents a member in a space type SpaceMemberDTO struct { UserID string `json:"user_id"` Username string `json:"username"` JoinedAt string `json:"joined_at"` } // UserOptionDTO is a lightweight user object for dropdowns type UserOptionDTO struct { ID string `json:"id"` Username string `json:"username"` } // NewAuthProviderDTO creates a DTO from an auth provider entity. func NewAuthProviderDTO(provider *entities.AuthProvider) *AuthProviderDTO { return &AuthProviderDTO{ ID: provider.ID.Hex(), Name: provider.Name, Type: provider.Type, AuthorizationURL: provider.AuthorizationURL, TokenURL: provider.TokenURL, UserInfoURL: provider.UserInfoURL, Scopes: provider.Scopes, IDTokenClaim: provider.IDTokenClaim, IsActive: provider.IsActive, } } // NewFeatureFlagsDTO creates a DTO from feature flags entity. func NewFeatureFlagsDTO(flags *entities.FeatureFlags) *FeatureFlagsDTO { if flags == nil { flags = entities.NewDefaultFeatureFlags() } return &FeatureFlagsDTO{ RegistrationEnabled: flags.RegistrationEnabled, ProviderLoginEnabled: flags.ProviderLoginEnabled, PublicSharingEnabled: flags.PublicSharingEnabled, FileExplorerEnabled: flags.FileExplorerEnabled, S3Endpoint: flags.S3Endpoint, S3Bucket: flags.S3Bucket, S3Region: flags.S3Region, S3AccessKey: flags.S3AccessKey, S3SecretKeySet: flags.S3SecretKey != "", } } // ========== SPACE DTOs ========== // CreateSpaceRequest represents a space creation request type CreateSpaceRequest struct { Name string `json:"name" validate:"required,min=1,max=100"` Description string `json:"description" validate:"max=500"` Icon string `json:"icon,omitempty" validate:"max=20"` IsPublic bool `json:"is_public"` } // SpaceDTO represents a space in API responses type SpaceDTO struct { ID string `json:"id"` Name string `json:"name"` PermissionKey string `json:"permission_key"` Description string `json:"description"` Icon string `json:"icon,omitempty"` OwnerID string `json:"owner_id"` IsPublic bool `json:"is_public"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // NewSpaceDTO creates a DTO from a space entity func NewSpaceDTO(space *entities.Space) *SpaceDTO { dto := &SpaceDTO{ ID: space.ID.Hex(), Name: space.Name, PermissionKey: entities.SpacePermissionToken(space.Name), Description: space.Description, Icon: space.Icon, OwnerID: space.OwnerID.Hex(), IsPublic: space.IsPublic, CreatedAt: space.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: space.UpdatedAt.Format("2006-01-02T15:04:05Z"), } return dto } // ========== NOTE DTOs ========== // CreateNoteRequest represents a note creation request type CreateNoteRequest struct { Title string `json:"title" validate:"required,min=1,max=255"` Description string `json:"description" validate:"max=500"` Content string `json:"content"` NotePassword string `json:"note_password,omitempty" validate:"omitempty,min=4,max=128"` Tags []string `json:"tags"` CategoryID *string `json:"category_id,omitempty"` IsPinned bool `json:"is_pinned"` IsFavorite bool `json:"is_favorite"` IsPublic bool `json:"is_public"` } // UpdateNoteRequest represents a note update request type UpdateNoteRequest struct { Title string `json:"title" validate:"min=1,max=255"` Description *string `json:"description,omitempty" validate:"omitempty,max=500"` Content string `json:"content"` NotePassword *string `json:"note_password,omitempty" validate:"omitempty,max=128"` Tags []string `json:"tags"` CategoryID *string `json:"category_id,omitempty"` IsPinned *bool `json:"is_pinned"` IsFavorite *bool `json:"is_favorite"` IsPublic *bool `json:"is_public,omitempty"` } // UnlockNoteRequest represents a password unlock request for protected notes type UnlockNoteRequest struct { Password string `json:"password" validate:"required,min=1,max=128"` } // NoteDTO represents a note in API responses type NoteDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` CategoryID *string `json:"category_id,omitempty"` Title string `json:"title"` Description string `json:"description"` Content string `json:"content"` Tags []string `json:"tags"` IsPinned bool `json:"is_pinned"` IsFavorite bool `json:"is_favorite"` IsPublic bool `json:"is_public"` IsPasswordProtected bool `json:"is_password_protected"` CreatedBy string `json:"created_by"` UpdatedBy string `json:"updated_by"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // NoteListItemDTO represents a lightweight note payload for list/tree endpoints type NoteListItemDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` CategoryID *string `json:"category_id,omitempty"` Title string `json:"title"` Description string `json:"description"` IsPinned bool `json:"is_pinned"` IsFavorite bool `json:"is_favorite"` IsPublic bool `json:"is_public"` IsPasswordProtected bool `json:"is_password_protected"` UpdatedAt string `json:"updated_at"` } // NewNoteDTO creates a DTO from a note entity func NewNoteDTO(note *entities.Note) *NoteDTO { var categoryID *string if note.CategoryID != nil { id := note.CategoryID.Hex() categoryID = &id } return &NoteDTO{ ID: note.ID.Hex(), SpaceID: note.SpaceID.Hex(), CategoryID: categoryID, Title: note.Title, Description: note.Description, Content: note.Content, Tags: note.Tags, IsPinned: note.IsPinned, IsFavorite: note.IsFavorite, IsPublic: note.IsPublic, IsPasswordProtected: note.IsPasswordProtected, CreatedBy: note.CreatedBy.Hex(), UpdatedBy: note.UpdatedBy.Hex(), CreatedAt: note.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: note.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // NewNoteListItemDTO creates a lightweight DTO from a note entity func NewNoteListItemDTO(note *entities.Note) *NoteListItemDTO { var categoryID *string if note.CategoryID != nil { id := note.CategoryID.Hex() categoryID = &id } return &NoteListItemDTO{ ID: note.ID.Hex(), SpaceID: note.SpaceID.Hex(), CategoryID: categoryID, Title: note.Title, Description: note.Description, IsPinned: note.IsPinned, IsFavorite: note.IsFavorite, IsPublic: note.IsPublic, IsPasswordProtected: note.IsPasswordProtected, UpdatedAt: note.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // ========== CATEGORY DTOs ========== // CreateCategoryRequest represents a category creation request type CreateCategoryRequest struct { Name string `json:"name" validate:"required,min=1,max=100"` Description string `json:"description" validate:"max=500"` ParentID *string `json:"parent_id,omitempty"` Icon string `json:"icon,omitempty" validate:"max=20"` } // UpdateCategoryRequest represents a category update request type UpdateCategoryRequest struct { Name string `json:"name" validate:"min=1,max=100"` Description string `json:"description" validate:"max=500"` Icon string `json:"icon,omitempty" validate:"max=20"` } // CategoryDTO represents a category in API responses type CategoryDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` Name string `json:"name"` Description string `json:"description"` ParentID *string `json:"parent_id,omitempty"` Icon string `json:"icon,omitempty"` Order int `json:"order"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // CategoryTreeDTO represents a category with its subcategories and notes type CategoryTreeDTO struct { *CategoryDTO Subcategories []*CategoryTreeDTO `json:"subcategories"` Notes []*NoteListItemDTO `json:"notes"` TaskLists []*TaskListDTO `json:"task_lists"` } // NewCategoryDTO creates a DTO from a category entity func NewCategoryDTO(category *entities.Category) *CategoryDTO { var parentID *string if category.ParentID != nil { id := category.ParentID.Hex() parentID = &id } return &CategoryDTO{ ID: category.ID.Hex(), SpaceID: category.SpaceID.Hex(), Name: category.Name, Description: category.Description, ParentID: parentID, Icon: category.Icon, Order: category.Order, CreatedAt: category.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: category.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // ========== TASK DTOs ========== // CreateTaskRequest represents task creation input. type CreateTaskRequest struct { Title string `json:"title" validate:"required,min=1,max=255"` Description string `json:"description" validate:"max=2000"` TaskListID string `json:"task_list_id" validate:"required"` StatusID string `json:"status_id" validate:"required"` ParentTaskID *string `json:"parent_task_id,omitempty"` NoteLinks []string `json:"note_links"` } // UpdateTaskRequest represents task update input. type UpdateTaskRequest struct { Title *string `json:"title,omitempty"` Description *string `json:"description,omitempty"` TaskListID *string `json:"task_list_id,omitempty"` StatusID *string `json:"status_id,omitempty"` ParentTaskID *string `json:"parent_task_id,omitempty"` NoteLinks []string `json:"note_links,omitempty"` } // TaskTransitionRequest allows moving task status by one step. type TaskTransitionRequest struct { Direction string `json:"direction" validate:"required,oneof=forward backward"` } // LinkTaskNoteRequest links/unlinks a note from a task. type LinkTaskNoteRequest struct { NoteID string `json:"note_id" validate:"required"` } // TaskDTO represents a task in API responses. type TaskDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` Title string `json:"title"` Description string `json:"description"` TaskListID string `json:"task_list_id"` StatusID string `json:"status_id"` ParentTaskID *string `json:"parent_task_id,omitempty"` Depth int `json:"depth"` NoteLinks []string `json:"note_links"` CreatedBy string `json:"created_by"` UpdatedBy string `json:"updated_by"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // TaskWithStatusDTO includes status details and child tasks for detail views. type TaskWithStatusDTO struct { *TaskDTO StatusName string `json:"status_name"` StatusColor string `json:"status_color,omitempty"` StatusOrder int `json:"status_order"` Subtasks []*TaskDTO `json:"subtasks"` } // CreateTaskStatusRequest represents task status creation input. type CreateTaskStatusRequest struct { Name string `json:"name" validate:"required,min=1,max=100"` Color string `json:"color,omitempty" validate:"max=20"` } // UpdateTaskStatusRequest represents task status updates. type UpdateTaskStatusRequest struct { Name string `json:"name" validate:"required,min=1,max=100"` Color string `json:"color,omitempty" validate:"max=20"` } // ReorderTaskStatusesRequest represents a full ordered status ID list. type ReorderTaskStatusesRequest struct { OrderedStatusIDs []string `json:"ordered_status_ids"` } // TaskStatusDTO represents a task status in API responses. type TaskStatusDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` Name string `json:"name"` Color string `json:"color,omitempty"` Order int `json:"order"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // CreateTaskListRequest represents task list creation input. type CreateTaskListRequest struct { Name string `json:"name" validate:"required,min=1,max=120"` Description string `json:"description" validate:"max=500"` CategoryID *string `json:"category_id,omitempty"` } // UpdateTaskListRequest represents task list update input. type UpdateTaskListRequest struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` CategoryID *string `json:"category_id,omitempty"` } // TaskListDTO represents a task list in API responses. type TaskListDTO struct { ID string `json:"id"` SpaceID string `json:"space_id"` CategoryID *string `json:"category_id,omitempty"` Name string `json:"name"` Description string `json:"description"` CreatedBy string `json:"created_by"` UpdatedBy string `json:"updated_by"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // NewTaskDTO creates a DTO from a task entity. func NewTaskDTO(task *entities.Task) *TaskDTO { var parentTaskID *string if task.ParentTaskID != nil { id := task.ParentTaskID.Hex() parentTaskID = &id } noteLinks := make([]string, 0, len(task.NoteLinks)) for _, noteID := range task.NoteLinks { noteLinks = append(noteLinks, noteID.Hex()) } return &TaskDTO{ ID: task.ID.Hex(), SpaceID: task.SpaceID.Hex(), Title: task.Title, Description: task.Description, TaskListID: task.TaskListID.Hex(), StatusID: task.StatusID.Hex(), ParentTaskID: parentTaskID, Depth: task.Depth, NoteLinks: noteLinks, CreatedBy: task.CreatedBy.Hex(), UpdatedBy: task.UpdatedBy.Hex(), CreatedAt: task.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: task.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // NewTaskListDTO creates a DTO from a task list entity. func NewTaskListDTO(taskList *entities.TaskList) *TaskListDTO { var categoryID *string if taskList.CategoryID != nil { id := taskList.CategoryID.Hex() categoryID = &id } return &TaskListDTO{ ID: taskList.ID.Hex(), SpaceID: taskList.SpaceID.Hex(), CategoryID: categoryID, Name: taskList.Name, Description: taskList.Description, CreatedBy: taskList.CreatedBy.Hex(), UpdatedBy: taskList.UpdatedBy.Hex(), CreatedAt: taskList.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: taskList.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // NewTaskStatusDTO creates a DTO from a task status entity. func NewTaskStatusDTO(status *entities.TaskStatus) *TaskStatusDTO { return &TaskStatusDTO{ ID: status.ID.Hex(), SpaceID: status.SpaceID.Hex(), Name: status.Name, Color: status.Color, Order: status.Order, CreatedAt: status.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: status.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // ========== ERROR DTOs ========== // ErrorResponse represents an error response type ErrorResponse struct { Error string `json:"error"` Message string `json:"message"` Code int `json:"code"` } // ValidationError represents a validation error type ValidationError struct { Field string `json:"field"` Message string `json:"message"` } // ValidationErrorResponse represents multiple validation errors type ValidationErrorResponse struct { Errors []ValidationError `json:"errors"` }