Skip to content

Commit feb1691

Browse files
committed
feat(teams): implement team management core with RBAC and metadata
1 parent 0c52d6a commit feb1691

9 files changed

Lines changed: 104 additions & 4 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { baseSchema } from 'src/shared/entities';
2+
3+
export const roleEnum = baseSchema.enum('team_role', ['admin', 'moderator', 'member']);
4+
export const statusEnum = baseSchema.enum('member_status', [
5+
'pending',
6+
'active',
7+
'declined',
8+
'banned',
9+
]);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { tags, teamsToTags, teams, teamMembers } from './teams.entity';
2+
export { roleEnum, statusEnum } from './enums';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { primaryKey, timestamp, text, varchar, index } from 'drizzle-orm/pg-core';
2+
import { createId } from '@paralleldrive/cuid2';
3+
import { roleEnum, statusEnum } from './enums';
4+
import { baseSchema, users } from 'src/shared/entities';
5+
6+
export const teams = baseSchema.table(
7+
'teams',
8+
{
9+
id: text('id')
10+
.primaryKey()
11+
.$defaultFn(() => createId()),
12+
slug: varchar('slug', { length: 120 }).unique().notNull(),
13+
name: varchar('name', { length: 100 }).notNull(),
14+
description: text('description'),
15+
avatarUrl: text('avatar_url'),
16+
coverUrl: text('cover_url'),
17+
ownerId: text('owner_id').references(() => users.id),
18+
createdAt: timestamp('created_at').defaultNow().notNull(),
19+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
20+
},
21+
(t) => ({
22+
slugIdx: index('team_slug_idx').on(t.slug),
23+
}),
24+
);
25+
26+
export const teamMembers = baseSchema.table(
27+
'team_members',
28+
{
29+
teamId: text('team_id')
30+
.references(() => teams.id, { onDelete: 'cascade' })
31+
.notNull(),
32+
userId: text('user_id')
33+
.references(() => users.id, { onDelete: 'cascade' })
34+
.notNull(),
35+
role: roleEnum('role').default('member').notNull(),
36+
status: statusEnum('status').default('pending').notNull(),
37+
joinedAt: timestamp('joined_at'),
38+
createdAt: timestamp('created_at').defaultNow().notNull(),
39+
},
40+
(t) => ({
41+
pk: primaryKey({ columns: [t.teamId, t.userId] }),
42+
statusIdx: index('member_status_idx').on(t.status),
43+
}),
44+
);
45+
46+
export const tags = baseSchema.table('tags', {
47+
id: text('id')
48+
.primaryKey()
49+
.$defaultFn(() => createId()),
50+
name: varchar('name', { length: 50 }).unique().notNull(),
51+
});
52+
53+
export const teamsToTags = baseSchema.table(
54+
'teams_to_tags',
55+
{
56+
teamId: text('team_id')
57+
.references(() => teams.id, { onDelete: 'cascade' })
58+
.notNull(),
59+
tagId: text('tag_id')
60+
.references(() => tags.id, { onDelete: 'cascade' })
61+
.notNull(),
62+
},
63+
(t) => ({
64+
pk: primaryKey({ columns: [t.teamId, t.tagId] }),
65+
}),
66+
);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { TeamsRepository } from './teams.repository';
2+
export { ITeamsRepository } from './teams.repository.interface';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export interface ITeamsRepository {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Inject } from '@nestjs/common';
2+
import { ITeamsRepository } from './teams.repository.interface';
3+
import { DATABASE_SERVICE, DatabaseService } from '@libs/database';
4+
import * as schema from '../entities';
5+
6+
export class TeamsRepository implements ITeamsRepository {
7+
constructor(
8+
@Inject(DATABASE_SERVICE)
9+
private readonly db: DatabaseService<typeof schema>,
10+
) {}
11+
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { Inject, Injectable } from '@nestjs/common';
2+
import { ITeamsRepository } from '../repository';
23

34
@Injectable()
4-
export class TeamsService {}
5+
export class TeamsService {
6+
constructor(
7+
@Inject('ITeamsRepository')
8+
private readonly teamsRepo: ITeamsRepository,
9+
) {}
10+
}

src/modules/teams/teams.module.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { Module } from '@nestjs/common';
22
import { TeamsController } from './controller';
33
import { TeamsService } from './services';
4+
import { TeamsRepository } from './repository';
5+
6+
const REPOSITORY = { provide: 'ITeamsRepository', useClass: TeamsRepository };
47

58
@Module({
69
imports: [],
710
controllers: [TeamsController],
8-
providers: [TeamsService],
9-
exports: [],
11+
providers: [REPOSITORY, TeamsService],
1012
})
1113
export class TeamsModule {}

src/shared/entities/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { baseSchema } from './schema';
22
export * from '../../modules/user/entities';
33
export * from '../../modules/auth/entities';
4+
export * from '../../modules/teams/entities';

0 commit comments

Comments
 (0)