Skip to content

Commit

Permalink
feat(backend): add create / update of users-groups support
Browse files Browse the repository at this point in the history
  • Loading branch information
Mati365 committed Dec 29, 2024
1 parent 2ec33d5 commit 0b4ce43
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 19 deletions.
29 changes: 28 additions & 1 deletion apps/backend/src/migrations/0026-add-users-groups-table.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Kysely } from 'kysely';
import { type Kysely, sql } from 'kysely';

import { addArchivedAtColumns, addIdColumn, addTimestampColumns } from './utils';

Expand All @@ -25,9 +25,36 @@ export async function up(db: Kysely<any>): Promise<void> {
.on('users_groups')
.column('organization_id')
.execute();

await sql`
CREATE OR REPLACE FUNCTION users_groups_users_check_organization() RETURNS TRIGGER AS $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM organizations_users ou
JOIN users_groups ug ON ug.organization_id = ou.organization_id
WHERE ou.user_id = NEW.user_id
AND ug.id = NEW.group_id
) THEN
RAISE EXCEPTION 'User must belong to the same organization as the group';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER users_groups_users_organization_check
BEFORE INSERT OR UPDATE ON users_groups_users
FOR EACH ROW
EXECUTE FUNCTION users_groups_users_check_organization();
`.execute(db);
}

export async function down(db: Kysely<any>): Promise<void> {
await sql`
DROP TRIGGER IF EXISTS users_groups_users_organization_check ON users_groups_users;
DROP FUNCTION IF EXISTS users_groups_users_check_organization();
`.execute(db);

await db.schema.dropTable('users_groups_users').execute();
await db.schema.dropTable('users_groups').execute();
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
import { pipe } from 'fp-ts/lib/function';
import { injectable } from 'tsyringe';

import { AbstractDatabaseRepo } from '~/modules/database';
import {
AbstractDatabaseRepo,
DatabaseError,
TableRowWithId,
TransactionalAttrs,
tryReuseOrCreateTransaction,
} from '~/modules/database';

@injectable()
export class UsersGroupsUsersRepo extends AbstractDatabaseRepo {
updateGroupUsers = (
{
forwardTransaction,
group,
users,
}: TransactionalAttrs<{
group: TableRowWithId;
users: TableRowWithId[];
}>,
) => {
const transaction = tryReuseOrCreateTransaction({
db: this.db,
forwardTransaction,
});

return transaction(trx => pipe(
async () => {
await trx
.deleteFrom('users_groups_users')
.where('group_id', '=', group.id)
.execute();

if (users.length > 0) {
await trx
.insertInto('users_groups_users')
.values(
users.map(user => ({
group_id: group.id,
user_id: user.id,
})),
)
.execute();
}

return {
success: true,
};
},
DatabaseError.tryTask,
));
};
}
98 changes: 91 additions & 7 deletions apps/backend/src/modules/users-groups/repo/users-groups.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,46 @@ import camelcaseKeys from 'camelcase-keys';
import { array as A, taskEither as TE } from 'fp-ts';
import { pipe } from 'fp-ts/lib/function';
import { jsonBuildObject } from 'kysely/helpers/postgres';
import { injectable } from 'tsyringe';
import { inject, injectable } from 'tsyringe';

import type { SdkCreateUsersGroupInputT, SdkUpdateUsersGroupInputT } from '@llm/sdk';

import type { UsersGroupTableRowWithRelations } from '../users-groups.tables';

import {
createArchiveRecordQuery,
createArchiveRecordsQuery,
createDatabaseRepo,
createProtectedDatabaseRepo,
createUnarchiveRecordQuery,
createUnarchiveRecordsQuery,
DatabaseConnectionRepo,
DatabaseError,
type TableId,
type TableRowWithId,
type TransactionalAttrs,
tryReuseOrCreateTransaction,
tryReuseTransactionOrSkip,
} from '../../database';
import { UsersGroupsUsersRepo } from './users-groups-users.repo';

@injectable()
export class UsersGroupsRepo extends createDatabaseRepo('users_groups') {
archive = createArchiveRecordQuery(this.queryFactoryAttrs);
export class UsersGroupsRepo extends createProtectedDatabaseRepo('users_groups') {
constructor(
@inject(DatabaseConnectionRepo) connectionRepo: DatabaseConnectionRepo,
@inject(UsersGroupsUsersRepo) private readonly usersGroupsUsersRepo: UsersGroupsUsersRepo,
) {
super(connectionRepo);
}

createIdsIterator = this.baseRepo.createIdsIterator;

archive = createArchiveRecordQuery(this.baseRepo.queryFactoryAttrs);

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

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

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

findWithRelationsByIds = ({ forwardTransaction, ids }: TransactionalAttrs<{ ids: TableId[]; }>) => {
const transaction = tryReuseTransactionOrSkip({ db: this.db, forwardTransaction });
Expand Down Expand Up @@ -93,4 +108,73 @@ export class UsersGroupsRepo extends createDatabaseRepo('users_groups') {
),
);
};

create = (
{
forwardTransaction,
value: {
users,
creator,
organization,
...attrs
},
}: TransactionalAttrs<{
value: SdkCreateUsersGroupInputT & {
creator: TableRowWithId;
};
}>,
) => {
const transaction = tryReuseOrCreateTransaction({
db: this.db,
forwardTransaction,
});

return transaction(trx => pipe(
this.baseRepo.create({
value: {
...attrs,
creatorUserId: creator.id,
organizationId: organization.id,
},
forwardTransaction: trx,
}),
TE.tap(group => this.usersGroupsUsersRepo.updateGroupUsers({
forwardTransaction: trx,
group,
users,
})),
));
};

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

return transaction(trx => pipe(
this.baseRepo.update({
id,
value: attrs,
forwardTransaction: trx,
}),
TE.tap(group => this.usersGroupsUsersRepo.updateGroupUsers({
forwardTransaction: trx,
group,
users,
})),
));
};
}
12 changes: 2 additions & 10 deletions apps/backend/src/modules/users-groups/users-groups.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,12 @@ export class UsersGroupsService implements WithAuthFirewall<UsersGroupsFirewall>
);

create = (
{
creator,
organization,
...values
}: SdkCreateUsersGroupInputT & {
value: SdkCreateUsersGroupInputT & {
creator: TableRowWithId;
},
) => pipe(
this.repo.create({
value: {
...values,
creatorUserId: creator.id,
organizationId: organization.id,
},
value,
}),
TE.tap(({ id }) => this.esIndexRepo.findAndIndexDocumentById(id)),
);
Expand Down

0 comments on commit 0b4ce43

Please sign in to comment.