mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 22:38:27 +00:00
429 lines
10 KiB
TypeScript
429 lines
10 KiB
TypeScript
import { logger } from "./logger.service";
|
|
import {
|
|
type SpaceNode,
|
|
type CreateSpaceParams,
|
|
type UpdateSpaceParams,
|
|
type SpaceAssignmentResult,
|
|
} from "@core/types";
|
|
import { type Space } from "@prisma/client";
|
|
|
|
import { triggerSpaceAssignment } from "~/trigger/spaces/space-assignment";
|
|
import {
|
|
assignStatementsToSpace,
|
|
createSpace,
|
|
deleteSpace,
|
|
getSpace,
|
|
getSpaceStatements,
|
|
initializeStatementSpaceIds,
|
|
removeStatementsFromSpace,
|
|
updateSpace,
|
|
} from "./graphModels/space";
|
|
import { prisma } from "~/trigger/utils/prisma";
|
|
|
|
export class SpaceService {
|
|
/**
|
|
* Create a new space for a user
|
|
*/
|
|
async createSpace(params: CreateSpaceParams): Promise<Space> {
|
|
logger.info(`Creating space "${params.name}" for user ${params.userId}`);
|
|
|
|
// Validate input
|
|
if (!params.name || params.name.trim().length === 0) {
|
|
throw new Error("Space name is required");
|
|
}
|
|
|
|
if (params.name.length > 100) {
|
|
throw new Error("Space name too long (max 100 characters)");
|
|
}
|
|
|
|
if (params.description && params.description.length > 1000) {
|
|
throw new Error("Space description too long (max 1000 characters)");
|
|
}
|
|
|
|
// Check for duplicate names
|
|
const existingSpaces = await prisma.space.findMany({
|
|
where: {
|
|
name: params.name,
|
|
workspaceId: params.workspaceId,
|
|
},
|
|
});
|
|
if (existingSpaces.length > 0) {
|
|
throw new Error("A space with this name already exists");
|
|
}
|
|
|
|
const space = await prisma.space.create({
|
|
data: {
|
|
name: params.name.trim(),
|
|
description: params.description?.trim(),
|
|
workspaceId: params.workspaceId,
|
|
status: "pending",
|
|
},
|
|
});
|
|
|
|
await createSpace(
|
|
space.id,
|
|
params.name.trim(),
|
|
params.description?.trim(),
|
|
params.userId,
|
|
);
|
|
|
|
logger.info(`Created space ${space.id} successfully`);
|
|
|
|
// Trigger automatic LLM assignment for the new space
|
|
try {
|
|
await triggerSpaceAssignment({
|
|
userId: params.userId,
|
|
workspaceId: params.workspaceId,
|
|
mode: "new_space",
|
|
newSpaceId: space.id,
|
|
batchSize: 25, // Analyze recent statements for the new space
|
|
});
|
|
|
|
logger.info(`Triggered LLM space assignment for new space ${space.id}`);
|
|
} catch (error) {
|
|
// Don't fail space creation if LLM assignment fails
|
|
logger.warn(
|
|
`Failed to trigger LLM assignment for space ${space.id}:`,
|
|
error as Record<string, unknown>,
|
|
);
|
|
}
|
|
|
|
return space;
|
|
}
|
|
|
|
/**
|
|
* Get all spaces for a user
|
|
*/
|
|
async getUserSpaces(userId: string): Promise<Space[]> {
|
|
const user = await prisma.user.findFirst({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
Workspace: true,
|
|
},
|
|
});
|
|
|
|
return await prisma.space.findMany({
|
|
where: {
|
|
workspaceId: user?.Workspace?.id,
|
|
},
|
|
});
|
|
}
|
|
|
|
async getSpaceByName(name: string, userId: string) {
|
|
const user = await prisma.user.findFirst({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
Workspace: true,
|
|
},
|
|
});
|
|
|
|
const space = await prisma.space.findFirst({
|
|
where: {
|
|
name: name,
|
|
workspaceId: user?.Workspace?.id,
|
|
},
|
|
});
|
|
|
|
return space;
|
|
}
|
|
|
|
/**
|
|
* Get a specific space by ID
|
|
*/
|
|
async getSpace(spaceId: string, userId: string) {
|
|
const space = await prisma.space.findUnique({
|
|
where: {
|
|
id: spaceId,
|
|
},
|
|
});
|
|
|
|
const nodeData = await getSpace(spaceId, userId);
|
|
|
|
return {
|
|
...(nodeData as SpaceNode),
|
|
...space,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update a space
|
|
*/
|
|
async updateSpace(
|
|
spaceId: string,
|
|
updates: UpdateSpaceParams,
|
|
userId: string,
|
|
): Promise<Space> {
|
|
logger.info(`Updating space ${spaceId} for user ${userId}`);
|
|
|
|
// Validate input
|
|
if (updates.name !== undefined) {
|
|
if (!updates.name || updates.name.trim().length === 0) {
|
|
throw new Error("Space name cannot be empty");
|
|
}
|
|
|
|
if (updates.name.length > 100) {
|
|
throw new Error("Space name too long (max 100 characters)");
|
|
}
|
|
|
|
// Check for duplicate names (excluding current space)
|
|
const existingSpaces = await prisma.space.findMany({
|
|
where: {
|
|
name: updates.name,
|
|
workspaceId: userId,
|
|
},
|
|
});
|
|
const duplicates = existingSpaces.filter((space) => space.id !== spaceId);
|
|
if (duplicates.length > 0) {
|
|
throw new Error("A space with this name already exists");
|
|
}
|
|
}
|
|
|
|
if (
|
|
updates.description !== undefined &&
|
|
updates.description.length > 1000
|
|
) {
|
|
throw new Error("Space description too long (max 1000 characters)");
|
|
}
|
|
|
|
const space = await prisma.space.update({
|
|
where: {
|
|
id: spaceId,
|
|
},
|
|
data: {
|
|
name: updates.name,
|
|
description: updates.description,
|
|
icon: updates.icon,
|
|
status: updates.status,
|
|
},
|
|
});
|
|
try {
|
|
await updateSpace(spaceId, updates, userId);
|
|
} catch (e) {
|
|
logger.info(`Nothing to update to graph`);
|
|
}
|
|
logger.info(`Updated space ${spaceId} successfully`);
|
|
return space;
|
|
}
|
|
|
|
/**
|
|
* Delete a space and clean up all statement references
|
|
*/
|
|
async deleteSpace(spaceId: string, userId: string): Promise<Space> {
|
|
logger.info(`Deleting space ${spaceId} for user ${userId}`);
|
|
|
|
const space = await prisma.space.delete({
|
|
where: {
|
|
id: spaceId,
|
|
},
|
|
});
|
|
|
|
if (space.name === "Profile") {
|
|
throw new Error("Bad request");
|
|
}
|
|
|
|
await deleteSpace(spaceId, userId);
|
|
|
|
logger.info(`Deleted space ${spaceId} successfully`);
|
|
|
|
return space;
|
|
}
|
|
|
|
/**
|
|
* Assign statements to a space
|
|
*/
|
|
async assignStatementsToSpace(
|
|
statementIds: string[],
|
|
spaceId: string,
|
|
userId: string,
|
|
): Promise<SpaceAssignmentResult> {
|
|
logger.info(
|
|
`Assigning ${statementIds.length} statements to space ${spaceId} for user ${userId}`,
|
|
);
|
|
|
|
// Validate input
|
|
if (statementIds.length === 0) {
|
|
throw new Error("No statement IDs provided");
|
|
}
|
|
|
|
if (statementIds.length > 1000) {
|
|
throw new Error("Too many statements (max 1000 per operation)");
|
|
}
|
|
|
|
const result = await assignStatementsToSpace(statementIds, spaceId, userId);
|
|
|
|
if (result.success) {
|
|
logger.info(
|
|
`Successfully assigned ${result.statementsUpdated} statements to space ${spaceId}`,
|
|
);
|
|
} else {
|
|
logger.warn(
|
|
`Failed to assign statements to space ${spaceId}: ${result.error}`,
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Remove statements from a space
|
|
*/
|
|
async removeStatementsFromSpace(
|
|
statementIds: string[],
|
|
spaceId: string,
|
|
userId: string,
|
|
): Promise<SpaceAssignmentResult> {
|
|
logger.info(
|
|
`Removing ${statementIds.length} statements from space ${spaceId} for user ${userId}`,
|
|
);
|
|
|
|
// Validate input
|
|
if (statementIds.length === 0) {
|
|
throw new Error("No statement IDs provided");
|
|
}
|
|
|
|
if (statementIds.length > 1000) {
|
|
throw new Error("Too many statements (max 1000 per operation)");
|
|
}
|
|
|
|
const result = await removeStatementsFromSpace(
|
|
statementIds,
|
|
spaceId,
|
|
userId,
|
|
);
|
|
|
|
if (result.success) {
|
|
logger.info(
|
|
`Successfully removed ${result.statementsUpdated} statements from space ${spaceId}`,
|
|
);
|
|
} else {
|
|
logger.warn(
|
|
`Failed to remove statements from space ${spaceId}: ${result.error}`,
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get all statements in a space
|
|
*/
|
|
async getSpaceStatements(spaceId: string, userId: string) {
|
|
logger.info(`Fetching statements for space ${spaceId} for user ${userId}`);
|
|
return await getSpaceStatements(spaceId, userId);
|
|
}
|
|
|
|
/**
|
|
* Search spaces by name
|
|
*/
|
|
async searchSpacesByName(
|
|
query: string,
|
|
workspaceId: string,
|
|
): Promise<Space[]> {
|
|
if (!query || query.trim().length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return await prisma.space.findMany({
|
|
where: {
|
|
workspaceId,
|
|
name: {
|
|
contains: query,
|
|
mode: "insensitive",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get spaces that contain specific statements
|
|
*/
|
|
async getSpacesForStatements(
|
|
statementIds: string[],
|
|
userId: string,
|
|
): Promise<{ statementId: string; spaces: Space[] }[]> {
|
|
const userSpaces = await this.getUserSpaces(userId);
|
|
const result: { statementId: string; spaces: Space[] }[] = [];
|
|
|
|
for (const statementId of statementIds) {
|
|
const spacesContainingStatement = [];
|
|
|
|
for (const space of userSpaces) {
|
|
const statements = await this.getSpaceStatements(space.id, userId);
|
|
if (statements.some((stmt) => stmt.uuid === statementId)) {
|
|
spacesContainingStatement.push(space);
|
|
}
|
|
}
|
|
|
|
result.push({
|
|
statementId,
|
|
spaces: spacesContainingStatement,
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Initialize spaceIds for existing statements (migration utility)
|
|
*/
|
|
async initializeSpaceIds(userId?: string): Promise<number> {
|
|
logger.info(
|
|
`Initializing spaceIds for ${userId ? `user ${userId}` : "all users"}`,
|
|
);
|
|
|
|
const updatedCount = await initializeStatementSpaceIds(userId);
|
|
|
|
logger.info(`Initialized spaceIds for ${updatedCount} statements`);
|
|
return updatedCount;
|
|
}
|
|
|
|
/**
|
|
* Validate space access
|
|
*/
|
|
async validateSpaceAccess(spaceId: string, userId: string): Promise<boolean> {
|
|
const space = await this.getSpace(spaceId, userId);
|
|
return space !== null && space.isActive;
|
|
}
|
|
|
|
/**
|
|
* Bulk assign statements to multiple spaces
|
|
*/
|
|
async bulkAssignStatements(
|
|
statementIds: string[],
|
|
spaceIds: string[],
|
|
userId: string,
|
|
): Promise<{ spaceId: string; result: SpaceAssignmentResult }[]> {
|
|
logger.info(
|
|
`Bulk assigning ${statementIds.length} statements to ${spaceIds.length} spaces for user ${userId}`,
|
|
);
|
|
|
|
const results: { spaceId: string; result: SpaceAssignmentResult }[] = [];
|
|
|
|
for (const spaceId of spaceIds) {
|
|
try {
|
|
const result = await this.assignStatementsToSpace(
|
|
statementIds,
|
|
spaceId,
|
|
userId,
|
|
);
|
|
results.push({ spaceId, result });
|
|
} catch (error) {
|
|
results.push({
|
|
spaceId,
|
|
result: {
|
|
success: false,
|
|
statementsUpdated: 0,
|
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
}
|