-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from DashHub-ai/feature/add-chats-database-str…
…ucture Add chats database structure
- Loading branch information
Showing
64 changed files
with
1,017 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { type Kysely, sql } from 'kysely'; | ||
|
||
import { addArchivedAtColumns, addIdColumn, addTimestampColumns } from './utils'; | ||
import { addUuidColumn } from './utils/add-uuid'; | ||
|
||
export async function up(db: Kysely<any>): Promise<void> { | ||
await db.schema | ||
.createTable('chats') | ||
.$call(addUuidColumn) | ||
.$call(addTimestampColumns) | ||
.$call(addArchivedAtColumns) | ||
.addColumn('creator_user_id', 'integer', col => col.notNull().references('users.id').onDelete('restrict')) | ||
.addColumn('organization_id', 'integer', col => col.notNull().references('organizations.id').onDelete('restrict')) | ||
.addColumn('public', 'boolean', col => col.notNull().defaultTo(false)) | ||
.addColumn('last_message_at', 'timestamp') | ||
.execute(); | ||
|
||
await db.schema | ||
.createTable('chat_summaries') | ||
.$call(addIdColumn) | ||
.$call(addTimestampColumns) | ||
.addColumn('chat_id', 'uuid', col => col.notNull().references('chats.id').onDelete('restrict')) | ||
.addColumn('content', 'text', col => col.notNull()) | ||
.execute(); | ||
|
||
await db.schema | ||
.createType('message_role') | ||
.asEnum(['user', 'assistant', 'system']) | ||
.execute(); | ||
|
||
await db.schema | ||
.createTable('messages') | ||
.$call(addUuidColumn) | ||
.$call(addTimestampColumns) | ||
.addColumn('chat_id', 'uuid', col => col.notNull().references('chats.id').onDelete('restrict')) | ||
.addColumn('content', 'text', col => col.notNull()) | ||
.addColumn('role', sql`message_role`, col => col.notNull()) | ||
.addColumn('metadata', 'jsonb', col => col.notNull().defaultTo('{}')) | ||
.addColumn('original_message_id', 'uuid', col => col.references('messages.id').onDelete('restrict')) | ||
.addColumn('repeat_count', 'integer', col => col.notNull().defaultTo(0)) | ||
.execute(); | ||
|
||
await db.schema | ||
.createIndex('chats_organization_id_index') | ||
.on('chats') | ||
.column('organization_id') | ||
.execute(); | ||
|
||
await db.schema | ||
.createIndex('messages_chat_id_index') | ||
.on('messages') | ||
.column('chat_id') | ||
.execute(); | ||
} | ||
|
||
export async function down(db: Kysely<any>): Promise<void> { | ||
await db.schema.dropTable('messages').execute(); | ||
await db.schema.dropTable('chat_summaries').execute(); | ||
await db.schema.dropTable('chats').execute(); | ||
await db.schema.dropType('message_role').execute(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* DO NOT CHANGE! It will break migrations! */ | ||
import { type AlterTableBuilder, type CreateTableBuilder, sql } from 'kysely'; | ||
|
||
export function addUuidColumn< | ||
C extends AlterTableBuilder | CreateTableBuilder<any>, | ||
>(qb: C) { | ||
return qb.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql`gen_random_uuid()`)) as C; | ||
} | ||
|
||
export function dropUuidColumn<C extends AlterTableBuilder>(qb: C) { | ||
return qb.dropColumn('id'); | ||
} |
38 changes: 38 additions & 0 deletions
38
apps/backend/src/modules/api/controllers/dashboard/chats.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { pipe } from 'fp-ts/lib/function'; | ||
import { inject, injectable } from 'tsyringe'; | ||
|
||
import { | ||
type ChatsSdk, | ||
SdkCreateChatInputV, | ||
} from '@llm/sdk'; | ||
import { ChatsService } from '~/modules/chats'; | ||
import { ConfigService } from '~/modules/config'; | ||
|
||
import { | ||
rejectUnsafeSdkErrors, | ||
sdkSchemaValidator, | ||
serializeSdkResponseTE, | ||
} from '../../helpers'; | ||
import { AuthorizedController } from '../shared/authorized.controller'; | ||
|
||
@injectable() | ||
export class ChatsController extends AuthorizedController { | ||
constructor( | ||
@inject(ConfigService) configService: ConfigService, | ||
@inject(ChatsService) chatsService: ChatsService, | ||
) { | ||
super(configService); | ||
|
||
this.router | ||
.post( | ||
'/', | ||
sdkSchemaValidator('json', SdkCreateChatInputV), | ||
async context => pipe( | ||
context.req.valid('json'), | ||
chatsService.asUser(context.var.jwt).create, | ||
rejectUnsafeSdkErrors, | ||
serializeSdkResponseTE<ReturnType<ChatsSdk['create']>>(context), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { flow } from 'fp-ts/lib/function'; | ||
|
||
import { | ||
ofSdkUnauthorizedErrorTE, | ||
type SdkCreateChatInputT, | ||
type SdkJwtTokenT, | ||
type SdkUnauthorizedError, | ||
} from '@llm/sdk'; | ||
import { AuthFirewallService } from '~/modules/auth/firewall'; | ||
|
||
import type { DatabaseTE, TableRowWithId } from '../database'; | ||
import type { ChatsService } from './chats.service'; | ||
|
||
export class ChatsFirewall extends AuthFirewallService { | ||
constructor( | ||
jwt: SdkJwtTokenT, | ||
private readonly chatsService: ChatsService, | ||
) { | ||
super(jwt); | ||
} | ||
|
||
// TODO: Add belongs checks | ||
unarchive = flow( | ||
this.chatsService.archive, | ||
this.tryTEIfUser.is.root, | ||
); | ||
|
||
// TODO: Add belongs checks | ||
archive = flow( | ||
this.chatsService.archive, | ||
this.tryTEIfUser.is.root, | ||
); | ||
|
||
create = ({ creator, organization, ...chat }: SdkCreateChatInputT): DatabaseTE<TableRowWithId, SdkUnauthorizedError> => { | ||
const { jwt } = this; | ||
|
||
switch (jwt.role) { | ||
case 'root': | ||
if (!organization) { | ||
return ofSdkUnauthorizedErrorTE(); | ||
} | ||
|
||
return this.chatsService.create({ | ||
...chat, | ||
creator: creator ?? this.userIdRow, | ||
organization, | ||
}); | ||
|
||
case 'user': | ||
return this.chatsService.create({ | ||
...chat, | ||
creator: this.userIdRow, | ||
organization: jwt.organization, | ||
}); | ||
|
||
default: { | ||
const _: never = jwt; | ||
|
||
return ofSdkUnauthorizedErrorTE(); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { injectable } from 'tsyringe'; | ||
|
||
import { | ||
createArchiveRecordQuery, | ||
createArchiveRecordsQuery, | ||
createDatabaseRepo, | ||
createUnarchiveRecordQuery, | ||
createUnarchiveRecordsQuery, | ||
} from '~/modules/database'; | ||
|
||
@injectable() | ||
export class ChatsRepo extends createDatabaseRepo('chats') { | ||
archive = createArchiveRecordQuery(this.queryFactoryAttrs); | ||
|
||
archiveRecords = createArchiveRecordsQuery(this.queryFactoryAttrs); | ||
|
||
unarchive = createUnarchiveRecordQuery(this.queryFactoryAttrs); | ||
|
||
unarchiveRecords = createUnarchiveRecordsQuery(this.queryFactoryAttrs); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { pipe } from 'fp-ts/lib/function'; | ||
import { inject, injectable } from 'tsyringe'; | ||
|
||
import type { RequiredBy } from '@llm/commons'; | ||
|
||
import { SdkCreateChatInputT, SdkJwtTokenT, SdkTableRowIdT } from '@llm/sdk'; | ||
|
||
import { WithAuthFirewall } from '../auth'; | ||
import { ChatsFirewall } from './chats.firewall'; | ||
import { ChatsRepo } from './chats.repo'; | ||
|
||
@injectable() | ||
export class ChatsService implements WithAuthFirewall<ChatsFirewall> { | ||
constructor( | ||
@inject(ChatsRepo) private readonly repo: ChatsRepo, | ||
) {} | ||
|
||
asUser = (jwt: SdkJwtTokenT) => new ChatsFirewall(jwt, this); | ||
|
||
unarchive = (id: SdkTableRowIdT) => this.repo.unarchive({ id }); | ||
|
||
archive = (id: SdkTableRowIdT) => this.repo.archive({ id }); | ||
|
||
create = ( | ||
{ | ||
organization, | ||
creator, | ||
...values | ||
}: RequiredBy<SdkCreateChatInputT, 'organization' | 'creator'>, | ||
) => pipe( | ||
this.repo.create({ | ||
value: { | ||
...values, | ||
organizationId: organization.id, | ||
creatorUserId: creator.id, | ||
}, | ||
}), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { ColumnType } from 'kysely'; | ||
|
||
import type { SdkOffsetPaginationOutputT } from '@llm/sdk'; | ||
import type { | ||
NormalizeSelectTableRow, | ||
TableId, | ||
TableUuid, | ||
TableWithAccessTimeColumns, | ||
TableWithArchivedAtColumn, | ||
TableWithDefaultColumns, | ||
TableWithUuidColumn, | ||
} from '~/modules/database'; | ||
|
||
import type { MessageTableRowWithRelations } from './messages'; | ||
|
||
export type ChatsTable = | ||
& TableWithUuidColumn | ||
& TableWithAccessTimeColumns | ||
& TableWithArchivedAtColumn | ||
& { | ||
creator_user_id: ColumnType<TableId, TableId, never>; | ||
organization_id: ColumnType<TableId, TableId, never>; | ||
public: boolean; | ||
last_message_at: Date | null; | ||
}; | ||
|
||
export type ChatSummariesTable = TableWithDefaultColumns & { | ||
chat_id: ColumnType<TableUuid, TableUuid, never>; | ||
content: string; | ||
}; | ||
|
||
export type ChatTableRow = NormalizeSelectTableRow<ChatsTable>; | ||
export type ChatSummaryTableRow = NormalizeSelectTableRow<ChatSummariesTable>; | ||
|
||
export type ChatTableRowWithRelations = ChatTableRow & { | ||
summary: ChatSummaryTableRow | null; | ||
recentMessages: SdkOffsetPaginationOutputT<MessageTableRowWithRelations>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './chats.firewall'; | ||
export * from './chats.repo'; | ||
export * from './chats.service'; | ||
export * from './chats.tables'; | ||
export * from './messages'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './messages.tables'; |
32 changes: 32 additions & 0 deletions
32
apps/backend/src/modules/chats/messages/messages.tables.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { ColumnType } from 'kysely'; | ||
|
||
import type { SdkMessageRoleT } from '@llm/sdk'; | ||
import type { | ||
NormalizeSelectTableRow, | ||
TableId, | ||
TableUuid, | ||
TableWithAccessTimeColumns, | ||
TableWithUuidColumn, | ||
} from '~/modules/database'; | ||
|
||
export type MessagesTable = | ||
& TableWithUuidColumn | ||
& TableWithAccessTimeColumns | ||
& { | ||
chat_id: ColumnType<TableUuid, TableUuid, never>; | ||
content: string; | ||
role: SdkMessageRoleT; | ||
metadata: Record<string, unknown>; | ||
original_message_id: ColumnType<TableId, TableId, null>; | ||
repeat_count: ColumnType<number, number, never>; | ||
}; | ||
|
||
export type MessageTableRow = NormalizeSelectTableRow<MessagesTable>; | ||
|
||
export type MessageTableRowWithRelations = | ||
& OmitRepeatFields<MessageTableRow> | ||
& { | ||
repeats: Array<OmitRepeatFields<MessageTableRow>>; | ||
}; | ||
|
||
type OmitRepeatFields<T> = Omit<T, 'originalMessageId' | 'repeatCount'>; |
Oops, something went wrong.