347 lines
9.8 KiB
Go
347 lines
9.8 KiB
Go
// Path: Backend/internal/bootstrap/project_queries.go
|
|
|
|
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func (service *Service) loadPrimaryOrganization(ctx context.Context) (*OrganizationRecord, error) {
|
|
var record OrganizationRecord
|
|
err := service.db.Pool.QueryRow(ctx, `
|
|
SELECT id::text, name, slug
|
|
FROM organizations
|
|
ORDER BY CASE WHEN slug = $1 THEN 0 ELSE 1 END, created_at ASC
|
|
LIMIT 1;
|
|
`, primaryOrganizationSlug).Scan(&record.ID, &record.Name, &record.Slug)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &record, nil
|
|
}
|
|
|
|
func (service *Service) loadPrimaryDepartment(ctx context.Context) (*DepartmentRecord, error) {
|
|
var record DepartmentRecord
|
|
err := service.db.Pool.QueryRow(ctx, `
|
|
SELECT id::text, organization_id::text, name, slug
|
|
FROM departments
|
|
ORDER BY CASE WHEN slug = $1 THEN 0 ELSE 1 END, created_at ASC
|
|
LIMIT 1;
|
|
`, primaryDepartmentSlug).Scan(&record.ID, &record.OrganizationID, &record.Name, &record.Slug)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &record, nil
|
|
}
|
|
|
|
func (service *Service) loadPrimaryTeam(ctx context.Context) (*TeamRecord, error) {
|
|
var record TeamRecord
|
|
err := service.db.Pool.QueryRow(ctx, `
|
|
SELECT id::text, organization_id::text, department_id::text, name, slug
|
|
FROM teams
|
|
ORDER BY CASE WHEN slug = $1 THEN 0 ELSE 1 END, created_at ASC
|
|
LIMIT 1;
|
|
`, primaryTeamSlug).Scan(&record.ID, &record.OrganizationID, &record.DepartmentID, &record.Name, &record.Slug)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &record, nil
|
|
}
|
|
|
|
func (service *Service) loadPrimaryProject(ctx context.Context) (*ProjectRecord, error) {
|
|
var record ProjectRecord
|
|
err := service.db.Pool.QueryRow(ctx, `
|
|
SELECT id::text, organization_id::text, department_id::text, team_id::text, name, slug
|
|
FROM projects
|
|
ORDER BY CASE WHEN slug = $1 THEN 0 ELSE 1 END, created_at ASC
|
|
LIMIT 1;
|
|
`, primaryProjectSlug).Scan(&record.ID, &record.OrganizationID, &record.DepartmentID, &record.TeamID, &record.Name, &record.Slug)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &record, nil
|
|
}
|
|
|
|
func (service *Service) listOrganizations(ctx context.Context) ([]OrganizationRecord, error) {
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT id::text, name, slug
|
|
FROM organizations
|
|
ORDER BY created_at ASC;
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []OrganizationRecord
|
|
for rows.Next() {
|
|
var record OrganizationRecord
|
|
if err := rows.Scan(&record.ID, &record.Name, &record.Slug); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (service *Service) listDepartments(ctx context.Context) ([]DepartmentRecord, error) {
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT id::text, organization_id::text, name, slug
|
|
FROM departments
|
|
ORDER BY created_at ASC;
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []DepartmentRecord
|
|
for rows.Next() {
|
|
var record DepartmentRecord
|
|
if err := rows.Scan(&record.ID, &record.OrganizationID, &record.Name, &record.Slug); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (service *Service) listTeams(ctx context.Context) ([]TeamRecord, error) {
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT id::text, organization_id::text, department_id::text, name, slug
|
|
FROM teams
|
|
ORDER BY created_at ASC;
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []TeamRecord
|
|
for rows.Next() {
|
|
var record TeamRecord
|
|
if err := rows.Scan(&record.ID, &record.OrganizationID, &record.DepartmentID, &record.Name, &record.Slug); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (service *Service) listProjects(ctx context.Context) ([]ProjectRecord, error) {
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT id::text, organization_id::text, department_id::text, team_id::text, name, slug
|
|
FROM projects
|
|
ORDER BY created_at ASC;
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []ProjectRecord
|
|
for rows.Next() {
|
|
var record ProjectRecord
|
|
if err := rows.Scan(&record.ID, &record.OrganizationID, &record.DepartmentID, &record.TeamID, &record.Name, &record.Slug); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (service *Service) loadProjectByID(ctx context.Context, projectID string) (*ProjectRecord, error) {
|
|
var record ProjectRecord
|
|
err := service.db.Pool.QueryRow(ctx, `
|
|
SELECT id::text, organization_id::text, department_id::text, team_id::text, name, slug
|
|
FROM projects
|
|
WHERE id = $1::uuid
|
|
LIMIT 1;
|
|
`, projectID).Scan(&record.ID, &record.OrganizationID, &record.DepartmentID, &record.TeamID, &record.Name, &record.Slug)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, ErrProjectNotFound
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &record, nil
|
|
}
|
|
|
|
func (service *Service) GetProjectHierarchyFolders(ctx context.Context, projectID string) ([]ProjectHierarchyFolderRecord, error) {
|
|
return service.getProjectHierarchyFoldersByRootPath(ctx, projectID, projectHierarchyRootPath)
|
|
}
|
|
|
|
func (service *Service) GetProjectTreeFolders(ctx context.Context, projectID string) ([]ProjectHierarchyFolderRecord, error) {
|
|
return service.getProjectHierarchyFoldersByRootPath(ctx, projectID, projectTreeRootPath)
|
|
}
|
|
|
|
func (service *Service) GetProjectTreeNodes(ctx context.Context, projectID string) ([]ProjectTreeNodeRecord, error) {
|
|
return service.getProjectTreeNodesByRootPath(ctx, projectID, projectTreeRootPath)
|
|
}
|
|
|
|
func (service *Service) getProjectHierarchyFoldersByRootPath(
|
|
ctx context.Context,
|
|
projectID string,
|
|
rootPath func(projectSlug string) string,
|
|
) ([]ProjectHierarchyFolderRecord, error) {
|
|
project, err := service.loadProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rootParentPath := rootPath(project.Slug)
|
|
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT
|
|
COALESCE(folder_meta.resource_id, ''),
|
|
directories.path,
|
|
COALESCE(directories.parent_path, ''),
|
|
COALESCE(folder_meta.resource_name, directories.resource_name, '')
|
|
FROM posix_nodes AS directories
|
|
LEFT JOIN posix_nodes AS folder_meta
|
|
ON folder_meta.path = directories.path || '/folder.cbor'
|
|
AND folder_meta.node_kind = 'file'::posix_node_kind
|
|
WHERE directories.node_kind = 'directory'::posix_node_kind
|
|
AND directories.logical_type = 'hierarchy_folder'
|
|
AND directories.project_slug = $1
|
|
AND directories.path LIKE $2
|
|
ORDER BY directories.depth ASC, directories.path ASC;
|
|
`, project.Slug, rootParentPath+"/%")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var folderRows []projectHierarchyFolderRow
|
|
for rows.Next() {
|
|
var row projectHierarchyFolderRow
|
|
if err := rows.Scan(&row.ID, &row.Path, &row.ParentPath, &row.Label); err != nil {
|
|
return nil, err
|
|
}
|
|
folderRows = append(folderRows, row)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
folders := buildProjectHierarchyFolderTree(folderRows, rootParentPath)
|
|
folderOrder := service.readProjectFolderOrder(project.Slug, rootParentPath)
|
|
|
|
return applyProjectHierarchyFolderOrdering(folders, folderOrder), nil
|
|
}
|
|
|
|
func (service *Service) getProjectTreeNodesByRootPath(
|
|
ctx context.Context,
|
|
projectID string,
|
|
rootPath func(projectSlug string) string,
|
|
) ([]ProjectTreeNodeRecord, error) {
|
|
project, err := service.loadProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rootParentPath := rootPath(project.Slug)
|
|
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT
|
|
COALESCE(node_meta.resource_id, ''),
|
|
directories.path,
|
|
COALESCE(directories.parent_path, ''),
|
|
COALESCE(node_meta.resource_name, directories.resource_name, ''),
|
|
directories.logical_type,
|
|
COALESCE(node_meta.content_json->>'type', directories.content_json->>'type', '')
|
|
FROM posix_nodes AS directories
|
|
LEFT JOIN posix_nodes AS node_meta
|
|
ON node_meta.node_kind = 'file'::posix_node_kind
|
|
AND (
|
|
(directories.logical_type = 'hierarchy_folder' AND node_meta.path = directories.path || '/folder.cbor')
|
|
OR (directories.logical_type = 'item' AND node_meta.path = directories.path || '/item.cbor')
|
|
)
|
|
WHERE directories.node_kind = 'directory'::posix_node_kind
|
|
AND directories.logical_type IN ('hierarchy_folder', 'item')
|
|
AND directories.project_slug = $1
|
|
AND directories.path LIKE $2
|
|
ORDER BY directories.depth ASC, directories.path ASC;
|
|
`, project.Slug, rootParentPath+"/%")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var nodeRows []projectTreeNodeRow
|
|
for rows.Next() {
|
|
var row projectTreeNodeRow
|
|
if err := rows.Scan(&row.ID, &row.Path, &row.ParentPath, &row.Label, &row.Kind, &row.ItemType); err != nil {
|
|
return nil, err
|
|
}
|
|
nodeRows = append(nodeRows, row)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodes := buildProjectTreeNodeTree(nodeRows, rootParentPath)
|
|
folderOrder := service.readProjectFolderOrder(project.Slug, rootParentPath)
|
|
|
|
return applyProjectTreeNodeOrdering(nodes, folderOrder), nil
|
|
}
|
|
|
|
func (service *Service) listWorkspaces(ctx context.Context) ([]WorkspaceRecord, error) {
|
|
rows, err := service.db.Pool.Query(ctx, `
|
|
SELECT id::text, organization_id::text, name, slug, kind::text, department_id::text, team_id::text, project_id::text
|
|
FROM workspaces
|
|
ORDER BY created_at ASC;
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []WorkspaceRecord
|
|
for rows.Next() {
|
|
var record WorkspaceRecord
|
|
if err := rows.Scan(&record.ID, &record.OrganizationID, &record.Name, &record.Slug, &record.Kind, &record.DepartmentID, &record.TeamID, &record.ProjectID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, rows.Err()
|
|
}
|