package drive import ( "net/url" "strings" "github.com/ultisuite/ulti-backend/internal/api/apivalidate" "github.com/ultisuite/ulti-backend/internal/nextcloud" ) const maxJSONRequestBody = 32 << 10 type moveRequest struct { Source string `json:"source"` Destination string `json:"destination"` SourceRoot string `json:"source_root,omitempty"` SourceRootID string `json:"source_root_id,omitempty"` DestinationRoot string `json:"destination_root,omitempty"` DestinationRootID string `json:"destination_root_id,omitempty"` } type copyRequest struct { Source string `json:"source"` Destination string `json:"destination"` SourceRoot string `json:"source_root,omitempty"` SourceRootID string `json:"source_root_id,omitempty"` DestinationRoot string `json:"destination_root,omitempty"` DestinationRootID string `json:"destination_root_id,omitempty"` } type renameRequest struct { Path string `json:"path"` NewName string `json:"new_name"` Root string `json:"root,omitempty"` RootID string `json:"root_id,omitempty"` } type favoriteRequest struct { Path string `json:"path"` Favorite bool `json:"favorite"` Root string `json:"root,omitempty"` RootID string `json:"root_id,omitempty"` } type createMountRequest struct { Scope string `json:"scope"` OrgSlug string `json:"org_slug,omitempty"` DisplayName string `json:"display_name"` BackendType string `json:"backend_type"` WebDAV *nextcloud.WebDAVMountConfig `json:"webdav,omitempty"` OAuthBackend string `json:"oauth_backend,omitempty"` OAuthAuth string `json:"oauth_auth,omitempty"` } func validateMoveRequest(req *moveRequest) *apivalidate.ValidationError { var details []apivalidate.FieldDetail if strings.TrimSpace(req.Source) == "" { details = append(details, apivalidate.FieldDetail{Field: "source", Message: "required"}) } if strings.TrimSpace(req.Destination) == "" { details = append(details, apivalidate.FieldDetail{Field: "destination", Message: "required"}) } if len(details) == 0 { return nil } return apivalidate.NewValidationError(details...) } func validateCopyRequest(req *copyRequest) *apivalidate.ValidationError { var details []apivalidate.FieldDetail if strings.TrimSpace(req.Source) == "" { details = append(details, apivalidate.FieldDetail{Field: "source", Message: "required"}) } if strings.TrimSpace(req.Destination) == "" { details = append(details, apivalidate.FieldDetail{Field: "destination", Message: "required"}) } if len(details) == 0 { return nil } return apivalidate.NewValidationError(details...) } func validateRenameRequest(req *renameRequest) *apivalidate.ValidationError { var details []apivalidate.FieldDetail if strings.TrimSpace(req.Path) == "" { details = append(details, apivalidate.FieldDetail{Field: "path", Message: "required"}) } newName := strings.TrimSpace(req.NewName) if newName == "" { details = append(details, apivalidate.FieldDetail{Field: "new_name", Message: "required"}) } else if strings.Contains(newName, "/") { details = append(details, apivalidate.FieldDetail{Field: "new_name", Message: "invalid"}) } if len(details) == 0 { return nil } return apivalidate.NewValidationError(details...) } type createShareRequest struct { Path string `json:"path"` ShareType int `json:"share_type"` Permissions int `json:"permissions"` Role string `json:"role"` Mode string `json:"mode"` ShareWith string `json:"share_with"` Note string `json:"note"` SendMail *bool `json:"send_mail"` Root string `json:"root,omitempty"` RootID string `json:"root_id,omitempty"` } func sharePermissionsForRole(role string) (int, bool) { switch strings.TrimSpace(strings.ToLower(role)) { case "owner": return 31, true case "editor": return 15, true case "viewer": return 1, true default: return 0, false } } func validateCreateShareRequest(req *createShareRequest) *apivalidate.ValidationError { var details []apivalidate.FieldDetail if strings.TrimSpace(req.Path) == "" { details = append(details, apivalidate.FieldDetail{Field: "path", Message: "required"}) } if role := strings.TrimSpace(req.Role); role != "" { if _, ok := sharePermissionsForRole(role); !ok { details = append(details, apivalidate.FieldDetail{Field: "role", Message: "invalid"}) } } mode := strings.TrimSpace(strings.ToLower(req.Mode)) switch mode { case "", "public", "internal": case "contact": if strings.TrimSpace(req.ShareWith) == "" { details = append(details, apivalidate.FieldDetail{Field: "share_with", Message: "required"}) } default: details = append(details, apivalidate.FieldDetail{Field: "mode", Message: "invalid"}) } if len(details) == 0 { return nil } return apivalidate.NewValidationError(details...) } type restoreTrashRequest struct { Name string `json:"name"` } func validateRestoreTrashRequest(req *restoreTrashRequest) *apivalidate.ValidationError { if strings.TrimSpace(req.Name) == "" { return apivalidate.NewValidationError(apivalidate.FieldDetail{ Field: "name", Message: "required", }) } return nil } type deleteTrashRequest struct { Name string `json:"name"` } func validateDeleteTrashRequest(req *deleteTrashRequest) *apivalidate.ValidationError { if strings.TrimSpace(req.Name) == "" { return apivalidate.NewValidationError(apivalidate.FieldDetail{ Field: "name", Message: "required", }) } return nil } func validateFavoriteRequest(req *favoriteRequest) *apivalidate.ValidationError { if strings.TrimSpace(req.Path) == "" { return apivalidate.NewValidationError(apivalidate.FieldDetail{ Field: "path", Message: "required", }) } return nil } type updateShareRequest struct { Permissions int `json:"permissions"` Role string `json:"role"` ExpireDate string `json:"expire_date"` Password string `json:"password"` } type newFileRequest struct { ParentPath string `json:"parent_path"` Name string `json:"name"` Kind string `json:"kind"` } func validateNewFileRequest(req *newFileRequest) *apivalidate.ValidationError { var details []apivalidate.FieldDetail if strings.TrimSpace(req.Name) == "" { details = append(details, apivalidate.FieldDetail{Field: "name", Message: "required"}) } k := strings.TrimSpace(strings.ToLower(req.Kind)) if k != "document" && k != "spreadsheet" && k != "presentation" && k != "drawing" { details = append(details, apivalidate.FieldDetail{Field: "kind", Message: "invalid"}) } if len(details) > 0 { return apivalidate.NewValidationError(details...) } return nil } func validatePath(path string) *apivalidate.ValidationError { if strings.TrimSpace(path) == "" { return apivalidate.NewValidationError(apivalidate.FieldDetail{ Field: "path", Message: "required", }) } return nil } const mountOAuthCallbackPath = "/drive/mounts/oauth/callback" func validateMountOAuthRedirectURI(raw string) error { raw = strings.TrimSpace(raw) if raw == "" { return ErrInvalid } parsed, err := url.Parse(raw) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return ErrInvalid } if parsed.Scheme != "http" && parsed.Scheme != "https" { return ErrInvalid } path := strings.TrimRight(parsed.Path, "/") if path != mountOAuthCallbackPath { return ErrInvalid } return nil }