Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions apps/server/src/application/commands/change-member-role.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// ChangeMemberRoleCommand - 조직원 역할 변경 Command

import { Organization } from '../../domain/organization/organization.domain';
import { Member } from '../../domain/member/member.domain';
import {
OrganizationMember,
OrganizationRole,
} from '../../domain/organization-member/organization-member.domain';
import { OrganizationRepository } from '../../domain/organization/organization.repository';
import { MemberRepository } from '../../domain/member/member.repository';
import { OrganizationMemberRepository } from '../../domain/organization-member/organization-member.repository';

/**
* 조직원 역할 변경 요청 데이터
*/
export interface ChangeMemberRoleRequest {
organizationSlug: string;
memberDiscordId: string;
newRole: OrganizationRole;
}

/**
* 조직원 역할 변경 결과
*/
export interface ChangeMemberRoleResult {
organizationMember: OrganizationMember;
organization: Organization;
member: Member;
}

/**
* 조직원 역할 변경 Command (Use Case)
*
* 책임:
* 1. 조직 존재 확인
* 2. 멤버 존재 확인
* 3. 조직원 존재 확인
* 4. 역할 변경
* 5. 저장
*/
export class ChangeMemberRoleCommand {
constructor(
private readonly organizationRepo: OrganizationRepository,
private readonly memberRepo: MemberRepository,
private readonly organizationMemberRepo: OrganizationMemberRepository
) {}

async execute(
request: ChangeMemberRoleRequest
): Promise<ChangeMemberRoleResult> {
// 1. 조직 존재 확인
const organization = await this.organizationRepo.findBySlug(
request.organizationSlug
);
if (!organization) {
throw new Error(`Organization "${request.organizationSlug}" not found`);
}

// 2. 멤버 존재 확인
const member = await this.memberRepo.findByDiscordId(
request.memberDiscordId
);
if (!member) {
throw new Error(
`Member with Discord ID ${request.memberDiscordId} not found`
);
}

// 3. 조직원 존재 확인
const organizationMember =
await this.organizationMemberRepo.findByOrganizationAndMember(
organization.id,
member.id
);

if (!organizationMember) {
throw new Error(
`Member is not part of organization "${request.organizationSlug}"`
);
}

// 4. 역할 변경
organizationMember.changeRole(request.newRole);

// 5. 저장
await this.organizationMemberRepo.save(organizationMember);

return {
organizationMember,
organization,
member,
};
}
}
1 change: 1 addition & 0 deletions apps/server/src/application/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './add-member-to-organization.command';
export * from './update-member-status.command';
export * from './join-organization.command';
export * from './join-generation.command';
export * from './change-member-role.command';
1 change: 1 addition & 0 deletions apps/server/src/presentation/graphql/mappers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './member.mapper';
export * from './generation.mapper';
export * from './cycle.mapper';
export * from './organization.mapper';
export * from './organization-member.mapper';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// OrganizationMember Mapper

import { OrganizationMember } from '@/domain/organization-member/organization-member.domain';
import { GqlOrganizationMember, GqlMember } from '../types';

