diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js
index 337bbc5..d9c2d26 100644
--- a/backend/src/routes/users.js
+++ b/backend/src/routes/users.js
@@ -4,7 +4,7 @@ const multer = require('multer');
const path = require('path');
const router = express.Router();
const { query, queryOne, queryResult, exec, addUserToPublicGroups, getOrCreateSupportGroup } = require('../models/db');
-const { authMiddleware, adminMiddleware, teamManagerMiddleware } = require('../middleware/auth');
+const { authMiddleware, teamManagerMiddleware } = require('../middleware/auth');
const avatarStorage = multer.diskStorage({
destination: '/app/uploads/avatars',
@@ -30,7 +30,7 @@ async function resolveUniqueName(schema, baseName, excludeId = null) {
function isValidEmail(e) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e); }
// List users
-router.get('/', authMiddleware, adminMiddleware, async (req, res) => {
+router.get('/', authMiddleware, teamManagerMiddleware, async (req, res) => {
try {
const users = await query(req.schema,
"SELECT id,name,email,role,status,is_default_admin,must_change_password,avatar,about_me,display_name,allow_dm,created_at,last_online FROM users WHERE status != 'deleted' ORDER BY created_at ASC"
@@ -81,7 +81,7 @@ router.get('/check-display-name', authMiddleware, async (req, res) => {
});
// Create user
-router.post('/', authMiddleware, adminMiddleware, async (req, res) => {
+router.post('/', authMiddleware, teamManagerMiddleware, async (req, res) => {
const { name, email, password, role } = req.body;
if (!name || !email) return res.status(400).json({ error: 'Name and email required' });
if (!isValidEmail(email)) return res.status(400).json({ error: 'Invalid email address' });
@@ -107,7 +107,7 @@ router.post('/', authMiddleware, adminMiddleware, async (req, res) => {
});
// Bulk create
-router.post('/bulk', authMiddleware, adminMiddleware, async (req, res) => {
+router.post('/bulk', authMiddleware, teamManagerMiddleware, async (req, res) => {
const { users } = req.body;
const results = { created: [], skipped: [] };
const seenEmails = new Set();
@@ -144,7 +144,7 @@ router.post('/bulk', authMiddleware, adminMiddleware, async (req, res) => {
});
// Patch name
-router.patch('/:id/name', authMiddleware, adminMiddleware, async (req, res) => {
+router.patch('/:id/name', authMiddleware, teamManagerMiddleware, async (req, res) => {
const { name } = req.body;
if (!name?.trim()) return res.status(400).json({ error: 'Name required' });
try {
@@ -157,7 +157,7 @@ router.patch('/:id/name', authMiddleware, adminMiddleware, async (req, res) => {
});
// Patch role
-router.patch('/:id/role', authMiddleware, adminMiddleware, async (req, res) => {
+router.patch('/:id/role', authMiddleware, teamManagerMiddleware, async (req, res) => {
const { role } = req.body;
if (!['member','admin'].includes(role)) return res.status(400).json({ error: 'Invalid role' });
try {
@@ -174,7 +174,7 @@ router.patch('/:id/role', authMiddleware, adminMiddleware, async (req, res) => {
});
// Reset password
-router.patch('/:id/reset-password', authMiddleware, adminMiddleware, async (req, res) => {
+router.patch('/:id/reset-password', authMiddleware, teamManagerMiddleware, async (req, res) => {
const { password } = req.body;
if (!password || password.length < 6) return res.status(400).json({ error: 'Password too short' });
try {
@@ -185,7 +185,7 @@ router.patch('/:id/reset-password', authMiddleware, adminMiddleware, async (req,
});
// Suspend / activate / delete
-router.patch('/:id/suspend', authMiddleware, adminMiddleware, async (req, res) => {
+router.patch('/:id/suspend', authMiddleware, teamManagerMiddleware, async (req, res) => {
try {
const t = await queryOne(req.schema, 'SELECT * FROM users WHERE id=$1', [req.params.id]);
if (!t) return res.status(404).json({ error: 'User not found' });
@@ -196,13 +196,13 @@ router.patch('/:id/suspend', authMiddleware, adminMiddleware, async (req, res)
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
-router.patch('/:id/activate', authMiddleware, adminMiddleware, async (req, res) => {
+router.patch('/:id/activate', authMiddleware, teamManagerMiddleware, async (req, res) => {
try {
await exec(req.schema, "UPDATE users SET status='active', updated_at=NOW() WHERE id=$1", [req.params.id]);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
-router.delete('/:id', authMiddleware, adminMiddleware, async (req, res) => {
+router.delete('/:id', authMiddleware, teamManagerMiddleware, async (req, res) => {
try {
const t = await queryOne(req.schema, 'SELECT * FROM users WHERE id=$1', [req.params.id]);
if (!t) return res.status(404).json({ error: 'User not found' });
diff --git a/build.sh b/build.sh
index df769a0..57b365e 100644
--- a/build.sh
+++ b/build.sh
@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
-VERSION="${1:-0.11.26}"
+VERSION="${1:-0.11.27}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="rosterchirp"
diff --git a/frontend/src/components/ChatWindow.jsx b/frontend/src/components/ChatWindow.jsx
index d8a853c..eb62333 100644
--- a/frontend/src/components/ChatWindow.jsx
+++ b/frontend/src/components/ChatWindow.jsx
@@ -296,19 +296,28 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess
)}
- {messages.map((msg, i) => (
-