Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
a5eb36a
getting started - basic frontend setup
akhintheruvath May 30, 2026
679870f
Merge pull request #1 from akhintheruvath/phase1mvp-akhin
akhintheruvath May 30, 2026
f10b946
tailwind css setup
akhintheruvath May 30, 2026
e7997b5
Merge pull request #2 from akhintheruvath/phase1mvp-akhin
akhintheruvath May 30, 2026
9f1516a
feat:UI updated for NavBar, Used Dummy Modal for Search system
ArjunO-008 May 30, 2026
59ee81b
refactor:Navbar refactored to more easy code style
ArjunO-008 May 31, 2026
8bd3eea
feat:Location Modal Added
ArjunO-008 May 31, 2026
bd7f898
fix: Change in Modal size change
ArjunO-008 May 31, 2026
ec315f3
feat: Added React-Route-DOM and basic for Pages
ArjunO-008 May 31, 2026
49b18ac
initial backend setup
akhintheruvath May 31, 2026
e7576e4
Merge pull request #5 from akhintheruvath/phase1mvp-akhin
akhintheruvath May 31, 2026
50719c0
feat(backend): add public venue & category read APIs
akhintheruvath Jun 1, 2026
d08d48e
Merge pull request #6 from akhintheruvath/phase1mvp-akhin
akhintheruvath Jun 1, 2026
db04b48
Merge pull request #4 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 2, 2026
bddf432
fix: Removed host related Link and Page
ArjunO-008 Jun 3, 2026
a18d0f9
refactor: file structure
ArjunO-008 Jun 3, 2026
d8acc55
fix: Page Routing
ArjunO-008 Jun 3, 2026
54ac97f
Merge pull request #7 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 3, 2026
702f410
chore: Added Admin Login and Home Page, Set Routing
ArjunO-008 Jun 3, 2026
9f88080
chore: Added Venue Owner Home and Login Pages, Routing
ArjunO-008 Jun 3, 2026
9db88ac
Merge pull request #8 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 3, 2026
a2a235a
feat: Established API Connections, And Display Category Page
ArjunO-008 Jun 3, 2026
41686ba
fix: CORS and /api EndPoint Fixed
ArjunO-008 Jun 3, 2026
91f5c91
feat: Added Venue Page Cards And API responses
ArjunO-008 Jun 3, 2026
6a22925
feat:Added Pagination
ArjunO-008 Jun 3, 2026
2d0b000
Merge pull request #9 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 3, 2026
9021122
wip: Working On Landing Page
ArjunO-008 Jun 4, 2026
fed6106
feat(backend): add Google OAuth login and JWT authentication
akhintheruvath Jun 4, 2026
f77bf1d
Merge branch 'main' of https://github.com/akhintheruvath/BookMyVenue …
akhintheruvath Jun 4, 2026
c2897cd
refactor(auth): extract toPublicUser helper and return user on login
akhintheruvath Jun 4, 2026
5e9c2fc
feat: add Google OAuth sign-in with JWT auth
akhintheruvath Jun 4, 2026
b5c64d5
Merge pull request #10 from akhintheruvath/phase1mvp-akhin
akhintheruvath Jun 4, 2026
d53cbca
Merge branch 'phase1-Arjun' into phase1-Arjun-User
ArjunO-008 Jun 5, 2026
ce1a655
fix: Git Merge, establish connection with backend
ArjunO-008 Jun 5, 2026
16684a6
chore: npm installation
ArjunO-008 Jun 5, 2026
5e1b0db
fix: API intergration of venue and venue Category for Home Page listing
ArjunO-008 Jun 5, 2026
8279731
feat: Hero-Section Banner and routing on it
ArjunO-008 Jun 5, 2026
e6e2a2e
feat: Improve thr UI and Behavior of HomePage Section and cards
ArjunO-008 Jun 5, 2026
9dfd4db
fix: Card Listing in Home and Category Routing link changed to /venue…
ArjunO-008 Jun 5, 2026
d0cc084
feat: Added basic Venue Filtering Logic
ArjunO-008 Jun 5, 2026
242451f
Merge branch 'phase1-Arjun' into phase1-Arjun-User
ArjunO-008 Jun 5, 2026
2d8c3e2
feat: Improved Venue service api for fliter based call logic
ArjunO-008 Jun 5, 2026
923aaec
wip: Working on Venue Page Filtering
ArjunO-008 Jun 5, 2026
83c6f4a
fix: Filteration on Venue Page API Issue Fixed
ArjunO-008 Jun 5, 2026
c88efd3
fix: Filteration System basic compliteted
ArjunO-008 Jun 5, 2026
ebe501a
feat: Added VenueModal Componenet
ArjunO-008 Jun 5, 2026
dacd908
feat: Improved UI For Filter Modal
ArjunO-008 Jun 5, 2026
8402597
feat: Intergrated URL Sync With Filtering
ArjunO-008 Jun 5, 2026
ab7d161
feat: Connected Category Page card to Show Filter Based on Venue Page
ArjunO-008 Jun 5, 2026
610cecd
feat: Added Link Routing For Home Page
ArjunO-008 Jun 5, 2026
d01fadb
Merge pull request #11 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 5, 2026
a63ff15
refactor(frontend): group components by audience folder
akhintheruvath Jun 6, 2026
1a5b615
refactor(frontend): adopt nested layout routes per portal
akhintheruvath Jun 6, 2026
d0ee27c
Merge pull request #12 from akhintheruvath/phase1mvp-akhin
akhintheruvath Jun 6, 2026
092b45e
chore: Added Venue wise Page and Routing
ArjunO-008 Jun 6, 2026
788f5b0
feat: Design UI For VenueDetail Page and Established Routing for 'vie…
ArjunO-008 Jun 6, 2026
882eb2e
tweak: UI Enhancement on VenueDetails Page
ArjunO-008 Jun 6, 2026
c4edccb
Merge pull request #13 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 6, 2026
a879b74
feat(venueOwner): add venue owner dashboard and management API
akhintheruvath Jun 7, 2026
ee4a9d1
Adding comments explaining statuses
akhintheruvath Jun 7, 2026
6f3035d
Merge pull request #14 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 7, 2026
2949ab2
feat(venueOwner): add landing page UI with sign-in form
akhintheruvath Jun 9, 2026
ce926a8
Merge pull request #15 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 9, 2026
d28c0f4
tweak: UI Enhancement on OwnerLanding Page
ArjunO-008 Jun 10, 2026
d5f8354
feat:Moved From Top NavBar to SideBar For Venue Owner
ArjunO-008 Jun 10, 2026
fd2335f
fix: File Path naming error on routing
ArjunO-008 Jun 10, 2026
f346d4b
Chore: Added DashboardStatCard Component
ArjunO-008 Jun 10, 2026
005ed25
chore: Added QuickAccess Card Component
ArjunO-008 Jun 10, 2026
0163fba
chore: Added recentActivty section.
ArjunO-008 Jun 10, 2026
08ee5fc
feat: Layout Changes to solve tight feeling in page
ArjunO-008 Jun 10, 2026
6b81252
Merge pull request #16 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 10, 2026
a73f1d8
feat: add venue owner sign-up card with sign-in toggle
akhintheruvath Jun 10, 2026
a4319e4
refactor: extract shared form components and use BookMyVenue logo on …
akhintheruvath Jun 10, 2026
843a85d
Merge pull request #17 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 10, 2026
84098aa
feat: Added back old Owner Dashboard back as my venues
ArjunO-008 Jun 13, 2026
530efc6
feat:Added Venue Add and Edit Page Layout
ArjunO-008 Jun 13, 2026
198a291
fix: undefined/null initial value
ArjunO-008 Jun 14, 2026
cfe7968
feat: add venue owner email/password sign-up
akhintheruvath Jun 14, 2026
ffd0428
feat: Added Functionalities for save as draft and submit for approval
ArjunO-008 Jun 14, 2026
a7893c1
fix: Fixed sidebar
ArjunO-008 Jun 14, 2026
269840f
Merge pull request #18 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 14, 2026
8971cee
Merge branch 'main' of https://github.com/akhintheruvath/BookMyVenue …
akhintheruvath Jun 14, 2026
930929a
fixes
ArjunO-008 Jun 14, 2026
d1ad2ca
Merge pull request #19 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 14, 2026
2e0bc6d
feat: guard venue owner routes with role-based auth
akhintheruvath Jun 14, 2026
d93bf25
feat: clear session on 401 to redirect to login
akhintheruvath Jun 14, 2026
3bdebc9
Merge pull request #20 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 14, 2026
653fce5
feat: add email/password sign-in for venue owners
akhintheruvath Jun 15, 2026
a1fc080
Merge pull request #21 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 15, 2026
8034d7f
feat: add log out button to venue owner sidebar
akhintheruvath Jun 18, 2026
82a9bc0
feat: return user from venue owner sign-up to skip extra /auth/me call
akhintheruvath Jun 18, 2026
2a58d48
refactor: restrict Google sign-in to customers only
akhintheruvath Jun 18, 2026
41906cc
Merge pull request #22 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 18, 2026
c2b1eea
refactor: rename "sign in" to "login" across auth flows for consistencey
akhintheruvath Jun 19, 2026
c3cf9fa
refactor: rename "owner" to "venueOwner" across venue-owner feature f…
akhintheruvath Jun 19, 2026
5778c45
refactor: rename "VENUE_STATUS" to "VENUE_STATUSES" for clarity
akhintheruvath Jun 19, 2026
0842915
Merge pull request #23 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 19, 2026
e5f7a71
chore: Added Owner Booking and Analytics Page
ArjunO-008 Jun 20, 2026
96a7fd4
chore: Added Settings Page
ArjunO-008 Jun 20, 2026
a393208
fix:Toggle button offset issue
ArjunO-008 Jun 20, 2026
13754ea
fix:Removed un-useed varibles and comments
ArjunO-008 Jun 20, 2026
3adb7bc
refactor: BreakDown the OwnerBooking Page to componenets
ArjunO-008 Jun 20, 2026
b683d39
refactor: BreakDown the OwnerAnalytic Page to componenets
ArjunO-008 Jun 20, 2026
4329a72
Merge pull request #24 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 20, 2026
8fddd54
feat: add draft-autosave flow for venue owner add/edit
akhintheruvath Jun 21, 2026
7c117d4
feat: enforce required-field completeness on venue submit
akhintheruvath Jun 21, 2026
6477b61
Merge pull request #25 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 21, 2026
9d512e8
refactor: Changed old admin navbar to new sidebar
ArjunO-008 Jun 22, 2026
2f0c520
chore:Added Pending Approval Tab and View for each Venues
ArjunO-008 Jun 22, 2026
3a32f32
Merge pull request #26 from ArjunO-008/phase1-Arjun
ArjunO-008 Jun 22, 2026
34ca8dc
feat: implement venue owner delete for DRAFT/EDIT_DRAFT venues
akhintheruvath Jun 22, 2026
d2d337c
refactor: use shared statusNotAllowedMessage in submit/update control…
akhintheruvath Jun 22, 2026
57a6d4f
Merge pull request #27 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 22, 2026
3eb95d9
feat: edit APPROVED venues via EDIT_DRAFT copy with admin re-approval
akhintheruvath Jun 27, 2026
fd63f33
feat: implement admin authentication
akhintheruvath Jun 27, 2026
1fd513f
Merge pull request #28 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jun 27, 2026
eebadd9
feat: add admin venue review queue
akhintheruvath Jul 4, 2026
391c99a
Merge pull request #29 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jul 4, 2026
f07628d
feat: add admin venue detail endpoint and wire up review page
akhintheruvath Jul 5, 2026
ad7a5a1
Merge pull request #30 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jul 5, 2026
1b4b1e1
feat: add venue amenities with multiselect in venue form
akhintheruvath Jul 5, 2026
1b60d4b
Merge pull request #31 from akhintheruvath/venueOwnerPageAkhin
akhintheruvath Jul 5, 2026
d917097
feat: Added Admin Options for Adding Amentities and Category
ArjunO-008 Jul 6, 2026
3bc7560
feat: Added CRUD Opertions for Admin Categories and Amenities
ArjunO-008 Jul 6, 2026
72838d5
fix: Removed some unused fields and stric boolen validation
ArjunO-008 Jul 7, 2026
97a5fa0
tweak: UI Enhancement on Adin Venue Option Page.
ArjunO-008 Jul 7, 2026
e6ed8d6
tweak: UI for Checkbox , Ascent colour chanhes to red
ArjunO-008 Jul 7, 2026
9906e07
Merge pull request #32 from ArjunO-008/phase1-Arjun
akhintheruvath Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.claude
PlanAndProgressFiles
.mcp.json
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
20 changes: 20 additions & 0 deletions backend/config/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
mongodbConnectionUrl: process.env.MONGODB_CONNECTION_URL,

