mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: dashboard page admin
This commit is contained in:
parent
a1ec2263a5
commit
1f9c290010
11 changed files with 248 additions and 2 deletions
|
|
@ -138,7 +138,6 @@ router.get(
|
|||
try {
|
||||
const userinfo = await client.fetchUserInfo(oidcConfig, tokens.access_token, claims.sub);
|
||||
picture = userinfo.picture || picture;
|
||||
console.log('[auth] userinfo claims received:', Object.keys(userinfo), '— picture:', userinfo.picture || '(none)');
|
||||
} catch (err) {
|
||||
console.warn('[auth] failed to fetch userinfo for avatar:', err.message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,26 @@ async function removeSong(setlistId, setlistSongId) {
|
|||
]);
|
||||
}
|
||||
|
||||
async function getStats() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE concert_date < CURRENT_DATE)::int AS past_concerts,
|
||||
COUNT(*) FILTER (WHERE concert_date >= CURRENT_DATE)::int AS upcoming_concerts,
|
||||
COALESCE((
|
||||
SELECT AVG(song_count)
|
||||
FROM (
|
||||
SELECT sl.id, COUNT(ss.id) AS song_count
|
||||
FROM setlists sl
|
||||
LEFT JOIN setlist_songs ss ON ss.setlist_id = sl.id
|
||||
WHERE sl.concert_date < CURRENT_DATE
|
||||
GROUP BY sl.id
|
||||
) t
|
||||
), 0)::numeric(10,1) AS avg_songs_per_concert
|
||||
FROM setlists
|
||||
`);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findNext,
|
||||
findHistory,
|
||||
|
|
@ -142,4 +162,5 @@ module.exports = {
|
|||
replaceSongs,
|
||||
addSong,
|
||||
removeSong,
|
||||
getStats,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ async function removeTutorial(songId, tutorialId) {
|
|||
]);
|
||||
}
|
||||
|
||||
async function getStats() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT COUNT(*)::int AS total_songs,
|
||||
COUNT(DISTINCT NULLIF(artist, ''))::int AS distinct_artists,
|
||||
(SELECT COUNT(*)::int FROM song_tutorials) AS tutorials_added
|
||||
FROM songs
|
||||
`);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findAll,
|
||||
findById,
|
||||
|
|
@ -84,4 +94,5 @@ module.exports = {
|
|||
findTutorials,
|
||||
addTutorial,
|
||||
removeTutorial,
|
||||
getStats,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -84,6 +84,18 @@ async function removeVote(suggestionId, userId) {
|
|||
]);
|
||||
}
|
||||
|
||||
async function getStats() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT COUNT(*)::int AS total,
|
||||
COUNT(*) FILTER (WHERE status = 'pending')::int AS pending,
|
||||
COUNT(*) FILTER (WHERE status = 'approved')::int AS approved,
|
||||
COUNT(*) FILTER (WHERE status = 'rejected')::int AS rejected,
|
||||
(SELECT COUNT(*)::int FROM suggestion_votes) AS total_votes
|
||||
FROM suggestions
|
||||
`);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function promoteToSong(suggestionId, addedBy) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
|
|
@ -134,4 +146,5 @@ module.exports = {
|
|||
upsertVote,
|
||||
removeVote,
|
||||
promoteToSong,
|
||||
getStats,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,4 +32,19 @@ async function getActivityStats(userId) {
|
|||
};
|
||||
}
|
||||
|
||||
module.exports = { findById, upsertFromClaims, getActivityStats };
|
||||
async function findAllWithActivity() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT u.id, u.name, u.avatar_url, u.is_admin, u.created_at, u.updated_at,
|
||||
COALESCE(s.count, 0) AS songs_added,
|
||||
COALESCE(sg.count, 0) AS suggestions_proposed,
|
||||
COALESCE(v.count, 0) AS votes_cast
|
||||
FROM users u
|
||||
LEFT JOIN (SELECT added_by, COUNT(*)::int AS count FROM songs GROUP BY added_by) s ON s.added_by = u.id
|
||||
LEFT JOIN (SELECT suggested_by, COUNT(*)::int AS count FROM suggestions GROUP BY suggested_by) sg ON sg.suggested_by = u.id
|
||||
LEFT JOIN (SELECT user_id, COUNT(*)::int AS count FROM suggestion_votes GROUP BY user_id) v ON v.user_id = u.id
|
||||
ORDER BY (COALESCE(s.count, 0) + COALESCE(sg.count, 0) + COALESCE(v.count, 0)) DESC, u.name
|
||||
`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
module.exports = { findById, upsertFromClaims, getActivityStats, findAllWithActivity };
|
||||
|
|
|
|||
56
src/routes/admin.js
Normal file
56
src/routes/admin.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
const express = require('express');
|
||||
const usersRepo = require('../repositories/usersRepo');
|
||||
const songsRepo = require('../repositories/songsRepo');
|
||||
const suggestionsRepo = require('../repositories/suggestionsRepo');
|
||||
const setlistsRepo = require('../repositories/setlistsRepo');
|
||||
const { requireAdmin } = require('../auth/middleware');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/stats',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const [users, songs, suggestions, concerts] = await Promise.all([
|
||||
usersRepo.findAllWithActivity(),
|
||||
songsRepo.getStats(),
|
||||
suggestionsRepo.getStats(),
|
||||
setlistsRepo.getStats(),
|
||||
]);
|
||||
|
||||
res.json({
|
||||
userCount: users.length,
|
||||
users: users.map((u) => ({
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
avatarUrl: u.avatar_url,
|
||||
isAdmin: u.is_admin,
|
||||
createdAt: u.created_at,
|
||||
lastSeenAt: u.updated_at,
|
||||
songsAdded: u.songs_added,
|
||||
suggestionsProposed: u.suggestions_proposed,
|
||||
votesCast: u.votes_cast,
|
||||
})),
|
||||
songs: {
|
||||
totalSongs: songs.total_songs,
|
||||
distinctArtists: songs.distinct_artists,
|
||||
tutorialsAdded: songs.tutorials_added,
|
||||
},
|
||||
suggestions: {
|
||||
total: suggestions.total,
|
||||
pending: suggestions.pending,
|
||||
approved: suggestions.approved,
|
||||
rejected: suggestions.rejected,
|
||||
totalVotes: suggestions.total_votes,
|
||||
},
|
||||
concerts: {
|
||||
pastConcerts: concerts.past_concerts,
|
||||
upcomingConcerts: concerts.upcoming_concerts,
|
||||
avgSongsPerConcert: concerts.avg_songs_per_concert,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -6,6 +6,7 @@ const suggestionsRoutes = require('./suggestions');
|
|||
const setlistsRoutes = require('./setlists');
|
||||
const musicSearchRoutes = require('./musicSearch');
|
||||
const calendarRoutes = require('./calendar');
|
||||
const adminRoutes = require('./admin');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -16,5 +17,6 @@ router.use('/suggestions', suggestionsRoutes);
|
|||
router.use('/setlists', setlistsRoutes);
|
||||
router.use('/music-search', musicSearchRoutes);
|
||||
router.use('/calendar', calendarRoutes);
|
||||
router.use('/admin', adminRoutes);
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue