feat: remove global admin, open registration, clear DB seed
- All users are equal — no is_admin superuser - Removed AdminPanel, Shield button from sidebar - isOwnerOrAdmin checks only by chat role (owner/admin) - Removed auto-seed of admin user on startup - DB already cleared on server Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
|
|
||||||
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
|
||||||
@@ -94,14 +93,5 @@ export async function initDB() {
|
|||||||
ALTER TABLE chat_members ADD COLUMN IF NOT EXISTS is_pinned BOOLEAN DEFAULT FALSE;
|
ALTER TABLE chat_members ADD COLUMN IF NOT EXISTS is_pinned BOOLEAN DEFAULT FALSE;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Seed admin if no users
|
// No auto-seed — users register themselves
|
||||||
const { rows } = await pool.query('SELECT COUNT(*) FROM users');
|
|
||||||
if (parseInt(rows[0].count) === 0) {
|
|
||||||
const hash = await bcrypt.hash('admin123', 10);
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO users (username, display_name, password_hash, is_admin) VALUES ($1,$2,$3,TRUE)`,
|
|
||||||
['admin', 'Администратор', hash]
|
|
||||||
);
|
|
||||||
console.log('Created admin user: admin / admin123');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,13 +339,13 @@ export default async function chatRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
// Delete chat (owner only)
|
// Delete chat (owner only)
|
||||||
app.delete('/:id', async (req, reply) => {
|
app.delete('/:id', async (req, reply) => {
|
||||||
const { id: userId, isAdmin } = req.user as { id: string; isAdmin: boolean };
|
const { id: userId } = req.user as { id: string };
|
||||||
const { id } = req.params as { id: string };
|
const { id } = req.params as { id: string };
|
||||||
|
|
||||||
const { rows: [member] } = await pool.query(
|
const { rows: [member] } = await pool.query(
|
||||||
'SELECT role FROM chat_members WHERE chat_id = $1 AND user_id = $2', [id, userId]
|
'SELECT role FROM chat_members WHERE chat_id = $1 AND user_id = $2', [id, userId]
|
||||||
);
|
);
|
||||||
if (!member || (member.role !== 'owner' && !isAdmin)) {
|
if (!member || member.role !== 'owner') {
|
||||||
return reply.status(403).send({ error: 'No permission' });
|
return reply.status(403).send({ error: 'No permission' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function ChatHeader({ chat, onBack, onRefresh, onShowInfo }: Prop
|
|||||||
subtitle = `${chat.memberCount} участников`;
|
subtitle = `${chat.memberCount} участников`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOwnerOrAdmin = chat.myRole === 'owner' || chat.myRole === 'admin' || user?.isAdmin;
|
const isOwnerOrAdmin = chat.myRole === 'owner' || chat.myRole === 'admin';
|
||||||
|
|
||||||
async function handleLeave() {
|
async function handleLeave() {
|
||||||
if (!confirm('Выйти из чата?')) return;
|
if (!confirm('Выйти из чата?')) return;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function ChatInfoPanel({ chat, onClose, onRefresh }: Props) {
|
|||||||
const [uploadingAvatar, setUploadingAvatar] = useState(false);
|
const [uploadingAvatar, setUploadingAvatar] = useState(false);
|
||||||
const fileRef = useRef<HTMLInputElement>(null);
|
const fileRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const isOwnerOrAdmin = chat.myRole === 'owner' || chat.myRole === 'admin' || me?.isAdmin;
|
const isOwnerOrAdmin = chat.myRole === 'owner' || chat.myRole === 'admin';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (addMode) {
|
if (addMode) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Search, Edit, Settings, Shield, Wifi, WifiOff, MessageCircle } from 'lucide-react';
|
import { Search, Edit, Settings, Wifi, WifiOff, MessageCircle } from 'lucide-react';
|
||||||
import { useStore } from '../store';
|
import { useStore } from '../store';
|
||||||
import { wsClient } from '../api/ws';
|
import { wsClient } from '../api/ws';
|
||||||
import api from '../api/client';
|
import api from '../api/client';
|
||||||
@@ -9,7 +9,6 @@ import MessageList from '../components/MessageList';
|
|||||||
import ChatHeader from '../components/ChatHeader';
|
import ChatHeader from '../components/ChatHeader';
|
||||||
import ChatInfoPanel from '../components/ChatInfoPanel';
|
import ChatInfoPanel from '../components/ChatInfoPanel';
|
||||||
import NewChatModal from '../components/NewChatModal';
|
import NewChatModal from '../components/NewChatModal';
|
||||||
import AdminPanel from '../components/admin/AdminPanel';
|
|
||||||
import ProfileModal from '../components/ProfileModal';
|
import ProfileModal from '../components/ProfileModal';
|
||||||
import { usePushNotifications } from '../hooks/usePushNotifications';
|
import { usePushNotifications } from '../hooks/usePushNotifications';
|
||||||
import { Chat } from '../types';
|
import { Chat } from '../types';
|
||||||
@@ -19,7 +18,6 @@ export default function MainLayout() {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [searchUsers, setSearchUsers] = useState<any[]>([]);
|
const [searchUsers, setSearchUsers] = useState<any[]>([]);
|
||||||
const [showNew, setShowNew] = useState(false);
|
const [showNew, setShowNew] = useState(false);
|
||||||
const [showAdmin, setShowAdmin] = useState(false);
|
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
const [showProfile, setShowProfile] = useState(false);
|
const [showProfile, setShowProfile] = useState(false);
|
||||||
const [mobileChatOpen, setMobileChatOpen] = useState(false);
|
const [mobileChatOpen, setMobileChatOpen] = useState(false);
|
||||||
@@ -132,7 +130,7 @@ export default function MainLayout() {
|
|||||||
if (!activeChat) return false;
|
if (!activeChat) return false;
|
||||||
if (activeChat.type === 'private') return true;
|
if (activeChat.type === 'private') return true;
|
||||||
if (activeChat.type === 'group') return activeChat.canSendMessages !== false;
|
if (activeChat.type === 'group') return activeChat.canSendMessages !== false;
|
||||||
if (activeChat.type === 'channel') return activeChat.myRole === 'owner' || activeChat.myRole === 'admin' || !!user?.isAdmin;
|
if (activeChat.type === 'channel') return activeChat.myRole === 'owner' || activeChat.myRole === 'admin';
|
||||||
return false;
|
return false;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -155,12 +153,6 @@ export default function MainLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{user?.isAdmin && (
|
|
||||||
<button onClick={() => setShowAdmin(true)}
|
|
||||||
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600" title="Панель администратора">
|
|
||||||
<Shield className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button onClick={() => setShowProfile(true)}
|
<button onClick={() => setShowProfile(true)}
|
||||||
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600" title="Профиль">
|
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600" title="Профиль">
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4" />
|
||||||
@@ -297,7 +289,6 @@ export default function MainLayout() {
|
|||||||
if (found) openChat(found);
|
if (found) openChat(found);
|
||||||
} catch {}
|
} catch {}
|
||||||
}} />}
|
}} />}
|
||||||
{showAdmin && <AdminPanel onClose={() => setShowAdmin(false)} />}
|
|
||||||
{showProfile && <ProfileModal onClose={() => setShowProfile(false)} />}
|
{showProfile && <ProfileModal onClose={() => setShowProfile(false)} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user