v0.11.26 new rules for default admin user
This commit is contained in:
@@ -283,8 +283,8 @@ async function seedAdmin(schema) {
|
||||
if (!existing) {
|
||||
const hash = bcrypt.hashSync(adminPass, 10);
|
||||
const ur = await queryResult(schema, `
|
||||
INSERT INTO users (name, email, password, role, status, is_default_admin, must_change_password)
|
||||
VALUES ($1, $2, $3, 'admin', 'active', TRUE, TRUE) RETURNING id
|
||||
INSERT INTO users (name, email, password, role, status, is_default_admin, must_change_password, avatar)
|
||||
VALUES ($1, $2, $3, 'admin', 'active', TRUE, TRUE, '/avatar/admin.png') RETURNING id
|
||||
`, [adminName, adminEmail, hash]);
|
||||
const adminId = ur.rows[0].id;
|
||||
|
||||
@@ -312,6 +312,10 @@ async function seedAdmin(schema) {
|
||||
}
|
||||
|
||||
console.log(`[DB:${schema}] Default admin exists (id=${existing.id})`);
|
||||
// Always ensure admin has the fixed avatar
|
||||
await exec(schema,
|
||||
"UPDATE users SET avatar='/avatar/admin.png', updated_at=NOW() WHERE is_default_admin=TRUE AND (avatar IS NULL OR avatar != '/avatar/admin.png')"
|
||||
);
|
||||
if (pwReset) {
|
||||
const hash = bcrypt.hashSync(adminPass, 10);
|
||||
await exec(schema,
|
||||
|
||||
@@ -188,7 +188,13 @@ router.post('/', authMiddleware, async (req, res) => {
|
||||
for (const u of allUsers) await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [groupId, u.id]);
|
||||
} else {
|
||||
await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [groupId, req.user.id]);
|
||||
if (memberIds?.length > 0) for (const uid of memberIds) await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [groupId, uid]);
|
||||
if (memberIds?.length > 0) {
|
||||
const defaultAdmin = await queryOne(req.schema, 'SELECT id FROM users WHERE is_default_admin=TRUE');
|
||||
for (const uid of memberIds) {
|
||||
if (defaultAdmin && uid === defaultAdmin.id) continue;
|
||||
await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [groupId, uid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
await emitGroupNew(req.schema, io, groupId);
|
||||
res.json({ group: await queryOne(req.schema, 'SELECT * FROM groups WHERE id=$1', [groupId]) });
|
||||
@@ -231,6 +237,8 @@ router.post('/:id/members', authMiddleware, async (req, res) => {
|
||||
if (group.type !== 'private') return res.status(400).json({ error: 'Cannot manually add members to public groups' });
|
||||
if (group.is_direct) return res.status(400).json({ error: 'Cannot add members to a direct message' });
|
||||
if (group.owner_id !== req.user.id && req.user.role !== 'admin') return res.status(403).json({ error: 'Only owner can add members' });
|
||||
const targetUser = await queryOne(req.schema, 'SELECT is_default_admin FROM users WHERE id=$1', [userId]);
|
||||
if (targetUser?.is_default_admin) return res.status(400).json({ error: 'Default admin cannot be added to private groups' });
|
||||
await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [group.id, userId]);
|
||||
const addedUser = await queryOne(req.schema, 'SELECT name,display_name FROM users WHERE id=$1', [userId]);
|
||||
const addedName = addedUser?.display_name || addedUser?.name || 'Unknown';
|
||||
|
||||
@@ -225,7 +225,9 @@ router.post('/', authMiddleware, teamManagerMiddleware, async (req, res) => {
|
||||
[name.trim(), dmGroupId]
|
||||
);
|
||||
const ugId = ugr.rows[0].id;
|
||||
const defaultAdmin = await queryOne(req.schema, 'SELECT id FROM users WHERE is_default_admin=TRUE');
|
||||
for (const uid of memberIds) {
|
||||
if (defaultAdmin && uid === defaultAdmin.id) continue;
|
||||
await exec(req.schema, 'INSERT INTO user_group_members (user_group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [ugId, uid]);
|
||||
await addUserSilent(req.schema, dmGroupId, uid);
|
||||
}
|
||||
@@ -249,7 +251,9 @@ router.patch('/:id', authMiddleware, teamManagerMiddleware, async (req, res) =>
|
||||
}
|
||||
|
||||
if (Array.isArray(memberIds) && ug.dm_group_id) {
|
||||
const defaultAdmin = await queryOne(req.schema, 'SELECT id FROM users WHERE is_default_admin=TRUE');
|
||||
const newIds = new Set(memberIds.map(Number).filter(Boolean));
|
||||
if (defaultAdmin) newIds.delete(defaultAdmin.id); // default admin cannot be in user groups
|
||||
const currentSet = new Set((await query(req.schema, 'SELECT user_id FROM user_group_members WHERE user_group_id=$1', [ug.id])).map(r => r.user_id));
|
||||
const addedUids = [], removedUids = [];
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ router.patch('/me/profile', authMiddleware, async (req, res) => {
|
||||
|
||||
// Upload avatar
|
||||
router.post('/me/avatar', authMiddleware, uploadAvatar.single('avatar'), async (req, res) => {
|
||||
if (req.user.is_default_admin) return res.status(403).json({ error: 'Default admin avatar cannot be changed' });
|
||||
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
||||
try {
|
||||
const sharp = require('sharp');
|
||||
|
||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${1:-0.11.25}"
|
||||
VERSION="${1:-0.11.26}"
|
||||
ACTION="${2:-}"
|
||||
REGISTRY="${REGISTRY:-}"
|
||||
IMAGE_NAME="jama"
|
||||
|
||||
BIN
frontend/public/avatar/admin.png
Normal file
BIN
frontend/public/avatar/admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@@ -1,6 +1,14 @@
|
||||
export default function Avatar({ user, size = 'md', className = '' }) {
|
||||
if (!user) return null;
|
||||
|
||||
if (user.is_default_admin) {
|
||||
return (
|
||||
<div className={`avatar avatar-${size} ${className}`}>
|
||||
<img src="/avatar/admin.png" alt="Admin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const initials = (() => {
|
||||
const name = user.display_name || user.name || '';
|
||||
const parts = name.trim().split(' ').filter(Boolean);
|
||||
|
||||
@@ -76,17 +76,19 @@ export default function ProfileModal({ onClose }) {
|
||||
<div className="flex items-center gap-3" style={{ gap: 16, marginBottom: 20 }}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Avatar user={user} size="xl" />
|
||||
<label title="Change avatar" style={{
|
||||
position: 'absolute', bottom: 0, right: 0,
|
||||
background: 'var(--primary)', color: 'white', borderRadius: '50%',
|
||||
width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
cursor: 'pointer', fontSize: 12
|
||||
}}>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||||
<input type="file" accept="image/*"
|
||||
style={{ opacity: 0, position: 'absolute', width: '100%', height: '100%', top: 0, left: 0, cursor: 'pointer' }}
|
||||
onChange={handleAvatarUpload} />
|
||||
</label>
|
||||
{!user?.is_default_admin && (
|
||||
<label title="Change avatar" style={{
|
||||
position: 'absolute', bottom: 0, right: 0,
|
||||
background: 'var(--primary)', color: 'white', borderRadius: '50%',
|
||||
width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
cursor: 'pointer', fontSize: 12
|
||||
}}>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||||
<input type="file" accept="image/*"
|
||||
style={{ opacity: 0, position: 'absolute', width: '100%', height: '100%', top: 0, left: 0, cursor: 'pointer' }}
|
||||
onChange={handleAvatarUpload} />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 16 }}>{user?.display_name || user?.name}</div>
|
||||
|
||||
Reference in New Issue
Block a user