Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add files to organization scope project #176

Merged
merged 5 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { Kysely } from 'kysely';

export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('organizations_ai_settings')
.addColumn('project_id', 'integer', col => col.references('projects.id').onDelete('restrict'))
.execute();

const organizations = await db
.selectFrom('organizations')
.select(['id'])
.execute();

const rootUser = await db
.selectFrom('users')
.select(['id'])
.where('role', '=', 'root')
.executeTakeFirst();

if (rootUser) {
for (const [index, organization] of organizations.entries()) {
const project = await db
.insertInto('projects')
.values({
name: `Migrated Organization Project - ${Date.now()}-${index}`,
organization_id: organization.id,
internal: true,
creator_user_id: rootUser.id,
})
.returning(['id'])
.executeTakeFirstOrThrow();

await db
.insertInto('projects_summaries')
.values({
project_id: project.id,
})
.execute();

await db
.insertInto('organizations_ai_settings')
.values({
organization_id: organization.id,
project_id: project.id,
chat_context: null,
})
.onConflict(oc => oc
.column('organization_id')
.doUpdateSet(eb => ({
project_id: eb.ref('excluded.project_id'),
})),
)
.execute();
}
}

await db.schema
.alterTable('organizations_ai_settings')
.alterColumn('project_id', col => col.setNotNull())
.execute();
}

export async function down(db: Kysely<any>): Promise<void> {
const projects = await db
.selectFrom('organizations_ai_settings')
.select(['project_id'])
.where('project_id', 'is not', null)
.execute();

await db.schema
.alterTable('organizations_ai_settings')
.dropColumn('project_id')
.execute();

if (projects.length > 0) {
await db
.deleteFrom('projects_summaries')
.where('project_id', 'in', projects.map(app => app.project_id))
.execute();

await db
.deleteFrom('projects')
.where('id', 'in', projects.map(app => app.project_id))
.execute();
}
}
2 changes: 2 additions & 0 deletions apps/backend/src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import * as addSearchEnginesTable from './0042-add-search-engines-table';
import * as addWebSearchToMessages from './0043-add-websearch-flag-to-messages';
import * as addPinnedMessages from './0044-add-pinned-messages';
import * as addOrganizationsAISettings from './0045-add-organizations-ai-settings-table';
import * as addProjectsToOrganizationsAISettings from './0046-add-projects-to-organizations-ai-settings';