export const domainToGraphqlOrganizationMember = (
organizationMember: OrganizationMember,
member?: GqlMember
): GqlOrganizationMember => {
return new GqlOrganizationMember(organizationMember, member);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@
import {
DrizzleGenerationRepository,
DrizzleOrganizationRepository,
DrizzleGenerationMemberRepository,
DrizzleMemberRepository,
} from '@/infrastructure/persistence/drizzle';
import { domainToGraphqlOrganization } from '../mappers';
import { GqlGeneration } from '../types';
import { GqlGeneration, GqlGenerationMember } from '../types';
import { GenerationMember } from '@/domain/generation-member/generation-member.domain';
import { Member } from '@/domain/member/member.domain';

Check failure on line 18 in apps/server/src/presentation/graphql/resolvers/generation.resolver.ts

View workflow job for this annotation

GitHub Actions / Lint

'Member' is defined but never used

// ========================================
// Repository Instances
// ========================================

const generationRepo = new DrizzleGenerationRepository();
const organizationRepo = new DrizzleOrganizationRepository();
const generationMemberRepo = new DrizzleGenerationMemberRepository();
const memberRepo = new DrizzleMemberRepository();

// ========================================
// Query & Command Instances
Expand All @@ -35,17 +41,44 @@
// Helper Functions
// ========================================

async function loadGenerationWithOrganization(
// Helper 함수: GenerationMember를 GqlGenerationMember로 변환
async function mapToGqlGenerationMember(
generationMember: GenerationMember
): Promise<GqlGenerationMember> {
const member = await memberRepo.findById(generationMember.memberId);
if (!member) {
throw new Error(`Member ${generationMember.memberId.value} not found`);
}

return new GqlGenerationMember(generationMember, {
id: member.id.value,
github: member.githubUsername?.value ?? '',
discordId: member.discordId.value,
name: member.name.value,
createdAt: member.createdAt.toISOString(),
});
}

async function loadGenerationWithOrganizationAndMembers(
generation: Awaited<ReturnType<typeof getGenerationByIdQuery.execute>>
): Promise<GqlGeneration | null> {
if (!generation) return null;

const organization = await organizationRepo.findById(
OrganizationId.create(generation.organizationId)
);

const generationMembers =
await generationMemberRepo.findByGeneration(generation.id.value);
const members = await Promise.all(
generationMembers.map(mapToGqlGenerationMember)
);

return new GqlGeneration(
generation,
organization ? domainToGraphqlOrganization(organization) : undefined
organization ? domainToGraphqlOrganization(organization) : undefined,
undefined,
members
);
}

Expand All @@ -62,9 +95,18 @@
const organization = await organizationRepo.findById(
OrganizationId.create(gen.organizationId)
);

const generationMembers =
await generationMemberRepo.findByGeneration(gen.id.value);
const members = await Promise.all(
generationMembers.map(mapToGqlGenerationMember)
);

return new GqlGeneration(
gen,
organization ? domainToGraphqlOrganization(organization) : undefined
organization ? domainToGraphqlOrganization(organization) : undefined,
undefined,
members
);
})
);
Expand All @@ -74,13 +116,13 @@
// 기수 단건 조회
generation: async (id: number): Promise<GqlGeneration | null> => {
const generation = await getGenerationByIdQuery.execute(id);
return loadGenerationWithOrganization(generation);
return loadGenerationWithOrganizationAndMembers(generation);
},

// 활성화된 기수 조회
activeGeneration: async (): Promise<GqlGeneration | null> => {
const generation = await generationRepo.findActive();
return loadGenerationWithOrganization(generation);
return loadGenerationWithOrganizationAndMembers(generation);
},
};

Expand All @@ -99,9 +141,18 @@
const organization = await organizationRepo.findById(
OrganizationId.create(result.generation.organizationId)
);

const generationMembers =
await generationMemberRepo.findByGeneration(result.generation.id.value);
const members = await Promise.all(
generationMembers.map(mapToGqlGenerationMember)
);

return new GqlGeneration(
result.generation,
organization ? domainToGraphqlOrganization(organization) : undefined
organization ? domainToGraphqlOrganization(organization) : undefined,
undefined,
members
);
},
};
6 changes: 6 additions & 0 deletions apps/server/src/presentation/graphql/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
organizationQueries,
organizationMutations,
} from './organization.resolver';
import {
organizationMemberQueries,
organizationMemberMutations,
} from './organization-member.resolver';

// ========================================
// Query Resolvers 합치기
Expand All @@ -17,6 +21,7 @@ export const queryResolvers = {
...generationQueries,
...cycleQueries,
...organizationQueries,
...organizationMemberQueries,
};

// ========================================
Expand All @@ -28,4 +33,5 @@ export const mutationResolvers = {
...generationMutations,
...cycleMutations,
...organizationMutations,
...organizationMemberMutations,
};
Loading
Loading