feat: add organization schema
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"start": "node dist/app.js",
|
"start": "node dist/app.js",
|
||||||
"dev": "tsx watch src/app.ts",
|
"dev": "tsx watch src/app.ts",
|
||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
|
"prisma:migrate-reset": "prisma migrate reset",
|
||||||
"prisma:generate": "prisma generate",
|
"prisma:generate": "prisma generate",
|
||||||
"prisma:push": "prisma db push",
|
"prisma:push": "prisma db push",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ model AuthIdentity {
|
|||||||
password String
|
password String
|
||||||
isVerified Boolean @default(false)
|
isVerified Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
organizationMemberships OrganizationUserMembership[]
|
memberships OrganizationMember[]
|
||||||
verifications AuthVerification[]
|
verifications AuthVerification[]
|
||||||
sentInvitations OrganizationInvitation[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model AuthVerification {
|
model AuthVerification {
|
||||||
|
|||||||
42
prisma/schema/organization.prisma
Normal file
42
prisma/schema/organization.prisma
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
model OrganizationMember {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
identityId String
|
||||||
|
organizationId String
|
||||||
|
roleId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
identity AuthIdentity @relation(fields: [identityId], references: [id])
|
||||||
|
organization Organization @relation(fields: [organizationId], references: [id])
|
||||||
|
role OrganizationRole @relation(fields: [roleId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model OrganizationRole {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
organizationId String
|
||||||
|
name String
|
||||||
|
permissions Json
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
organization Organization @relation(fields: [organizationId], references: [id])
|
||||||
|
members OrganizationMember[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model OrganizationInvite {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
recipientEmail String
|
||||||
|
organizationId String
|
||||||
|
roleId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
organization Organization @relation(fields: [organizationId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Organization {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
organizationRoles OrganizationRole[]
|
||||||
|
organizationInvites OrganizationInvite[]
|
||||||
|
members OrganizationMember[]
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
model OrganizationInvitation {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
senderId String
|
|
||||||
organizationId String
|
|
||||||
inviteToken String
|
|
||||||
emailRecipient String
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
acceptedAt DateTime?
|
|
||||||
isAccepted Boolean @default(false)
|
|
||||||
isRevoked Boolean @default(false)
|
|
||||||
organization Organization @relation(fields: [organizationId], references: [id])
|
|
||||||
inviteSender AuthIdentity @relation(fields: [senderId], references: [id])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Organization {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
name String
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
organizationUsers OrganizationUserMembership[]
|
|
||||||
invitations OrganizationInvitation[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model OrganizationUserMembership {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
organizationId String
|
|
||||||
identityId String
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
organization Organization @relation(fields: [organizationId], references: [id])
|
|
||||||
identity AuthIdentity @relation(fields: [identityId], references: [id])
|
|
||||||
}
|
|
||||||
|
|
||||||
enum OrganizationRole {
|
|
||||||
OWNER
|
|
||||||
ADMIN
|
|
||||||
MEMBER
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export class InvalidEmailFormat extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Invalid email format");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export class OrganizationMismatch extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Organization is not the same as the current organization");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { InvalidEmailFormat } from "./errors/InvalidEmailFormat.js";
|
||||||
|
import { OrganizationMismatch } from "./errors/OrganizationMismatch.js";
|
||||||
|
import type { OrganizationEntity } from "./organization.entity.js";
|
||||||
|
import type { OrganizationRoleEntity } from "./organization-role.entity.js";
|
||||||
|
|
||||||
|
export class OrganizationInviteEntity {
|
||||||
|
constructor(
|
||||||
|
public id: string,
|
||||||
|
public recipientEmail: string,
|
||||||
|
public createdAt: Date,
|
||||||
|
public updatedAt: Date,
|
||||||
|
public role: OrganizationRoleEntity,
|
||||||
|
public organization: OrganizationEntity,
|
||||||
|
) {
|
||||||
|
if (!recipientEmail.includes("@")) {
|
||||||
|
throw new InvalidEmailFormat();
|
||||||
|
}
|
||||||
|
if (organization.id !== role.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeRole(newRole: OrganizationRoleEntity) {
|
||||||
|
if (this.organization.id !== newRole.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
this.role = newRole;
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { OrganizationMismatch } from "./errors/OrganizationMismatch.js";
|
||||||
|
import type { OrganizationEntity } from "./organization.entity.js";
|
||||||
|
import type { OrganizationRoleEntity } from "./organization-role.entity.js";
|
||||||
|
|
||||||
|
export class OrganizationMemberEntity {
|
||||||
|
constructor(
|
||||||
|
public id: string,
|
||||||
|
public createdAt: Date,
|
||||||
|
public updatedAt: Date,
|
||||||
|
public identityId: string, // cross-domain
|
||||||
|
public organization: OrganizationEntity,
|
||||||
|
public role: OrganizationRoleEntity,
|
||||||
|
) {
|
||||||
|
if (organization.id !== role.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeRole(newRole: OrganizationRoleEntity) {
|
||||||
|
if (this.organization.id !== newRole.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
this.role = newRole;
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/modules/organization/domain/organization-role.entity.ts
Normal file
12
src/modules/organization/domain/organization-role.entity.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { OrganizationEntity } from "./organization.entity.js";
|
||||||
|
|
||||||
|
export class OrganizationRoleEntity {
|
||||||
|
constructor(
|
||||||
|
public id: string,
|
||||||
|
public name: string,
|
||||||
|
public permissions: string[],
|
||||||
|
public createdAt: Date,
|
||||||
|
public updatedAt: Date,
|
||||||
|
public organization: OrganizationEntity
|
||||||
|
) { }
|
||||||
|
}
|
||||||
28
src/modules/organization/domain/organization.entity.ts
Normal file
28
src/modules/organization/domain/organization.entity.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { OrganizationMismatch } from "./errors/OrganizationMismatch.js";
|
||||||
|
import type { OrganizationMemberEntity } from "./organization-member.entity.js";
|
||||||
|
import type { OrganizationRoleEntity } from "./organization-role.entity.js";
|
||||||
|
|
||||||
|
export class OrganizationEntity {
|
||||||
|
constructor(
|
||||||
|
public id: string,
|
||||||
|
public name: string,
|
||||||
|
public createdAt: Date,
|
||||||
|
public updatedAt: Date,
|
||||||
|
public members: OrganizationMemberEntity[],
|
||||||
|
public roles: OrganizationRoleEntity[],
|
||||||
|
) { }
|
||||||
|
|
||||||
|
addMember(member: OrganizationMemberEntity) {
|
||||||
|
if (this.id !== member.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
this.members.push(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRole(role: OrganizationRoleEntity) {
|
||||||
|
if (this.id !== role.organization.id) {
|
||||||
|
throw new OrganizationMismatch();
|
||||||
|
}
|
||||||
|
this.roles.push(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/modules/organization/domain/organization.repo.ts
Normal file
5
src/modules/organization/domain/organization.repo.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { IBaseRepository } from "@/shared/core/IBaseRepository.js";
|
||||||
|
import type { OrganizationEntity } from "./organization.entity.js";
|
||||||
|
|
||||||
|
export interface IOrganizationRepository
|
||||||
|
extends IBaseRepository<OrganizationEntity> { }
|
||||||
1
src/modules/organization/domain/organization.symbols.ts
Normal file
1
src/modules/organization/domain/organization.symbols.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const OrganizationDomain = {};
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { ContainerModule } from "inversify";
|
import { ContainerModule } from "inversify";
|
||||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: This bounded context is empty.
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: This bounded context is empty.
|
||||||
export const UserDIModule = new ContainerModule(({ bind }) => {});
|
export const OrganizationDIModule = new ContainerModule(({ bind }) => { });
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const UserDomain = {};
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Container } from "inversify";
|
import { Container } from "inversify";
|
||||||
import { AuthDIModule } from "@/modules/auth/infrastructure/di/auth.di.js";
|
import { AuthDIModule } from "@/modules/auth/infrastructure/di/auth.di.js";
|
||||||
import { UserDIModule } from "@/modules/user/infrastructure/di/user.di.js";
|
import { OrganizationDIModule } from "@/modules/organization/infrastructure/di/organization.di.js";
|
||||||
import { SharedDIModule } from "./shared.di.js";
|
import { SharedDIModule } from "./shared.di.js";
|
||||||
|
|
||||||
const appContainer = new Container();
|
const appContainer = new Container();
|
||||||
|
|
||||||
appContainer.load(SharedDIModule);
|
appContainer.load(SharedDIModule);
|
||||||
appContainer.load(AuthDIModule);
|
appContainer.load(AuthDIModule);
|
||||||
appContainer.load(UserDIModule);
|
appContainer.load(OrganizationDIModule);
|
||||||
|
|
||||||
export { appContainer };
|
export { appContainer };
|
||||||
|
|||||||
Reference in New Issue
Block a user