Let users edit their profile in account settings

Node · Node · beginner · modification

Adds the backend for the new account settings form: `PATCH /api/me` lets a signed-in user change their display name, time zone and marketing-email opt-in. Input checks live in a small `validateProfile` helper that returns the invalid field names, so the form can highlight them (`400 invalid_profile`); a partial body only touches the fields it sends. The route loads the user, applies the update, saves and answers with the same shape as `GET /api/me`, so the page can re-render from the response.

Express 4.21 API on Node 20 with Sequelize 6 on PostgreSQL. `src/app.js` (unchanged) uses `express.json()` (default 100 kB limit), mounts `app.use('/api/me', requireAuth, meRouter)`, and its error handler answers `400 { error: 'invalid_json' }` for a body `express.json()` refuses (malformed JSON, or a top-level value that is not an object or array) and `500 { error: 'internal' }` for anything else (logged). `requireAuth` (unchanged) answers 401 without a valid session and otherwise sets `req.user = { id }`. The `User` model (`src/models`, unchanged) has `id`, `email`, `passwordHash`, `displayName` (varchar 80), `timezone` (varchar 64, default `UTC`), `marketingEmails` (boolean), `role` (`member` or `admin`; admins manage the organisation, its members and its billing) and `plan` (`free`, `pro` or `team`, which decides the paid features). The settings form sends only the three profile fields. Whitespace in a display name is kept as typed (the form trims it).

Requirements

Files touched

--- src/routes/me.js
 const express = require('express');
 const { User } = require('../models');
+const { validateProfile } = require('../validation/profile');
 
 const router = express.Router();
 });
 
+router.patch('/', async (req, res, next) => {
+  const problems = validateProfile(req.body);
+  if (problems.length > 0) {
+    return res.status(400).json({ error: 'invalid_profile', fields: problems });
+  }
+  try {
+    const user = await User.findByPk(req.user.id);
+    if (!user) return res.status(404).json({ error: 'not_found' });
+    Object.assign(user, req.body);
+    await user.save();
+    res.json(toPublicUser(user));
+  } catch (err) {
+    next(err);
+  }
+});
+
 module.exports = router;
 
--- src/validation/profile.js
+const MAX_DISPLAY_NAME = 80;
+const MAX_TIMEZONE = 64;
+const CONTROL_CHARS = /[\u0000-\u001f\u007f]/;

Review this PR

Node practice