Add badges, checkins, and transport endpoints

Introduce gamification and check-in support plus expanded transport/admin APIs. Added DDL for checkins, badges and user_badges. New repositories: badgeRepository, userBadgeRepository, checkinRepository. New routes: admin badge management, admin transport (lines/platforms/stations), public transport endpoints, user checkins and stations endpoints, and user badges on /me. Services updated: adminService (badge CRUD & user assignment), userService (checkins, badge queries), authService (hasAdminRights). Minor package.json name update. These changes implement database, repo, service and route wiring for badges, user_badins and checkin features and additional transport admin routes.
This commit is contained in:
2026-09-23 02:09:39 +02:00
parent e5eccb77db
commit b03f5c91e6
22 changed files with 686 additions and 24 deletions
+86 -1
View File
@@ -1,5 +1,7 @@
const { DefaultError } = require("../errors/errors")
const banRepo = require("../repositories/banRepo")
const badgeRepo = require("../repositories/badgeRepository")
const userBadgeRepo = require("../repositories/userBadgeRepository")
async function banUser(userId, message, expiresAt) {
const existingBan = await banRepo.isBanned(userId)
@@ -31,8 +33,91 @@ async function isUserBanned(userId) {
return true
}
async function createBadge({ name, description, icon, condition }) {
const existing = await badgeRepo.findAllBadges()
if (existing.some(b => b.name === name)) {
throw new DefaultError(409, "Badge already exists", "Duplicate badge name")
}
return await badgeRepo.createBadge(name, description, icon, condition)
}
async function getAllBadges() {
return await badgeRepo.findAllBadges()
}
async function getBadgeById({ badgeId }) {
const badge = await badgeRepo.findBadgeById(badgeId)
if (!badge) {
throw new DefaultError(404, "Badge not found", "Badge ID does not exist")
}
return badge
}
async function updateBadge({ badgeId, updateData }) {
const badge = await badgeRepo.findBadgeById(badgeId)
if (!badge) {
throw new DefaultError(404, "Badge not found", "Badge ID does not exist")
}
const allowedFields = ["name", "description", "icon", "condition"]
const filteredData = Object.keys(updateData)
.filter(key => allowedFields.includes(key))
.reduce((obj, key) => {
obj[key] = updateData[key]
return obj
}, {})
if (Object.keys(filteredData).length === 0) {
throw new DefaultError(400, "No valid fields to update", "Invalid update data")
}
return await badgeRepo.updateBadge(badgeId, filteredData)
}
async function deleteBadge({ badgeId }) {
const badge = await badgeRepo.findBadgeById(badgeId)
if (!badge) {
throw new DefaultError(404, "Badge not found", "Badge ID does not exist")
}
return await badgeRepo.deleteBadge(badgeId)
}
async function assignBadgeToUser({ userId, badgeId }) {
const badge = await badgeRepo.findBadgeById(badgeId)
if (!badge) {
throw new DefaultError(404, "Badge not found", "Badge ID does not exist")
}
const hasAlready = await userBadgeRepo.hasBadge(userId, badgeId)
if (hasAlready) {
throw new DefaultError(409, "User already has this badge", "Duplicate badge assignment")
}
return await userBadgeRepo.assignBadgeToUser(userId, badgeId)
}
async function removeBadgeFromUser({ userId, badgeId }) {
const badge = await badgeRepo.findBadgeById(badgeId)
if (!badge) {
throw new DefaultError(404, "Badge not found", "Badge ID does not exist")
}
const success = await userBadgeRepo.removeBadgeFromUser(userId, badgeId)
if (!success) {
throw new DefaultError(404, "Badge not assigned to user", "User does not have this badge")
}
return success
}
module.exports = {
banUser,
unbanUser,
isUserBanned
isUserBanned,
createBadge,
getAllBadges,
getBadgeById,
updateBadge,
deleteBadge,
assignBadgeToUser,
removeBadgeFromUser
}
+8
View File
@@ -111,6 +111,13 @@ async function isLogged(req, res, next) {
next()
}
function hasAdminRights(req, res, next) {
if (req.user?.role == "admin") {
return next()
}
throw new DefaultError(403, "Forbidden", "You must be administrator.")
}
module.exports = {
isLogged,
loginLocal,
@@ -118,5 +125,6 @@ module.exports = {
resetPassword,
registerLocal,
changePassword,
hasAdminRights,
refreshAccessToken,
}
+45 -1
View File
@@ -1,4 +1,6 @@
const userRepo = require("../repositories/usersRepo")
const userBadgeRepo = require("../repositories/userBadgeRepository")
const checkinRepo = require("../repositories/checkinRepository")
const cache = require("../modules/cache")
async function getProfile({ userId }) {
@@ -30,11 +32,53 @@ async function getShadowUser({ serverId, remoteId }) {
return await userRepo.findByServerAndRemoteId(serverId, remoteId)
}
async function getUserBadges({ userId }) {
return await userBadgeRepo.findUserBadges(userId)
}
async function countUserBadges({ userId }) {
return await userBadgeRepo.countUserBadges(userId)
}
async function hasBadge({ userId, badgeId }) {
return await userBadgeRepo.hasBadge(userId, badgeId)
}
async function createCheckin({ userId, stationId, platformId, latitude, longitude, distance }) {
const checkinId = await checkinRepo.createCheckin(userId, stationId, platformId, latitude, longitude, distance)
cache.users.delete(userId)
return checkinId
}
async function getUserCheckins({ userId, limit = 50, offset = 0 }) {
return await checkinRepo.findCheckinsByUserId(userId, limit, offset)
}
async function getUserCheckinsCount({ userId }) {
return await checkinRepo.countCheckinsByUser(userId)
}
async function getUserUniqueStations({ userId }) {
return await checkinRepo.findUniqueStationsByUser(userId)
}
async function getUserStationCheckinsCount({ userId, stationId }) {
return await checkinRepo.countCheckinsByUserAndStation(userId, stationId)
}
module.exports = {
isLocal,
getProfile,
createUser,
updateProfile,
deleteAccount,
getShadowUser
getShadowUser,
getUserBadges,
countUserBadges,
hasBadge,
createCheckin,
getUserCheckins,
getUserCheckinsCount,
getUserUniqueStations,
getUserStationCheckinsCount
}