Admin API
Administrative endpoints for managing the platform. All endpoints require admin authentication.
⚠️
Admin endpoints require ADMIN or SUPER_ADMIN role. Unauthorized access returns 403 Forbidden.
Listings Management
List All Listings (Admin)
GET /api/admin/listingsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status |
type | string | - | Filter by type |
featured | boolean | - | Filter featured only |
page | number | 1 | Page number |
limit | number | 20 | Items per page |
Response
{
"data": [
{
"id": "clx123...",
"title": "Luxury Beach Villa",
"status": "PUBLISHED",
"type": "ACCOMMODATION",
"region": "Greater Accra",
"priceGHS": "850.00",
"featured": true,
"viewCount": 1250,
"bookingCount": 45,
"createdAt": "2024-01-15T10:30:00Z"
}
],
"pagination": { ... },
"stats": {
"total": 156,
"published": 142,
"draft": 10,
"pending": 4
}
}Create Listing
POST /api/admin/listingsRequest Body
{
"type": "ACCOMMODATION",
"title": "Mountain Lodge Retreat",
"description": "A peaceful mountain getaway...",
"highlights": ["Mountain views", "Private pool", "Chef service"],
"region": "Ashanti",
"city": "Kumasi",
"area": "Lake Bosomtwe",
"address": "Lake Bosomtwe Road",
"latitude": 6.5091,
"longitude": -1.4093,
"priceGHS": 650,
"propertyType": "VILLA",
"bedrooms": 4,
"bathrooms": 3,
"maxGuests": 8,
"checkInTime": "14:00",
"checkOutTime": "11:00",
"amenityIds": ["clxABC...", "clxDEF..."],
"featured": false
}Response
{
"data": {
"id": "clx456...",
"slug": "mountain-lodge-retreat-kumasi",
"status": "DRAFT",
...
}
}Update Listing
PATCH /api/admin/listings/{id}Request Body
{
"title": "Updated Title",
"priceGHS": 750,
"status": "PUBLISHED"
}Delete Listing
DELETE /api/admin/listings/{id}⚠️
Deleting a listing with active bookings will fail. Cancel or complete bookings first.
Upload Listing Images
POST /api/admin/listings/{id}/images
Content-Type: multipart/form-dataRequest
files[]: (binary)
files[]: (binary)Response
{
"data": [
{
"id": "clxIMG1...",
"url": "https://res.cloudinary.com/...",
"publicId": "y3nko/listings/abc123",
"isPrimary": true,
"order": 0
}
]
}Bookings Management
List All Bookings (Admin)
GET /api/admin/bookingsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status |
startDate | string | - | Start of date range |
endDate | string | - | End of date range |
listingId | string | - | Filter by listing |
page | number | 1 | Page number |
limit | number | 20 | Items per page |
Response
{
"data": [
{
"id": "clx789...",
"reference": "Y3K-ABC123",
"status": "CONFIRMED",
"checkIn": "2024-03-15",
"checkOut": "2024-03-18",
"total": "3123.75",
"user": {
"id": "clxUSR...",
"email": "guest@example.com",
"profile": { "firstName": "Kwame", "lastName": "Asante" }
},
"listing": {
"id": "clx123...",
"title": "Luxury Beach Villa"
},
"createdAt": "2024-02-20T10:30:00Z"
}
],
"pagination": { ... },
"stats": {
"total": 250,
"pending": 12,
"confirmed": 180,
"completed": 45,
"cancelled": 13,
"revenue": "485250.00"
}
}Update Booking Status
PATCH /api/admin/bookings/{reference}Request Body
{
"status": "CONFIRMED",
"note": "Manually confirmed after verification"
}Admin Cancel Booking
POST /api/admin/bookings/{reference}/cancelRequest Body
{
"reason": "Property unavailable due to maintenance",
"refundFull": true
}Users Management
List Users
GET /api/admin/usersQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
role | string | - | Filter by role |
search | string | - | Search by name/email |
page | number | 1 | Page number |
limit | number | 20 | Items per page |
Response
{
"data": [
{
"id": "clxUSR...",
"email": "user@example.com",
"role": "USER",
"createdAt": "2024-01-10T08:00:00Z",
"profile": {
"firstName": "Kwame",
"lastName": "Asante",
"avatarUrl": "..."
},
"_count": {
"bookings": 5,
"favorites": 12
}
}
],
"pagination": { ... }
}Update User Role
PATCH /api/admin/users/{id}/roleRequest Body
{
"role": "HOST"
}Only SUPER_ADMIN can promote users to ADMIN role.
Analytics
Dashboard Stats
GET /api/admin/statsResponse
{
"data": {
"users": {
"total": 1250,
"newThisMonth": 85,
"growth": 7.2
},
"bookings": {
"total": 450,
"thisMonth": 42,
"revenue": "125000.00",
"avgBookingValue": "2778.00"
},
"listings": {
"total": 156,
"published": 142,
"avgOccupancy": 68.5
},
"recentBookings": [ ... ],
"topListings": [ ... ]
}
}Server Action Implementation
// lib/actions/admin.ts
"use server"
import { requireRole } from '@/lib/auth'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'
export async function createListing(data: CreateListingInput) {
await requireRole(['ADMIN', 'SUPER_ADMIN'])
const slug = generateSlug(data.title, data.city)
const listing = await prisma.listing.create({
data: {
...data,
slug,
status: 'DRAFT',
amenities: {
create: data.amenityIds.map(id => ({
amenityId: id
}))
}
}
})
revalidatePath('/admin/listings')
return listing
}
export async function updateListingStatus(
listingId: string,
status: ListingStatus
) {
await requireRole(['ADMIN', 'SUPER_ADMIN'])
const listing = await prisma.listing.update({
where: { id: listingId },
data: {
status,
publishedAt: status === 'PUBLISHED' ? new Date() : undefined
}
})
revalidatePath('/admin/listings')
revalidatePath(`/accommodations/${listing.slug}`)
return listing
}
export async function getAdminStats() {
await requireRole(['ADMIN', 'SUPER_ADMIN'])
const [users, bookings, listings, revenue] = await prisma.$transaction([
prisma.user.count(),
prisma.booking.count(),
prisma.listing.count({ where: { status: 'PUBLISHED' } }),
prisma.booking.aggregate({
_sum: { total: true },
where: { status: 'CONFIRMED' }
})
])
return {
users,
bookings,
listings,
revenue: revenue._sum.total || 0
}
}