// Google OAuth — the Web client ID from Google Cloud Console.
// Used to verify that an incoming Google ID token was issued for THIS app.
googleClientId: process.env.GOOGLE_CLIENT_ID,

jwtSecret: process.env.JWT_SECRET,
// Token lifetime per role. Customers stay logged in longer for convenience;
// venue owners get a shorter session since their account manages listings.
jwtExpiresIn: {
customer: "7d",
venueOwner: "1d",
admin: "2h"
},

// Comma-separated list of frontend origins allowed by CORS.
// Defaults to the Vite dev server.
corsOrigin: process.env.CORS_ORIGIN || "http://localhost:5173",
};
6 changes: 6 additions & 0 deletions backend/config/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const mongoose = require("mongoose");
const { mongodbConnectionUrl } = require("./config");

module.exports.connectDB = async () => {
await mongoose.connect(mongodbConnectionUrl);
}
9 changes: 9 additions & 0 deletions backend/constants/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const USER_ROLES = Object.freeze({
CUSTOMER: "customer", // customer books venues
VENUE_OWNER: "venueOwner", // lists and manages venues
ADMIN: "admin", // platform administration
});

const USER_ROLE_VALUES = Object.values(USER_ROLES);

module.exports = { USER_ROLES, USER_ROLE_VALUES };
65 changes: 65 additions & 0 deletions backend/constants/venue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Venue domain constants — shared across model, controllers, and seed.
// Centralized here so there are no magic strings scattered through the codebase.

// Listing lifecycle. Only APPROVED venues are publicly searchable
const VENUE_STATUSES = {
DRAFT: "DRAFT", // Venue owner just created the venue; fields can be filled anytime. Not visible to admin or public.
PENDING: "PENDING", // Venue owner submitted the venue; waiting for admin approval. Not yet public.
APPROVED: "APPROVED", // Admin approved the venue; visible to the public (if isActive is true).
REJECTED: "REJECTED", // REJECTED means admin declined (+rejectionReason); not terminal — venue owner can fix & resubmit (REJECTED → change back to "DRAFT"/"EDIT_DRAFT" → submit → PENDING)
EDIT_DRAFT: "EDIT_DRAFT", // EDIT_DRAFT is an in-progress edit copy of a live (APPROVED) venue (editOf field set in db); venue owner keeps editing until they submit it (EDIT_DRAFT → submit → CHANGES_PENDING).
CHANGES_PENDING: "CHANGES_PENDING", // CHANGES_PENDING is a submitted edit copy waiting for admin re-approval; on approve it merges into the original.
};

// Venue owner-initiated submit → review queue.
// DRAFT → PENDING (a new/own venue enters the queue)
// EDIT_DRAFT → CHANGES_PENDING (a drafted edit copy enters the queue)
const SUBMITTABLE_STATUSES = Object.freeze([
VENUE_STATUSES.DRAFT,
VENUE_STATUSES.EDIT_DRAFT,
]);

