Skip to content

Commit

Permalink
feature: Add user_organizations migration and update related entities
Browse files Browse the repository at this point in the history
  • Loading branch information
PooyaRaki committed Feb 5, 2025
1 parent 9623ede commit 187960d
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 6 deletions.
46 changes: 46 additions & 0 deletions migrations/1738679234042-user_organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';

export class UserOrganization1738679234042 implements MigrationInterface {
name = 'UserOrganization1738679234042';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "user_organizations" ("id" SERIAL NOT NULL, "name" character varying(255), "role" integer NOT NULL, "status" integer NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "userId" integer NOT NULL, "organizationId" integer NOT NULL, CONSTRAINT "UQ_user_organizations" UNIQUE ("userId", "organizationId"), CONSTRAINT "PK_UO_id" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE INDEX "idx_UO_role_status" ON "user_organizations" ("role", "status")`,
);
await queryRunner.query(
`CREATE INDEX "idx_UO_name" ON "user_organizations" ("name")`,
);
await queryRunner.query(
`ALTER TABLE "user_organizations" ADD CONSTRAINT "FK_UO_user_id" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "user_organizations" ADD CONSTRAINT "FK_UO_organization_id" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(`
CREATE TRIGGER update_updated_at
BEFORE UPDATE
ON
user_organizations
FOR EACH ROW
EXECUTE PROCEDURE update_updated_at();
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP TRIGGER IF EXISTS update_updated_at ON user_organizations;`,
);
await queryRunner.query(
`ALTER TABLE "user_organizations" DROP CONSTRAINT "FK_UO_organization_id"`,
);
await queryRunner.query(
`ALTER TABLE "user_organizations" DROP CONSTRAINT "FK_UO_user_id"`,
);
await queryRunner.query(`DROP INDEX "public"."idx_UO_name"`);
await queryRunner.query(`DROP INDEX "public"."idx_UO_role_status"`);
await queryRunner.query(`DROP TABLE "user_organizations"`);
}
}
18 changes: 16 additions & 2 deletions src/datasources/organizations/entities/organizations.entity.db.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
import {
Column,
Entity,
Index,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import {
OrganizationStatus,
Organization as DomainOrganization,
} from '@/domain/organizations/entities/organization.entity';
import { UserOrganization } from '@/datasources/users/entities/user-organizations.entity.db';

// @todo make organizations singular, The database table name should remain plural
@Entity('organizations')
export class Organizations implements DomainOrganization {
export class Organization implements DomainOrganization {
@PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_org_id' })
id!: number;

Expand All @@ -29,4 +37,10 @@ export class Organizations implements DomainOrganization {
default: () => 'CURRENT_TIMESTAMP',
})
updated_at!: Date;

@OneToMany(
() => UserOrganization,
(userOrganization: UserOrganization) => userOrganization.organization,
)
userOrganizations!: Array<UserOrganization>;
}
75 changes: 75 additions & 0 deletions src/datasources/users/entities/user-organizations.entity.db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import { User } from '@/datasources/users/entities/users.entity.db';
import {
UserOrganizationRole,
UserOrganizationStatus,
} from '@/domain/users/entities/user-organization.entity';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';

@Entity('user_organizations')
@Unique('UQ_user_organizations', ['user', 'organization'])
@Index('idx_UO_name', ['name'])
@Index('idx_UO_role_status', ['role', 'status'])
export class UserOrganization {
@PrimaryGeneratedColumn({
primaryKeyConstraintName: 'PK_UO_id',
})
id!: number;

@ManyToOne(() => User, (user: User) => user.id, {
cascade: true,
nullable: false,
})
@JoinColumn({
foreignKeyConstraintName: 'FK_UO_user_id',
})
user!: User;

@ManyToOne(
() => Organization,
(organization: Organization) => organization.id,
{
cascade: true,
nullable: false,
},
)
@JoinColumn({
foreignKeyConstraintName: 'FK_UO_organization_id',
})
organization!: Organization;

@Column({ type: 'varchar', length: 255, nullable: true })
name!: string | null;

// Postgres enums are string therefore we use integer
@Column({
type: 'integer',
})
role!: UserOrganizationRole;

// Postgres enums are string therefore we use integer
@Column({
type: 'integer',
})
status!: UserOrganizationStatus;

@Column({
type: 'timestamp with time zone',
default: () => 'CURRENT_TIMESTAMP',
})
created_at!: Date;

@Column({
type: 'timestamp with time zone',
default: () => 'CURRENT_TIMESTAMP',
})
updated_at!: Date;
}
8 changes: 8 additions & 0 deletions src/datasources/users/entities/users.entity.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
User as DomainUser,
} from '@/domain/users/entities/user.entity';
import { Wallet } from '@/datasources/wallets/entities/wallets.entity.db';
import { UserOrganization } from '@/datasources/users/entities/user-organizations.entity.db';

@Entity('users')
export class User implements DomainUser {
@PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_id' })
id!: number;

// Postgres enums are string therefore we use integer
@Index('idx_user_status')
@Column({
type: 'integer',
Expand All @@ -38,4 +40,10 @@ export class User implements DomainUser {
default: () => 'CURRENT_TIMESTAMP',
})
updated_at!: Date;

@OneToMany(
() => UserOrganization,
(userOrganization: UserOrganization) => userOrganization.user,
)
userOrganizations!: Array<UserOrganization>;
}
1 change: 1 addition & 0 deletions src/datasources/wallets/entities/wallets.entity.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class Wallet implements z.infer<typeof WalletSchema> {

@ManyToOne(() => User, (user: User) => user.id, {
onDelete: 'CASCADE',
nullable: false,
})
@JoinColumn({
name: 'user_id',
Expand Down
5 changes: 3 additions & 2 deletions src/domain/organizations/organizations.repository.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Module } from '@nestjs/common';
import { PostgresDatabaseModuleV2 } from '@/datasources/db/v2/postgres-database.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Organizations } from '@/datasources/organizations/entities/organizations.entity.db';
import { Organization } from '@/datasources/organizations/entities/organizations.entity.db';
import { IOrganizationsRepository } from '@/domain/organizations/organizations.repository.interface';
import { OrganizationsRepository } from '@/domain/organizations/organizations.repository';
import { UserOrganization } from '@/datasources/users/entities/user-organizations.entity.db';

@Module({
imports: [
PostgresDatabaseModuleV2,
TypeOrmModule.forFeature([Organizations]),
TypeOrmModule.forFeature([Organization, UserOrganization]),
],
providers: [
{
Expand Down
9 changes: 9 additions & 0 deletions src/domain/users/entities/user-organization.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum UserOrganizationRole {
ADMIN = 1,
MEMBER = 2,
}

export enum UserOrganizationStatus {
PENDING = 0,
ACTIVE = 1,
}
2 changes: 1 addition & 1 deletion src/domain/users/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RowSchema } from '@/datasources/db/v1/entities/row.entity';

export enum UserStatus {
ACTIVE = 1,
PENDING = 2,
PENDING = 0,
}

export type User = z.infer<typeof UserSchema>;
Expand Down
3 changes: 2 additions & 1 deletion src/domain/users/users.repository.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { IUsersRepository } from '@/domain/users/users.repository.interface';
import { UsersRepository } from '@/domain/users/users.repository';
import { Wallet } from '@/datasources/wallets/entities/wallets.entity.db';
import { WalletsRepositoryModule } from '@/domain/wallets/wallets.repository.module';
import { UserOrganization } from '@/datasources/users/entities/user-organizations.entity.db';

@Module({
imports: [
PostgresDatabaseModuleV2,
TypeOrmModule.forFeature([User, Wallet]),
TypeOrmModule.forFeature([User, Wallet, UserOrganization]),
WalletsRepositoryModule,
],
providers: [
Expand Down
1 change: 1 addition & 0 deletions src/routes/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class UsersController {
return await this.usersService.delete(authPayload);
}

// @todo move wallet methods to Wallet controller
@ApiOkResponse({ type: CreatedUserWithWallet })
@ApiUnauthorizedResponse({ description: 'Signer address not provided' })
@ApiConflictResponse({ description: 'Wallet already exists' })
Expand Down

0 comments on commit 187960d

Please sign in to comment.