package client import ( "context" "errors" "fmt" "regexp" "strings" "queryorchestration/internal/database/repository" ) // RootFolderPath is the path used for the auto-created root folder per client. // All other folders are descendants of this root folder. const RootFolderPath = "/" // SystemEmail is the email used for system-created entities like root folders. const SystemEmail = "system@doczy.local" type CreateParams struct { Name string ID string } // Create creates a new client and automatically creates a root folder ("/") for it. // The root folder serves as the parent for all top-level folders and as the default // location for documents uploaded without a folder path. // Both operations are performed atomically within a database transaction. func (s *Service) Create(ctx context.Context, params CreateParams) (string, error) { err := s.normalizeCreate(¶ms) if err != nil { return "", err } err = s.cfg.ExecuteDBTransaction(ctx, func(ctx context.Context, q *repository.Queries) error { // Create the client err := q.CreateClient(ctx, &repository.CreateClientParams{ Name: params.Name, Clientid: params.ID, }) if err != nil { return fmt.Errorf("failed to create client: %w", err) } // Create the root folder for this client // The root folder has path "/" and no parent (parentId is nil) _, err = q.CreateFolder(ctx, &repository.CreateFolderParams{ Path: RootFolderPath, Parentid: nil, Clientid: params.ID, Createdby: SystemEmail, }) if err != nil { return fmt.Errorf("failed to create root folder: %w", err) } return nil }) if err != nil { return "", err } return params.ID, nil } func (s *Service) normalizeCreate(params *CreateParams) error { err := normalizeName(¶ms.Name) if err != nil { return err } err = normalizeID(¶ms.ID) if err != nil { return err } return nil } // NOTE: Make sure the char ~ never makes way in as a valid client id character // Reference bucket key filename const CLIENT_ID_REGEX = `[a-zA-Z0-9\_#-]+` func normalizeID(id *string) error { if id == nil { return nil } trim := strings.TrimSpace(*id) patternStr := fmt.Sprintf("^%s$", CLIENT_ID_REGEX) pattern := regexp.MustCompile(patternStr) if !pattern.MatchString(trim) { return errors.New("invalid external id") } *id = trim return nil }