From 0d6b901995e954318774c235415256a76e82f1b6 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 9 Oct 2025 23:21:06 +0000 Subject: [PATCH] feat: Add POST /api/v1/families endpoint for creating new families MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created CreateFamilyDto to validate family creation requests - Added createFamily() service method with duplicate family check - Implemented POST /api/v1/families controller endpoint - Auto-creates family with parent role and full permissions - Fixes 404 error during onboarding flow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../modules/families/dto/create-family.dto.ts | 8 ++++ .../modules/families/families.controller.ts | 21 ++++++++ .../src/modules/families/families.service.ts | 48 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 maternal-app/maternal-app-backend/src/modules/families/dto/create-family.dto.ts diff --git a/maternal-app/maternal-app-backend/src/modules/families/dto/create-family.dto.ts b/maternal-app/maternal-app-backend/src/modules/families/dto/create-family.dto.ts new file mode 100644 index 0000000..82812b2 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/modules/families/dto/create-family.dto.ts @@ -0,0 +1,8 @@ +import { IsString, IsNotEmpty, MaxLength } from 'class-validator'; + +export class CreateFamilyDto { + @IsString() + @IsNotEmpty() + @MaxLength(100) + name: string; +} diff --git a/maternal-app/maternal-app-backend/src/modules/families/families.controller.ts b/maternal-app/maternal-app-backend/src/modules/families/families.controller.ts index 72d4166..ee5262e 100644 --- a/maternal-app/maternal-app-backend/src/modules/families/families.controller.ts +++ b/maternal-app/maternal-app-backend/src/modules/families/families.controller.ts @@ -14,12 +14,33 @@ import { import { FamiliesService } from './families.service'; import { InviteFamilyMemberDto } from './dto/invite-family-member.dto'; import { JoinFamilyDto } from './dto/join-family.dto'; +import { CreateFamilyDto } from './dto/create-family.dto'; import { FamilyRole } from '../../database/entities/family-member.entity'; @Controller('api/v1/families') export class FamiliesController { constructor(private readonly familiesService: FamiliesService) {} + /** + * Create a new family + * POST /api/v1/families + * Body: { name: string } + */ + @Post() + async createFamily(@Req() req: any, @Body() createFamilyDto: CreateFamilyDto) { + const family = await this.familiesService.createFamily( + req.user.userId, + createFamilyDto, + ); + + return { + success: true, + data: { + family, + }, + }; + } + @Post('invite') async inviteMember( @Req() req: any, diff --git a/maternal-app/maternal-app-backend/src/modules/families/families.service.ts b/maternal-app/maternal-app-backend/src/modules/families/families.service.ts index da398dd..38ff640 100644 --- a/maternal-app/maternal-app-backend/src/modules/families/families.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/families/families.service.ts @@ -15,6 +15,7 @@ import { import { User } from '../../database/entities/user.entity'; import { InviteFamilyMemberDto } from './dto/invite-family-member.dto'; import { JoinFamilyDto } from './dto/join-family.dto'; +import { CreateFamilyDto } from './dto/create-family.dto'; import { EmailService } from '../../common/services/email.service'; @Injectable() @@ -29,6 +30,53 @@ export class FamiliesService { private emailService: EmailService, ) {} + /** + * Create a new family + */ + async createFamily( + userId: string, + createFamilyDto: CreateFamilyDto, + ): Promise { + // Check if user already has a family + const existingMembership = await this.familyMemberRepository.findOne({ + where: { userId }, + }); + + if (existingMembership) { + throw new ConflictException( + 'You are already a member of a family. Leave your current family before creating a new one.', + ); + } + + // Create the family (ID and shareCode are auto-generated via @BeforeInsert) + const family = this.familyRepository.create({ + name: createFamilyDto.name, + shareCodeExpiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days + createdBy: userId, + subscriptionTier: 'free', // Default tier + }); + + const savedFamily = await this.familyRepository.save(family); + + // Add creator as a parent (admin) of the family + const creatorMembership = this.familyMemberRepository.create({ + userId, + familyId: savedFamily.id, + role: FamilyRole.PARENT, + permissions: { + canAddChildren: true, + canEditChildren: true, + canLogActivities: true, + canViewReports: true, + canInviteMembers: true, + }, + }); + + await this.familyMemberRepository.save(creatorMembership); + + return savedFamily; + } + async inviteMember( userId: string, familyId: string,