package drivestore import ( "context" "errors" "fmt" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) var ErrOrgFolderNotFound = errors.New("org folder not found") var ErrMountNotFound = errors.New("mount not found") type OrgFolder struct { ID string `json:"id"` OrgSlug string `json:"org_slug"` NCFolderID int `json:"nc_folder_id"` MountPoint string `json:"mount_point"` QuotaBytes *int64 `json:"quota_bytes,omitempty"` AutoProvisioned bool `json:"auto_provisioned"` CreatedBy string `json:"created_by"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Mount struct { ID string `json:"id"` Scope string `json:"scope"` OwnerUserID *string `json:"owner_user_id,omitempty"` OrgSlug *string `json:"org_slug,omitempty"` NCMountID *int `json:"nc_mount_id,omitempty"` DisplayName string `json:"display_name"` BackendType string `json:"backend_type"` MountPoint string `json:"mount_point"` Status string `json:"status"` LastError string `json:"last_error,omitempty"` ConfigEnc []byte `json:"-"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Store struct { db *pgxpool.Pool } func NewStore(db *pgxpool.Pool) *Store { return &Store{db: db} } func (s *Store) ListOrgFolders(ctx context.Context) ([]OrgFolder, error) { if s.db == nil { return nil, fmt.Errorf("database not configured") } rows, err := s.db.Query(ctx, ` SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by, created_at, updated_at FROM drive_org_folders ORDER BY mount_point ASC `) if err != nil { return nil, err } defer rows.Close() var out []OrgFolder for rows.Next() { var item OrgFolder if err := rows.Scan( &item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint, &item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy, &item.CreatedAt, &item.UpdatedAt, ); err != nil { return nil, err } out = append(out, item) } return out, rows.Err() } func (s *Store) GetOrgFolder(ctx context.Context, id string) (OrgFolder, error) { if s.db == nil { return OrgFolder{}, fmt.Errorf("database not configured") } var item OrgFolder err := s.db.QueryRow(ctx, ` SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by, created_at, updated_at FROM drive_org_folders WHERE id = $1 `, id).Scan( &item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint, &item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy, &item.CreatedAt, &item.UpdatedAt, ) if errors.Is(err, pgx.ErrNoRows) { return OrgFolder{}, ErrOrgFolderNotFound } if err != nil { return OrgFolder{}, err } return item, nil } func (s *Store) GetOrgFolderBySlug(ctx context.Context, orgSlug string) (OrgFolder, error) { if s.db == nil { return OrgFolder{}, fmt.Errorf("database not configured") } var item OrgFolder err := s.db.QueryRow(ctx, ` SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by, created_at, updated_at FROM drive_org_folders WHERE org_slug = $1 `, orgSlug).Scan( &item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint, &item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy, &item.CreatedAt, &item.UpdatedAt, ) if errors.Is(err, pgx.ErrNoRows) { return OrgFolder{}, ErrOrgFolderNotFound } if err != nil { return OrgFolder{}, err } return item, nil } type CreateOrgFolderParams struct { OrgSlug string NCFolderID int MountPoint string QuotaBytes *int64 AutoProvisioned bool CreatedBy string } func (s *Store) CreateOrgFolder(ctx context.Context, p CreateOrgFolderParams) (OrgFolder, error) { if s.db == nil { return OrgFolder{}, fmt.Errorf("database not configured") } id := uuid.NewString() var item OrgFolder err := s.db.QueryRow(ctx, ` INSERT INTO drive_org_folders ( id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by, created_at, updated_at `, id, p.OrgSlug, p.NCFolderID, p.MountPoint, p.QuotaBytes, p.AutoProvisioned, p.CreatedBy).Scan( &item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint, &item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy, &item.CreatedAt, &item.UpdatedAt, ) if err != nil { return OrgFolder{}, err } return item, nil } func (s *Store) UpdateOrgFolder(ctx context.Context, id, mountPoint string, quotaBytes *int64) (OrgFolder, error) { if s.db == nil { return OrgFolder{}, fmt.Errorf("database not configured") } var item OrgFolder err := s.db.QueryRow(ctx, ` UPDATE drive_org_folders SET mount_point = $2, quota_bytes = $3, updated_at = NOW() WHERE id = $1 RETURNING id, org_slug, nc_folder_id, mount_point, quota_bytes, auto_provisioned, created_by, created_at, updated_at `, id, mountPoint, quotaBytes).Scan( &item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint, &item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy, &item.CreatedAt, &item.UpdatedAt, ) if errors.Is(err, pgx.ErrNoRows) { return OrgFolder{}, ErrOrgFolderNotFound } if err != nil { return OrgFolder{}, err } return item, nil } func (s *Store) DeleteOrgFolder(ctx context.Context, id string) error { if s.db == nil { return fmt.Errorf("database not configured") } tag, err := s.db.Exec(ctx, `DELETE FROM drive_org_folders WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrOrgFolderNotFound } return nil } func (s *Store) ListOrgMounts(ctx context.Context) ([]Mount, error) { if s.db == nil { return nil, fmt.Errorf("database not configured") } rows, err := s.db.Query(ctx, ` SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name, backend_type, mount_point, status, last_error, created_at, updated_at FROM drive_mounts WHERE scope = 'org' ORDER BY display_name ASC `) if err != nil { return nil, err } defer rows.Close() return scanMounts(rows) } func (s *Store) ListMountsForUser(ctx context.Context, ownerUserID string, orgSlugs []string) ([]Mount, error) { if s.db == nil { return nil, fmt.Errorf("database not configured") } rows, err := s.db.Query(ctx, ` SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name, backend_type, mount_point, status, last_error, created_at, updated_at FROM drive_mounts WHERE (scope = 'user' AND owner_user_id = $1::uuid) OR ( scope = 'org' AND (cardinality($2::text[]) = 0 OR org_slug = ANY($2)) ) ORDER BY display_name ASC `, ownerUserID, orgSlugs) if err != nil { return nil, err } defer rows.Close() return scanMounts(rows) } func (s *Store) GetMount(ctx context.Context, id string) (Mount, error) { if s.db == nil { return Mount{}, fmt.Errorf("database not configured") } row := s.db.QueryRow(ctx, ` SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name, backend_type, mount_point, status, last_error, created_at, updated_at FROM drive_mounts WHERE id = $1 `, id) item, err := scanMount(row) if errors.Is(err, pgx.ErrNoRows) { return Mount{}, ErrMountNotFound } return item, err } type CreateMountParams struct { Scope string OwnerUserID *string OrgSlug *string NCMountID *int DisplayName string BackendType string MountPoint string Status string ConfigEnc []byte } func (s *Store) CreateMount(ctx context.Context, p CreateMountParams) (Mount, error) { if s.db == nil { return Mount{}, fmt.Errorf("database not configured") } id := uuid.NewString() status := p.Status if status == "" { status = "active" } row := s.db.QueryRow(ctx, ` INSERT INTO drive_mounts ( id, scope, owner_user_id, org_slug, nc_mount_id, display_name, backend_type, mount_point, status, config_encrypted ) VALUES ($1, $2, $3::uuid, $4, $5, $6, $7, $8, $9, $10) RETURNING id, scope, owner_user_id, org_slug, nc_mount_id, display_name, backend_type, mount_point, status, last_error, created_at, updated_at `, id, p.Scope, p.OwnerUserID, p.OrgSlug, p.NCMountID, p.DisplayName, p.BackendType, p.MountPoint, status, p.ConfigEnc) return scanMount(row) } func (s *Store) UpdateMountConfig(ctx context.Context, id string, configEnc []byte) error { if s.db == nil { return fmt.Errorf("database not configured") } tag, err := s.db.Exec(ctx, ` UPDATE drive_mounts SET config_encrypted = $2, updated_at = NOW() WHERE id = $1 `, id, configEnc) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrMountNotFound } return nil } func (s *Store) GetMountConfig(ctx context.Context, id string) ([]byte, error) { if s.db == nil { return nil, fmt.Errorf("database not configured") } var config []byte err := s.db.QueryRow(ctx, `SELECT config_encrypted FROM drive_mounts WHERE id = $1`, id).Scan(&config) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrMountNotFound } return config, err } func (s *Store) UpdateMountStatus(ctx context.Context, id, status, lastError string, ncMountID *int) error { if s.db == nil { return fmt.Errorf("database not configured") } tag, err := s.db.Exec(ctx, ` UPDATE drive_mounts SET status = $2, last_error = $3, nc_mount_id = COALESCE($4, nc_mount_id), updated_at = NOW() WHERE id = $1 `, id, status, lastError, ncMountID) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrMountNotFound } return nil } func (s *Store) DeleteMount(ctx context.Context, id string) error { if s.db == nil { return fmt.Errorf("database not configured") } tag, err := s.db.Exec(ctx, `DELETE FROM drive_mounts WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrMountNotFound } return nil } type scannable interface { Scan(dest ...any) error } func scanMount(row scannable) (Mount, error) { var item Mount err := row.Scan( &item.ID, &item.Scope, &item.OwnerUserID, &item.OrgSlug, &item.NCMountID, &item.DisplayName, &item.BackendType, &item.MountPoint, &item.Status, &item.LastError, &item.CreatedAt, &item.UpdatedAt, ) return item, err } func scanMounts(rows pgx.Rows) ([]Mount, error) { var out []Mount for rows.Next() { item, err := scanMount(rows) if err != nil { return nil, err } out = append(out, item) } return out, rows.Err() }