export const DB_MIGRATIONS = {
'0000-add-users-tables': addUsersTables,
Expand Down Expand Up @@ -92,4 +93,5 @@ export const DB_MIGRATIONS = {
'0043-add-websearch-flag-to-messages': addWebSearchToMessages,
'0044-add-pinned-messages': addPinnedMessages,
'0045-add-organizations-ai-settings-table': addOrganizationsAISettings,
'0046-add-projects-to-organizations-ai-settings': addProjectsToOrganizationsAISettings,
};
4 changes: 2 additions & 2 deletions apps/backend/src/modules/messages/messages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class MessagesService implements WithAuthFirewall<MessagesFirewall> {
);
}),
)),
TE.bindW('mappedContent', ({ message, history }) => pipe(
TE.bindW('mappedContent', ({ message, chat, history }) => pipe(
message.repliedMessage
? createReplyAiMessagePrefix(message.repliedMessage, message.content)
: message.content,
Expand All @@ -176,7 +176,7 @@ export class MessagesService implements WithAuthFirewall<MessagesFirewall> {
prefixedMessage => this.projectsEmbeddingsService.wrapWithEmbeddingContextPrompt({
history,
message: prefixedMessage,
chat: { id: message.chat.id },
chat,
}),
)),
TE.chainW(({ mappedContent, history, message, chat, personalities }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './organizations-ai-settings.repo';
export * from './organizations-ai-settings.service';
export * from './organizations-ai-settings.tables';
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,39 @@ import { injectable } from 'tsyringe';
import {
AbstractDatabaseRepo,
DatabaseError,
TableId,
TransactionalAttrs,
type TableId,
type TransactionalAttrs,
tryReuseTransactionOrSkip,
} from '~/modules/database';

import type { OrganizationsAISettingsTableInsertRow } from './organizations-ai-settings.tables';

@injectable()
export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo {
getProjectIdByOrganizationIdOrNil = (
{
forwardTransaction,
organizationId,
}: TransactionalAttrs<{ organizationId: TableId; }>,
) => {
const transaction = tryReuseTransactionOrSkip({
db: this.db,
forwardTransaction,
});

return pipe(
transaction(trx =>
trx
.selectFrom('organizations_ai_settings')
.select('project_id as id')
.where('organization_id', '=', organizationId)
.executeTakeFirst(),
),
DatabaseError.tryTask,
TE.map(row => row?.id ?? null),
);
};

getChatContextByOrganizationIdOrNil = (
{
forwardTransaction,
Expand Down Expand Up @@ -42,7 +66,9 @@ export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo {
{
forwardTransaction,
value,
}: TransactionalAttrs<{ value: OrganizationsAISettingsTableInsertRow; }>,
}: TransactionalAttrs<{
value: OrganizationsAISettingsTableInsertRow;
}>,
) => {
const transaction = tryReuseTransactionOrSkip({
db: this.db,
Expand All @@ -56,6 +82,7 @@ export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo {
.values({
organization_id: value.organizationId,
chat_context: value.chatContext || null,
project_id: value.projectId,
})
.onConflict(oc => oc
.column('organization_id')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
import { taskEither as TE } from 'fp-ts';
import { pipe } from 'fp-ts/lib/function';
import { inject, injectable } from 'tsyringe';

import { SdkUpsertOrganizationAISettingsInputT } from '@llm/sdk';

import { TableRowWithId } from '../database';
import { ProjectsService } from '../projects';
import { OrganizationsAISettingsRepo } from './organizations-ai-settings.repo';

@injectable()
export class OrganizationsAISettingsService {
constructor(
@inject(OrganizationsAISettingsRepo) private readonly organizationsAISettingsRepo: OrganizationsAISettingsRepo,
@inject(OrganizationsAISettingsRepo) private readonly repo: OrganizationsAISettingsRepo,
@inject(ProjectsService) private readonly projectsService: ProjectsService,
) {}

getChatContextByOrganizationIdOrNil = this.organizationsAISettingsRepo.getChatContextByOrganizationIdOrNil;
getProjectIdByOrganizationIdOrNil = this.repo.getProjectIdByOrganizationIdOrNil;

getChatContextByOrganizationIdOrNil = this.repo.getChatContextByOrganizationIdOrNil;

upsert = ({ chatContext, organization }: SdkUpsertOrganizationAISettingsInputT & { organization: TableRowWithId; }) => pipe(
TE.Do,
TE.bind('project', () => pipe(
this.repo.getProjectIdByOrganizationIdOrNil({
organizationId: organization.id,
}),
TE.chainW((projectId) => {
if (projectId) {
return TE.right({ id: projectId });
}

return this.projectsService.createInternal({
organization,
});
}),
)),
TE.chainW(({ project }) => this.repo.upsert({
value: {
projectId: project.id,
organizationId: organization.id,
chatContext,
},
})),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ import type {
NormalizeInsertTableRow,
NormalizeSelectTableRow,
TableId,
TableRowWithIdName,
} from '../database';

export type OrganizationsAISettingsTable = {
organization_id: ColumnType<TableId, TableId, never>;
project_id: ColumnType<TableId, TableId, never>;
chat_context: string | null;
};

export type OrganizationsAISettingsTableRow = NormalizeSelectTableRow<OrganizationsAISettingsTable>;

export type OrganizationsAISettingsTableInsertRow = NormalizeInsertTableRow<OrganizationsAISettingsTable>;

export type OrganizationsAISettingsTableRelationRow = Omit<OrganizationsAISettingsTableRow, 'organizationId'>;
export type OrganizationsAISettingsTableRelationRow =
& Omit<OrganizationsAISettingsTableRow, 'organizationId' | 'projectId'>
& {
project: TableRowWithIdName;
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export class OrganizationsEsSearchRepo {
maxNumberOfUsers: source.max_number_of_users ?? 0,
aiSettings: {
chatContext: source.ai_settings?.chat_context ?? null,
project: source.ai_settings?.project,
},
});
}
82 changes: 16 additions & 66 deletions apps/backend/src/modules/organizations/organizations.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,95 +3,36 @@ import { array as A, taskEither as TE } from 'fp-ts';
import { pipe } from 'fp-ts/lib/function';
import { inject, injectable } from 'tsyringe';

import type { SdkCreateOrganizationInputT } from '@llm/sdk';

import {
createArchiveRecordQuery,
createArchiveRecordsQuery,
createProtectedDatabaseRepo,
createDatabaseRepo,
createUnarchiveRecordQuery,
createUnarchiveRecordsQuery,
DatabaseConnectionRepo,
DatabaseError,
TableId,
type TableId,
type TransactionalAttrs,
tryReuseOrCreateTransaction,
tryReuseTransactionOrSkip,
} from '~/modules/database';

import { OrganizationsAISettingsRepo } from '../organizations-ai-settings';
import { OrganizationTableRowWithRelations } from './organizations.tables';

@injectable()
export class OrganizationsRepo extends createProtectedDatabaseRepo('organizations') {
export class OrganizationsRepo extends createDatabaseRepo('organizations') {
constructor(
@inject(DatabaseConnectionRepo) databaseConnectionRepo: DatabaseConnectionRepo,
@inject(OrganizationsAISettingsRepo) private readonly organizationsAISettingsRepo: OrganizationsAISettingsRepo,
) {
super(databaseConnectionRepo);
}

createIdsIterator = this.baseRepo.createIdsIterator;

archive = createArchiveRecordQuery(this.baseRepo.queryFactoryAttrs);

archiveRecords = createArchiveRecordsQuery(this.baseRepo.queryFactoryAttrs);

unarchive = createUnarchiveRecordQuery(this.baseRepo.queryFactoryAttrs);
archive = createArchiveRecordQuery(this.queryFactoryAttrs);

unarchiveRecords = createUnarchiveRecordsQuery(this.baseRepo.queryFactoryAttrs);
archiveRecords = createArchiveRecordsQuery(this.queryFactoryAttrs);

create = ({ value, forwardTransaction }: TransactionalAttrs<{ value: SdkCreateOrganizationInputT; }>) => {
const transaction = tryReuseOrCreateTransaction({
db: this.db,
forwardTransaction,
});
unarchive = createUnarchiveRecordQuery(this.queryFactoryAttrs);

return transaction(trx => pipe(
this.baseRepo.create({
forwardTransaction: trx,
value: {
name: value.name,
maxNumberOfUsers: value.maxNumberOfUsers,
},
}),
TE.tap(({ id }) => this.organizationsAISettingsRepo.upsert({
forwardTransaction: trx,
value: {
organizationId: id,
chatContext: value.aiSettings.chatContext,
},
})),
));
};

update = ({ id, value, forwardTransaction }: TransactionalAttrs<{
id: TableId;
value: SdkCreateOrganizationInputT;
}>) => {
const transaction = tryReuseOrCreateTransaction({
db: this.db,
forwardTransaction,
});

return transaction(trx => pipe(
this.baseRepo.update({
id,
forwardTransaction: trx,
value: {
name: value.name,
maxNumberOfUsers: value.maxNumberOfUsers,
},
}),
TE.tap(() => this.organizationsAISettingsRepo.upsert({
forwardTransaction: trx,
value: {
organizationId: id,
chatContext: value.aiSettings.chatContext,
},
})),
));
};
unarchiveRecords = createUnarchiveRecordsQuery(this.queryFactoryAttrs);

findWithRelationsByIds = ({ forwardTransaction, ids }: TransactionalAttrs<{ ids: TableId[]; }>) => {
const transaction = tryReuseTransactionOrSkip({ db: this.db, forwardTransaction });
Expand All @@ -103,9 +44,12 @@ export class OrganizationsRepo extends createProtectedDatabaseRepo('organization
.selectFrom(this.table)
.where('organizations.id', 'in', ids)
.leftJoin('organizations_ai_settings', 'organizations_ai_settings.organization_id', 'organizations.id')
.innerJoin('projects', 'projects.id', 'organizations_ai_settings.project_id')
.selectAll('organizations')
.select([
'organizations_ai_settings.chat_context as ai_settings_chat_context',
'projects.id as ai_settings_project_id',
'projects.name as ai_settings_project_name',
])
.limit(ids.length)
.execute(),
Expand All @@ -114,11 +58,17 @@ export class OrganizationsRepo extends createProtectedDatabaseRepo('organization
TE.map(
A.map(({
ai_settings_chat_context: aiSettingsChatContext,
ai_settings_project_id: projectId,
ai_settings_project_name: projectName,
...organization
}): OrganizationTableRowWithRelations => ({
...camelcaseKeys(organization),
aiSettings: {
chatContext: aiSettingsChatContext,
project: {
id: projectId,
name: projectName,
},
},
})),
),
Expand Down
Loading