Skip to content

Adding Features

Adding a New API Endpoint

1. Define the route in CDK

Edit infrastructure/lib/task-time-stack.ts:

// Add resource under existing API structure
const newResource = api.root.addResource('api').addResource('my-feature');

// Add method with Cognito auth
newResource.addMethod('GET', new apigateway.LambdaIntegration(myLambda), {
  authorizer: cognitoAuthorizer,
  authorizationType: apigateway.AuthorizationType.COGNITO,
});

// Add OPTIONS for CORS
newResource.addMethod('OPTIONS', new apigateway.MockIntegration({
  integrationResponses: [{
    statusCode: '200',
    responseParameters: {
      'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'",
      'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,POST,PUT,PATCH,DELETE'",
      'method.response.header.Access-Control-Allow-Origin': "'*'",
    },
  }],
  requestTemplates: { 'application/json': '{"statusCode": 200}' },
}), {
  methodResponses: [{
    statusCode: '200',
    responseParameters: {
      'method.response.header.Access-Control-Allow-Headers': true,
      'method.response.header.Access-Control-Allow-Methods': true,
      'method.response.header.Access-Control-Allow-Origin': true,
    },
  }],
});

2. Add handler logic

In the Lambda handler (e.g., infrastructure/lambda/task-sync/index.ts):

if (path === '/api/my-feature' && method === 'GET') {
  // Handler logic here
  return {
    statusCode: 200,
    headers: corsHeaders,
    body: JSON.stringify({ data: result }),
  };
}

3. Add types

Update packages/shared/src/types.ts with request/response types.

4. Deploy

pnpm build:lambdas && cd infrastructure && pnpm cdk deploy

Adding a New DynamoDB Table

1. Define table in CDK

Edit infrastructure/lib/task-time-stack.ts:

const myTable = new dynamodb.Table(this, 'MyTable', {
  tableName: 'task-time-my-table',
  partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

// Optional: Add GSI
myTable.addGlobalSecondaryIndex({
  indexName: 'status-index',
  partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAt', type: dynamodb.AttributeType.STRING },
});

2. Grant Lambda access

myTable.grantReadWriteData(myLambda);

// Pass table name to Lambda
myLambda.addEnvironment('MY_TABLE_NAME', myTable.tableName);

3. Add types

Update packages/shared/src/types.ts:

export interface MyItem {
  id: string;
  status: string;
  createdAt: string;
  updatedAt: string;
}

4. Use in Lambda

const tableName = process.env.MY_TABLE_NAME!;
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

const result = await docClient.send(new GetCommand({
  TableName: tableName,
  Key: { id: itemId },
}));

Adding a New Web Page

1. Create the page component

Create apps/web/app/(dashboard)/my-page/page.tsx:

'use client';

export default function MyPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold text-brand-foreground mb-6">My Page</h1>
      {/* Page content */}
    </div>
  );
}

2. Add navigation item

Edit apps/web/app/(dashboard)/layout.tsx:

import { MyIcon } from '@heroicons/react/24/outline';

// Inside the navigation array (for internal users):
{ name: 'My Page', href: '/my-page', icon: MyIcon },

Adding Sub-Tabs

If your page needs tabs, create a layout with SubTabNav:

Create apps/web/app/(dashboard)/my-page/layout.tsx:

'use client';

import SubTabNav from '../../../components/SubTabNav';

export default function MyPageLayout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <SubTabNav basePath="/my-page" tabs={[
        { name: 'Overview', href: 'overview' },
        { name: 'Details', href: 'details' },
      ]} />
      {children}
    </div>
  );
}

Then create pages for each tab: - apps/web/app/(dashboard)/my-page/overview/page.tsx - apps/web/app/(dashboard)/my-page/details/page.tsx


Adding a New Lambda Function

1. Create Lambda directory

infrastructure/lambda/my-function/
└── index.ts

2. Write the handler

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Content-Type,Authorization',
};

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  const { httpMethod: method, path } = event;

  try {
    // Route handling
    if (path === '/api/my-endpoint' && method === 'GET') {
      return {
        statusCode: 200,
        headers: corsHeaders,
        body: JSON.stringify({ message: 'Hello' }),
      };
    }

    return { statusCode: 404, headers: corsHeaders, body: '{"error":"Not found"}' };
  } catch (error) {
    console.error(error);
    return { statusCode: 500, headers: corsHeaders, body: '{"error":"Internal server error"}' };
  }
};

3. Define in CDK stack

Edit infrastructure/lib/task-time-stack.ts:

const myFunction = new lambda.Function(this, 'MyFunction', {
  functionName: 'task-time-my-function',
  runtime: lambda.Runtime.NODEJS_20_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, '../lambda/my-function/dist')),
  memorySize: 512,
  timeout: cdk.Duration.seconds(30),
  environment: {
    // Environment variables
  },
});

4. Build and deploy

The Lambda will be automatically bundled by infrastructure/scripts/build-lambdas.cjs when you run:

pnpm build:lambdas && cd infrastructure && pnpm cdk deploy