// Venue owner-initiated hard delete. Only never-submitted/in-progress copies are
// deletable:
// DRAFT → a new venue that was never submitted; safe to drop entirely
// EDIT_DRAFT → an edit copy of a live APPROVED venue; deleting it discards the
// in-progress edits without touching the original APPROVED doc
const DELETABLE_STATUSES = Object.freeze([
VENUE_STATUSES.DRAFT,
VENUE_STATUSES.EDIT_DRAFT,
]);

// Statuses an edit applies in place (the doc isn't live). Covers a new venue's
// own draft states AND a not-yet-submitted edit copy. APPROVED is absent — editing
// an APPROVED venue spawns a new EDIT_DRAFT copy instead. AND PENDING, CHANGES_PENDING are submitted versions
const IN_PLACE_EDIT_STATUSES = Object.freeze([
VENUE_STATUSES.DRAFT,
VENUE_STATUSES.EDIT_DRAFT,
]);

// Statuses awaiting an admin decision — the admin review queue.
// PENDING → a brand-new venue submitted for first approval
// CHANGES_PENDING → a submitted edit copy awaiting re-approval
const REVIEW_STATUSES = Object.freeze([
VENUE_STATUSES.PENDING,
VENUE_STATUSES.CHANGES_PENDING,
]);

// Admin decisions recorded in a venue's editHistory. Deliberately distinct from
// the APPROVED/REJECTED status strings so a history entry (the noun for the
// decision) isn't confused with the venue's lifecycle state.
const HISTORY_ACTIONS = Object.freeze({
APPROVAL: "APPROVAL",
REJECTION: "REJECTION",
});

