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
9 changes: 5 additions & 4 deletions apps/backend/db/db_setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ CREATE TABLE users (
CREATE TABLE projects (
project_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
total_budget NUMERIC(12,2),
start_date DATE,
end_date DATE,
Expand Down Expand Up @@ -71,10 +72,10 @@ INSERT INTO users (name, email, is_admin) VALUES
('Renee Reddy', 'renee@branch.org', TRUE),
('Nour Shoreibah', 'nour@branch.org', TRUE);

INSERT INTO projects (name, total_budget, start_date, end_date, currency) VALUES
('Clinician Communication Study', 500000, '2025-01-01', '2026-01-01', 'USD'),
('Health Education Initiative', 300000, '2025-03-01', '2026-03-01', 'USD'),
('Policy Advocacy Program', 200000, '2025-06-01', '2026-06-01', 'USD');
INSERT INTO projects (name, description, total_budget, start_date, end_date, currency) VALUES
('Clinician Communication Study', 'Study of clinician-patient communication patterns', 500000, '2025-01-01', '2026-01-01', 'USD'),
('Health Education Initiative', 'Community health education and outreach', 300000, '2025-03-01', '2026-03-01', 'USD'),
('Policy Advocacy Program', 'Advocacy and policy change efforts', 200000, '2025-06-01', '2026-06-01', 'USD');

INSERT INTO donors (organization, contact_name, contact_email) VALUES
('NIH', 'Dr. Sarah Lee', 'sarah@nih.gov'),
Expand Down
1 change: 1 addition & 0 deletions apps/backend/lambdas/auth/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BranchProjects {
created_at: Generated<Timestamp | null>;
currency: Generated<string | null>;
end_date: Timestamp | null;
description: string | null;
name: string;
project_id: Generated<number>;
start_date: Timestamp | null;
Expand Down
1 change: 1 addition & 0 deletions apps/backend/lambdas/donors/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BranchProjects {
created_at: Generated<Timestamp | null>;
currency: Generated<string | null>;
end_date: Timestamp | null;
description: string | null;
name: string;
project_id: Generated<number>;
start_date: Timestamp | null;
Expand Down
1 change: 1 addition & 0 deletions apps/backend/lambdas/expenditures/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BranchProjects {
created_at: Generated<Timestamp | null>;
currency: Generated<string | null>;
end_date: Timestamp | null;
description: string | null;
name: string;
project_id: Generated<number>;
start_date: Timestamp | null;
Expand Down
1 change: 1 addition & 0 deletions apps/backend/lambdas/projects/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BranchProjects {
created_at: Generated<Timestamp | null>;
currency: Generated<string | null>;
end_date: Timestamp | null;
description: string | null;
name: string;
project_id: Generated<number>;
start_date: Timestamp | null;
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/lambdas/projects/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,15 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
if (!currencyResult.isValid) return json(400, { message: currencyResult.error });
if (currencyResult.value !== null) values.currency = currencyResult.value;

const descriptionResult = ProjectValidationUtils.validateDescription(body.description);
if (!descriptionResult.isValid) return json(400, { message: descriptionResult.error });
if (descriptionResult.value !== null) values.description = descriptionResult.value;

try {
const inserted = await db
.insertInto('branch.projects')
.values(values)
.returning(['project_id','name','total_budget','currency','start_date','end_date','created_at'])
.returning(['project_id','name','description','total_budget','currency','start_date','end_date','created_at'])
.executeTakeFirst();

return json(201, inserted);
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/lambdas/projects/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ paths:
type: string
total_budget:
type: number
description:
type: string
responses:
'200':
description: OK
2 changes: 2 additions & 0 deletions apps/backend/lambdas/projects/test/projects.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('POST /projects (e2e)', () => {
start_date: '2025-03-01',
end_date: '2025-09-30',
currency: 'EUR',
description: 'End-to-end project description',
}),
});
expect(res.status).toBe(201);
Expand All @@ -45,6 +46,7 @@ describe('POST /projects (e2e)', () => {
expect(json.start_date).toContain('2025-03-01');
expect(json.end_date).toContain('2025-09-30');
expect(json.currency).toBe('EUR');
expect(json.description).toBe('End-to-end project description');
});

test('400 when name missing', async () => {
Expand Down
6 changes: 4 additions & 2 deletions apps/backend/lambdas/projects/test/projects.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ test('201: creates project with all fields', async () => {
name: 'AllFieldsUnit',
total_budget: 12345.67,
start_date: '2025-01-01',
end_date: '2025-12-31',
currency: 'USD',
end_date: '2025-12-31',
currency: 'USD',
description: 'Unit test project description',
}),
} as any);
expect(res.statusCode).toBe(201);
Expand All @@ -56,6 +57,7 @@ test('201: creates project with all fields', async () => {
expect(json.start_date).toContain('2025-01-01');
expect(json.end_date).toContain('2025-12-31');
expect(json.currency).toBe('USD');
expect(json.description).toBe('Unit test project description');
});

// Validation errors (400)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need a test case for an invalid description here?

Expand Down
15 changes: 15 additions & 0 deletions apps/backend/lambdas/projects/validation-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,19 @@ export class ProjectValidationUtils {
}
return { isValid: true, value: c };
}

// validates optional description field - if provided, trims the whitespace
static validateDescription(input: unknown): ValidationResult<string | null> {
if (input === undefined || input === null) {
return { isValid: true, value: null };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to confirm my understanding, are there any cases we would be returning isValid with false?

}
if (typeof input !== 'string') {
return { isValid: true, value: null };
}
const d = input.trim();
if (d.length === 0) {
return { isValid: true, value: null };
}
return { isValid: true, value: d };
}
}
1 change: 1 addition & 0 deletions apps/backend/lambdas/users/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BranchProjects {
created_at: Generated<Timestamp | null>;
currency: Generated<string | null>;
end_date: Timestamp | null;
description: string | null;
name: string;
project_id: Generated<number>;
start_date: Timestamp | null;
Expand Down