feat: add organization schema
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"start": "node dist/app.js",
|
||||
"dev": "tsx watch src/app.ts",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:migrate-reset": "prisma migrate reset",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:push": "prisma db push",
|
||||
"test": "vitest",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
model AuthIdentity {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
password String
|
||||
isVerified Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
organizationMemberships OrganizationUserMembership[]
|
||||
verifications AuthVerification[]
|
||||
sentInvitations OrganizationInvitation[]
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
password String
|
||||
isVerified Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
memberships OrganizationMember[]
|
||||
verifications 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";
|
||||
// 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 { 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";
|
||||
|
||||
const appContainer = new Container();
|
||||
|
||||
appContainer.load(SharedDIModule);
|
||||
appContainer.load(AuthDIModule);
|
||||
appContainer.load(UserDIModule);
|
||||
appContainer.load(OrganizationDIModule);
|
||||
|
||||
export { appContainer };
|
||||
|
||||
Reference in New Issue
Block a user