module.exports = {
VENUE_STATUSES,
VENUE_STATUS_VALUES: Object.values(VENUE_STATUSES),
SUBMITTABLE_STATUSES,
DELETABLE_STATUSES,
IN_PLACE_EDIT_STATUSES,
REVIEW_STATUSES,
HISTORY_ACTIONS,
HISTORY_ACTION_VALUES: Object.values(HISTORY_ACTIONS),
};
47 changes: 47 additions & 0 deletions backend/controllers/admin/adminGetVenueById.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const mongoose = require("mongoose");
const Venues = require("../../models/venue");
const { REVIEW_STATUSES } = require("../../constants/venue");
const { VENUE_POPULATE } = require("../venue/shared");

// Owner fields surfaced to the review page — enough to identify/contact the owner.
const OWNER_POPULATE = { path: "venueOwner", select: "name email" };

// Fields excluded from the review detail. version/editHistory are the admin audit
// trail (not shown on the review page yet); deletedAt/__v are internal.
const DETAIL_HIDDEN_FIELDS = "-version -editHistory -deletedAt -__v";

// GET /admin/venues/:id
// Returns a single venue awaiting review (PENDING or CHANGES_PENDING) for the
// admin review page. Non-review statuses are treated as not found — this endpoint
// only serves the approval queue. For a CHANGES_PENDING edit copy the response
// carries `editOf` (the original APPROVED venue's id) so the UI can link to the
// live listing.
async function adminGetVenueById(req, res) {
try {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ message: "Invalid venue id" });
}

const venue = await Venues.findOne({
_id: id,
status: { $in: REVIEW_STATUSES },
deletedAt: null,
})
.select(DETAIL_HIDDEN_FIELDS)
.populate(VENUE_POPULATE)
.populate(OWNER_POPULATE)
.lean();

if (!venue) {
return res.status(404).json({ message: "Venue not found" });
}

return res.status(200).json({ data: venue });
} catch (err) {
return res.status(500).json({ error: err.message, message: "Failed to fetch venue" });
}
}

module.exports = adminGetVenueById;
80 changes: 80 additions & 0 deletions backend/controllers/admin/adminListVenues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const Venues = require("../../models/venue");
const { REVIEW_STATUSES } = require("../../constants/venue");
const {
DEFAULT_PAGE,
DEFAULT_LIMIT,
CATEGORY_POPULATE,
parsePageParam,
} = require("../venue/shared");

// Owner fields surfaced to the admin queue — enough to identify/contact the owner.
const OWNER_POPULATE = { path: "venueOwner", select: "name email" };

// Lean projection for the review table. Full venue data comes from the detail
// endpoint; the queue only needs what the table renders.
const LIST_FIELDS = "name city district venueCategory venueOwner status createdAt updatedAt";

// The queue is always sorted by submission time (updatedAt); only the direction
// is client-controlled. Map the two allowed sortOrder values to Mongo sort ints.
const SORT_DIRECTIONS = { asc: 1, desc: -1 };
const DEFAULT_SORT_ORDER = "desc";

// GET /admin/venues?status=<PENDING|CHANGES_PENDING>&sortOrder=<asc|desc>
// Paginated admin review queue. `status` is required and must be exactly one
// review status (the UI's two tabs each fetch one). PENDING = new venues awaiting
// first approval; CHANGES_PENDING = submitted edit copies awaiting re-approval.
// Results are sorted by submission time (updatedAt); sortOrder defaults to desc.
// countOnly "true" — returns { data: { total } } with no venue docs (used to
// populate both tab count badges without fetching rows).
async function adminListVenues(req, res) {
try {
const { status } = req.query;
if (!status) {
return res.status(400).json({ message: "status query parameter is required" });
}
if (!REVIEW_STATUSES.includes(status)) {
return res.status(400).json({
message: `status must be one of: ${REVIEW_STATUSES.join(", ")}`,
});
}

const filter = { status, deletedAt: null };

if (req.query.countOnly === "true") {
const total = await Venues.countDocuments(filter);
return res.status(200).json({ data: { total } });
}

const sortOrder = req.query.sortOrder || DEFAULT_SORT_ORDER;
if (!Object.hasOwn(SORT_DIRECTIONS, sortOrder)) {
return res.status(400).json({
message: `sortOrder must be one of: ${Object.keys(SORT_DIRECTIONS).join(", ")}`,
});
}

const page = parsePageParam(req.query.page, DEFAULT_PAGE);
const limit = parsePageParam(req.query.limit, DEFAULT_LIMIT);
const skip = (page - 1) * limit;

const [venues, total] = await Promise.all([
Venues.find(filter)
.select(LIST_FIELDS)
.populate(CATEGORY_POPULATE)
.populate(OWNER_POPULATE)
.sort({ updatedAt: SORT_DIRECTIONS[sortOrder] })
.skip(skip)
.limit(limit)
.lean(),
Venues.countDocuments(filter),
]);

return res.status(200).json({
data: venues,
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
});
} catch (err) {
return res.status(500).json({ error: err.message, message: "Failed to fetch venues" });
}
}

