Add Activity Feed Feature
Implement a persistent activity timeline with backend storage, API, and UI integration
The route
6 steps to Done
- 01
Define Activity data model
Create a durable schema for storing activity events
Preview prompt + verify gate ▾ Hide ▴
In the project's prisma/schema.prisma file, add a new model named Activity with the following exact fields and attributes: 1. id Int @id @default(autoincrement()) 2. userId Int // foreign key to User.id 3. type String // short descriptor of the action (e.g., "post_created") 4. payload Json // any additional data needed for the UI 5. createdAt DateTime @default(now()) After defining the model, add a relation to the existing User model: `user User @relation(fields: [userId], references: [id], onDelete: Cascade)`. Run `npx prisma migrate dev --name add-activity-model` to generate and apply the migration. Finally, run `npx prisma generate` to refresh the client. Verify the migration SQL creates a table named "Activity" with the specified columns and foreign key.
- ✓ Does prisma/schema.prisma contain a model named Activity with all five fields?
- ✓ Is there a migration file named something like `2023xxxxxx_add-activity-model.sql`?
- ✓ Can you query the database (e.g., via psql) and see an Activity table with the correct columns?
- 02
Create server‑side API endpoints
Expose activities via a RESTful interface for the frontend
Preview prompt + verify gate ▾ Hide ▴
In the Express router file (e.g., src/routes/activity.ts), implement two endpoints: 1. `GET /api/activities` – returns a JSON array of Activity records ordered by createdAt descending. Accept optional query parameters `page` (default 1) and `limit` (default 20). Use Prisma `findMany` with `skip` and `take`. Require authentication via `requireAuth` and only return activities belonging to the authenticated user (`where: { userId: req.userId }`). 2. `POST /api/activities` – creates a new Activity record. Expect a JSON body with `type` (string) and `payload` (object). Populate `userId` from `req.userId`. Use Prisma `create`. Return the created record with status 201. Add proper TypeScript typings, error handling (400 for validation errors, 401 for unauthenticated), and export the router for inclusion in `app.ts`. Do not include any UI code.
- ✓ Does GET /api/activities return a JSON list ordered newest‑first and respect page/limit?
- ✓ Does POST /api/activities create a record in the database and return it with status 201?
- ✓ Are both routes protected by `requireAuth` and scoped to `req.userId`?
- 03
Emit activity events from key actions
Automatically generate activity records when users perform important actions
Preview prompt + verify gate ▾ Hide ▴
Modify the existing business‑logic functions so they record an Activity each time a user creates a post or likes a post: 1. In `src/services/postService.ts`, after the `prisma.post.create` call succeeds, invoke `prisma.activity.create` with `userId` = the creator's id, `type` = "post_created", and `payload` containing `{ postId, title }`. 2. In `src/services/likeService.ts`, after a successful `prisma.like.create`, invoke `prisma.activity.create` with `userId` = liker id, `type` = "post_liked", and `payload` containing `{ postId }`. Do not expose these calls via the UI; they should be pure backend side‑effects. Include proper error handling: if the activity creation fails, log the error but do not abort the original operation.
- ✓ After creating a post, does a new Activity with type "post_created" exist?
- ✓ After liking a post, does a new Activity with type "post_liked" exist?
- ✓ If activity creation throws, does the original post/like still succeed?
- 04
Build the ActivityFeed React component
Display a paginated list of activities on the client side
Preview prompt + verify gate ▾ Hide ▴
In `src/components/ActivityFeed.tsx`, implement a React functional component that: 1. Uses `useState` to hold an array of activities and pagination state (`page`, `hasMore`). 2. On mount (`useEffect`), calls `axios.get('/api/activities', { params: { page, limit: 20 } })` with the auth token from the existing `authContext`. 3. Renders each activity as a list item showing: - Formatted `createdAt` (e.g., `MM/DD/YYYY HH:mm`) - A human‑readable message based on `type` and `payload` (e.g., "You created a post titled 'Hello'" or "You liked a post"). 4. Adds a "Load more" button that increments `page` and appends new activities when `hasMore` is true. 5. Handles loading and error states with simple UI feedback. Export the component as default. Do not include any mock data; the component must call the real endpoint.
- ✓ When the Dashboard loads, does the ActivityFeed show at least one real activity (if present)?
- ✓ Does clicking "Load more" fetch the next page and append items?
- ✓ Are timestamps formatted and messages human‑readable?
- 05
Integrate ActivityFeed into the Dashboard
Make the feed part of the existing user interface
Preview prompt + verify gate ▾ Hide ▴
Edit `src/pages/Dashboard.tsx` to: 1. Import the default ActivityFeed component. 2. Insert `<ActivityFeed />` inside the main JSX beneath the existing overview widgets, ensuring proper layout (e.g., within a `<section>` with a heading "Recent Activity"). 3. Verify that the page still compiles and that the feed appears when a logged‑in user visits the Dashboard. Do not modify any unrelated code; keep existing imports untouched.
- ✓ When navigating to /dashboard, is the "Recent Activity" section visible?
- ✓ Does the section list real activity items (not placeholders)?
- ✓ Does the page still load without TypeScript or runtime errors?
- 06
Test end‑to‑end functionality
Confirm the whole pipeline works from action to UI
Preview prompt + verify gate ▾ Hide ▴
Add two test files: 1. `tests/api/activity.integration.test.ts` – using SuperTest, authenticate a test user, perform a POST request to `/api/posts` (or the existing post‑creation endpoint), then GET `/api/activities?page=1&limit=10` and assert the returned array contains an object with `type: "post_created"` and a payload matching the created post. 2. `src/components/__tests__/ActivityFeed.test.tsx` – using React Testing Library, mock the Axios GET request to return a single activity, render `<ActivityFeed />` within a router and auth context, and assert that the human‑readable message appears in the document. Both tests must run with `npm test` and pass. Do not use dummy data without asserting it matches the model.
- ✓ Does the integration test create a post and then find a matching activity in the DB?
- ✓ Does the ActivityFeed unit test render the mocked activity message correctly?
- ✓ Does `npm test` finish with zero failing tests?