-- Database Schema (users.sql)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role user_role DEFAULT 'user',
created_at TIMESTAMP DEFAULT NOW(),
last_login TIMESTAMP
);
CREATE TABLE user_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id INTEGER REFERENCES users(id),
expires_at TIMESTAMP NOT NULL
);
-- API Endpoint (users.js)
app.get('/api/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
const result = await db.query(`
SELECT id, email, role, created_at, last_login
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`, [limit, offset]);
res.json(result.rows);
});
// Frontend (users.html)
<div id="users"></div>
<script>
fetch('/api/users')
.then(r => r.json())
.then(users => {
document.getElementById('users').innerHTML = users
.map(u => `<div>${u.email} - ${u.role}</div>`)
.join('');
});
</script>
// userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async ({ page, limit }, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users?page=${page}&limit=${limit}`);
if (!response.ok) throw new Error('Failed to fetch');
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const userSlice = createSlice({
name: 'users',
initialState: {
items: [],
loading: false,
error: null,
pagination: { page: 1, limit: 10, total: 0 }
},
reducers: {
setPage: (state, action) => {
state.pagination.page = action.payload;
},
clearError: (state) => {
state.error = null;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});
// UserList.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers, setPage } from './userSlice';
const UserList = () => {
const dispatch = useDispatch();
const { items, loading, error, pagination } = useSelector(state => state.users);
useEffect(() => {
dispatch(fetchUsers({ page: pagination.page, limit: pagination.limit }));
}, [dispatch, pagination.page, pagination.limit]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{items.map(user => (
<UserCard key={user.id} user={user} />
))}
<Pagination
current={pagination.page}
onChange={(page) => dispatch(setPage(page))}
/>
</div>
);
};
// UserCard.jsx
const UserCard = React.memo(({ user }) => (
<div className="user-card">
<span>{user.email}</span>
<span>{user.role}</span>
</div>
));
// Pagination.jsx
const Pagination = ({ current, onChange }) => {
return (
<div className="pagination">
{/* 50 more lines of pagination logic */}
</div>
);
};
-- Database Schema
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
channel_id INTEGER REFERENCES channels(id),
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Trigger for real-time notifications
CREATE OR REPLACE FUNCTION notify_new_message()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('new_message',
json_build_object(
'id', NEW.id,
'user_id', NEW.user_id,
'channel_id', NEW.channel_id,
'content', NEW.content,
'created_at', NEW.created_at
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER message_notify
AFTER INSERT ON messages
FOR EACH ROW EXECUTE FUNCTION notify_new_message();
// Server (chat.js)
const client = new pg.Client();
client.connect();
client.query('LISTEN new_message');
client.on('notification', (msg) => {
const message = JSON.parse(msg.payload);
io.to(`channel_${message.channel_id}`).emit('message', message);
});
app.post('/api/messages', async (req, res) => {
const { content, channel_id } = req.body;
const user_id = req.user.id;
await db.query(
'INSERT INTO messages (user_id, channel_id, content) VALUES ($1, $2, $3)',
[user_id, channel_id, content]
);
res.json({ success: true });
});
// Frontend (chat.html)
<div id="messages"></div>
<input id="messageInput" placeholder="Type a message...">
<script>
const socket = io();
socket.on('message', (msg) => {
const div = document.createElement('div');
div.textContent = `${msg.user_id}: ${msg.content}`;
document.getElementById('messages').appendChild(div);
});
document.getElementById('messageInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
fetch('/api/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: e.target.value,
channel_id: 1
})
});
e.target.value = '';
}
});
</script>
// chatSaga.js
import { takeEvery, call, put, fork, take } from 'redux-saga/effects';
import { eventChannel } from 'redux-saga';
function createSocketChannel(socket) {
return eventChannel(emit => {
socket.on('message', (message) => emit({ type: 'SOCKET_MESSAGE', payload: message }));
socket.on('disconnect', () => emit({ type: 'SOCKET_DISCONNECT' }));
return () => socket.disconnect();
});
}
function* watchSocketConnection() {
const socket = io();
const socketChannel = yield call(createSocketChannel, socket);
while (true) {
try {
const action = yield take(socketChannel);
yield put(action);
} catch (error) {
yield put({ type: 'SOCKET_ERROR', payload: error.message });
}
}
}
function* sendMessage(action) {
try {
yield call(fetch, '/api/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action.payload)
});
} catch (error) {
yield put({ type: 'SEND_MESSAGE_FAILED', payload: error.message });
}
}
function* chatSaga() {
yield fork(watchSocketConnection);
yield takeEvery('SEND_MESSAGE', sendMessage);
}
// chatSlice.js
const chatSlice = createSlice({
name: 'chat',
initialState: {
messages: [],
connected: false,
typing: {},
error: null
},
reducers: {
socketConnected: (state) => {
state.connected = true;
},
socketDisconnected: (state) => {
state.connected = false;
},
messageReceived: (state, action) => {
state.messages.push(action.payload);
},
setTyping: (state, action) => {
state.typing[action.payload.userId] = action.payload.isTyping;
}
}
});
// MessageList.jsx
const MessageList = () => {
const messages = useSelector(state => state.chat.messages);
const messagesEndRef = useRef(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="messages-container">
{messages.map(message => (
<Message key={message.id} message={message} />
))}
<div ref={messagesEndRef} />
</div>
);
};
// MessageInput.jsx
const MessageInput = () => {
const [message, setMessage] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (message.trim()) {
dispatch({ type: 'SEND_MESSAGE', payload: { content: message } });
setMessage('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message..."
/>
</form>
);
};
-- Database Views for Analytics
CREATE VIEW daily_user_stats AS
SELECT
DATE(created_at) as date,
COUNT(*) as new_users,
COUNT(DISTINCT CASE WHEN last_login >= DATE(created_at) THEN id END) as active_users
FROM users
GROUP BY DATE(created_at)
ORDER BY date DESC;
CREATE VIEW message_stats AS
SELECT
DATE(created_at) as date,
COUNT(*) as total_messages,
COUNT(DISTINCT user_id) as active_users,
AVG(LENGTH(content)) as avg_message_length
FROM messages
GROUP BY DATE(created_at)
ORDER BY date DESC;
-- API Endpoint
app.get('/api/analytics', async (req, res) => {
const [userStats, messageStats] = await Promise.all([
db.query('SELECT * FROM daily_user_stats LIMIT 30'),
db.query('SELECT * FROM message_stats LIMIT 30')
]);
res.json({
users: userStats.rows,
messages: messageStats.rows
});
});
// Frontend (dashboard.html)
<canvas id="userChart" width="400" height="200"></canvas>
<canvas id="messageChart" width="400" height="200"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
fetch('/api/analytics')
.then(r => r.json())
.then(data => {
new Chart(document.getElementById('userChart'), {
type: 'line',
data: {
labels: data.users.map(d => d.date),
datasets: [{
label: 'New Users',
data: data.users.map(d => d.new_users),
borderColor: '#0f0'
}]
}
});
new Chart(document.getElementById('messageChart'), {
type: 'bar',
data: {
labels: data.messages.map(d => d.date),
datasets: [{
label: 'Messages',
data: data.messages.map(d => d.total_messages),
backgroundColor: '#ff6b6b'
}]
}
});
});
</script>
// analyticsSlice.js
const analyticsSlice = createSlice({
name: 'analytics',
initialState: {
userStats: { data: [], loading: false, error: null },
messageStats: { data: [], loading: false, error: null },
dateRange: { start: null, end: null },
filters: { userType: 'all', messageType: 'all' }
},
reducers: {
setDateRange: (state, action) => {
state.dateRange = action.payload;
},
setFilters: (state, action) => {
state.filters = { ...state.filters, ...action.payload };
},
// ... 20 more reducers
}
});
// ChartContainer.jsx
const ChartContainer = ({ type, data, options }) => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (chartInstance.current) {
chartInstance.current.destroy();
}
const ctx = chartRef.current.getContext('2d');
chartInstance.current = new Chart(ctx, {
type,
data,
options
});
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
}
};
}, [type, data, options]);
return <canvas ref={chartRef} />;
};
// AnalyticsDashboard.jsx
const AnalyticsDashboard = () => {
const dispatch = useDispatch();
const { userStats, messageStats, dateRange, filters } = useSelector(state => state.analytics);
useEffect(() => {
dispatch(fetchUserStats({ dateRange, filters }));
dispatch(fetchMessageStats({ dateRange, filters }));
}, [dispatch, dateRange, filters]);
const userChartData = useMemo(() => ({
labels: userStats.data.map(d => d.date),
datasets: [{
label: 'New Users',
data: userStats.data.map(d => d.new_users),
borderColor: '#0f0'
}]
}), [userStats.data]);
const messageChartData = useMemo(() => ({
labels: messageStats.data.map(d => d.date),
datasets: [{
label: 'Messages',
data: messageStats.data.map(d => d.total_messages),
backgroundColor: '#ff6b6b'
}]
}), [messageStats.data]);
if (userStats.loading || messageStats.loading) {
return <LoadingSpinner />;
}
return (
<div className="analytics-dashboard">
<DateRangePicker
value={dateRange}
onChange={(range) => dispatch(setDateRange(range))}
/>
<FilterPanel
filters={filters}
onFilterChange={(newFilters) => dispatch(setFilters(newFilters))}
/>
<div className="charts-grid">
<ChartContainer
type="line"
data={userChartData}
options={userChartOptions}
/>
<ChartContainer
type="bar"
data={messageChartData}
options={messageChartOptions}
/>
</div>
</div>
);
};