module.exports = adminListVenues;
22 changes: 22 additions & 0 deletions backend/controllers/amenity/adminListAmenities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Amenities = require("../../models/amenity");

// GET /admin/amenities
// Full management list for the admin UI: includes inactive/soft-deleted-excluded
// rows (deletedAt: null) regardless of isActive, so admins can see and
// reactivate a retired amenity, not just the ones live on the public site.
async function adminListAmenities(req, res) {
try {
const amenities = await Amenities.find({ deletedAt: null })
.select("name isActive")
.sort({ name: 1 })
.lean();

return res.status(200).json({ data: amenities });
} catch (err) {
return res
.status(500)
.json({ error: err.message, message: "Failed to fetch amenities" });
}
}

module.exports = adminListAmenities;
37 changes: 37 additions & 0 deletions backend/controllers/amenity/createAmenity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const Amenities = require("../../models/amenity");
const slugify = require("../../utils/slugify");

// POST /admin/amenities body: { name }
// identifier is derived from name and never exposed for manual editing —
// venues reference amenities by identifier, so keeping it machine-generated
// avoids typo'd/duplicate slugs.
async function createAmenity(req, res) {
try {
const { name } = req.body;
if (!name || !name.trim()) {
return res.status(400).json({ message: "name is required" });
}

const identifier = slugify(name);
if (!identifier) {
return res.status(400).json({ message: "name must contain at least one letter or number" });
}

const existing = await Amenities.findOne({ identifier });
if (existing) {
return res.status(409).json({ message: "An amenity with this name already exists" });
}

const amenity = await Amenities.create({ identifier, name: name.trim() });
return res.status(201).json({ data: amenity });
} catch (err) {
if (err.code === 11000) {
return res.status(409).json({ message: "An amenity with this name already exists" });
}
return res
.status(500)
.json({ error: err.message, message: "Failed to create amenity" });
}
}

module.exports = createAmenity;
28 changes: 28 additions & 0 deletions backend/controllers/amenity/deleteAmenity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const Amenities = require("../../models/amenity");

// DELETE /admin/amenities/:id
// Soft delete: marks deletedAt + isActive false rather than removing the doc,
// so venues that already reference this amenity's identifier don't break.
async function deleteAmenity(req, res) {
try {
const { id } = req.params;

const amenity = await Amenities.findOneAndUpdate(
{ _id: id, deletedAt: null },
{ deletedAt: new Date(), isActive: false },
{ new: true }
);

if (!amenity) {
return res.status(404).json({ message: "Amenity not found" });
}

return res.status(200).json({ data: amenity });
} catch (err) {
return res
.status(500)
.json({ error: err.message, message: "Failed to delete amenity" });
}
}

module.exports = deleteAmenity;
21 changes: 21 additions & 0 deletions backend/controllers/amenity/listAmenities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const Amenities = require("../../models/amenity");

const PUBLIC_FIELDS = "identifier name";

// multiselect dropdown and for resolving amenity labels
async function listAmenities(req, res) {
try {
const amenities = await Amenities.find({ isActive: true, deletedAt: null })
.select(PUBLIC_FIELDS)
.sort({ name: 1 })
.lean();

return res.status(200).json({ data: amenities });
} catch (err) {
return res
.status(500)
.json({ error: err.message, message: "Failed to fetch amenities" });
}
}

module.exports = listAmenities;
Loading