Skip to content

Commit 3e8e6fb

Browse files
authored
Merge pull request #1 from cuappdev/services
Initial commit
2 parents 36b4498 + 8b5af00 commit 3e8e6fb

8 files changed

Lines changed: 578 additions & 91 deletions

File tree

controllers/services.go

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
package controllers
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
7+
"github.com/gin-gonic/gin"
8+
"github.com/cuappdev/hustle-backend/middleware"
9+
"github.com/cuappdev/hustle-backend/services"
10+
)
11+
12+
type ServiceController struct {
13+
listingService *services.ListingService
14+
}
15+
16+
func NewServiceController() *ServiceController {
17+
return &ServiceController{
18+
listingService: services.NewListingService(),
19+
}
20+
}
21+
22+
// CreateServiceListingInput defines the input for creating a service listing
23+
type CreateServiceListingInput struct {
24+
Description string `json:"description" binding:"required"`
25+
Categories string `json:"categories" binding:"required"`
26+
}
27+
28+
// CreateServiceInput defines the input for adding a service to a listing
29+
type CreateServiceInput struct {
30+
Title string `json:"title" binding:"required"`
31+
Price float64 `json:"price" binding:"required"`
32+
PriceUnit string `json:"price_unit" binding:"required"`
33+
}
34+
35+
// CreateServiceListing creates a new service listing
36+
// POST /service-listings
37+
func (sc *ServiceController) CreateServiceListing(c *gin.Context) {
38+
// Get seller ID from auth middleware
39+
sellerIDStr := middleware.UIDFrom(c)
40+
if sellerIDStr == "" {
41+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
42+
return
43+
}
44+
45+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
46+
if err != nil {
47+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
48+
return
49+
}
50+
51+
// Validate input
52+
var input CreateServiceListingInput
53+
if err := c.ShouldBindJSON(&input); err != nil {
54+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
55+
return
56+
}
57+
58+
// Use service to create listing
59+
serviceListing, err := sc.listingService.CreateServiceListing(uint(sellerID), input.Description, input.Categories)
60+
if err != nil {
61+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create service listing"})
62+
return
63+
}
64+
65+
c.JSON(http.StatusCreated, gin.H{"data": serviceListing})
66+
}
67+
68+
// GetServiceListing retrieves a service listing with its services
69+
// GET /service-listings/:id
70+
func (sc *ServiceController) GetServiceListing(c *gin.Context) {
71+
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
72+
if err != nil {
73+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID format"})
74+
return
75+
}
76+
77+
serviceListing, err := sc.listingService.GetServiceListingWithServices(uint(id))
78+
if err != nil {
79+
c.JSON(http.StatusNotFound, gin.H{"error": "Service listing not found"})
80+
return
81+
}
82+
83+
c.JSON(http.StatusOK, gin.H{"data": serviceListing})
84+
}
85+
86+
// GetServiceListings retrieves all service listings for the current user (seller)
87+
// GET /service-listings
88+
func (sc *ServiceController) GetServiceListings(c *gin.Context) {
89+
// Get seller ID from auth middleware
90+
sellerIDStr := middleware.UIDFrom(c)
91+
if sellerIDStr == "" {
92+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
93+
return
94+
}
95+
96+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
97+
if err != nil {
98+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
99+
return
100+
}
101+
102+
serviceListings, err := sc.listingService.GetServiceListings(uint(sellerID))
103+
if err != nil {
104+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch service listings"})
105+
return
106+
}
107+
108+
c.JSON(http.StatusOK, gin.H{"data": serviceListings})
109+
}
110+
111+
// UpdateServiceListing updates a service listing
112+
// PATCH /service-listings/:id
113+
func (sc *ServiceController) UpdateServiceListing(c *gin.Context) {
114+
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
115+
if err != nil {
116+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID format"})
117+
return
118+
}
119+
120+
// Get seller ID from auth middleware
121+
sellerIDStr := middleware.UIDFrom(c)
122+
if sellerIDStr == "" {
123+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
124+
return
125+
}
126+
127+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
128+
if err != nil {
129+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
130+
return
131+
}
132+
133+
// Validate input
134+
var input CreateServiceListingInput
135+
if err := c.ShouldBindJSON(&input); err != nil {
136+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
137+
return
138+
}
139+
140+
// Use service to update listing
141+
serviceListing, err := sc.listingService.UpdateServiceListing(uint(id), uint(sellerID), input.Description, input.Categories)
142+
if err == models.ErrNotFound {
143+
c.JSON(http.StatusNotFound, gin.H{"error": "Service listing not found or unauthorized"})
144+
return
145+
} else if err != nil {
146+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update service listing"})
147+
return
148+
}
149+
150+
c.JSON(http.StatusOK, gin.H{"data": serviceListing})
151+
}
152+
153+
// DeleteServiceListing deletes a service listing and its services
154+
// DELETE /service-listings/:id
155+
func (sc *ServiceController) DeleteServiceListing(c *gin.Context) {
156+
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
157+
if err != nil {
158+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID format"})
159+
return
160+
}
161+
162+
// Get seller ID from auth middleware
163+
sellerIDStr := middleware.UIDFrom(c)
164+
if sellerIDStr == "" {
165+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
166+
return
167+
}
168+
169+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
170+
if err != nil {
171+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
172+
return
173+
}
174+
175+
// Use service to delete listing
176+
err = sc.listingService.DeleteServiceListing(uint(id), uint(sellerID))
177+
if err == models.ErrNotFound {
178+
c.JSON(http.StatusNotFound, gin.H{"error": "Service listing not found or unauthorized"})
179+
return
180+
} else if err != nil {
181+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete service listing"})
182+
return
183+
}
184+
185+
c.JSON(http.StatusOK, gin.H{"message": "Service listing deleted successfully"})
186+
}
187+
188+
// AddService adds a new service to a service listing
189+
// POST /service-listings/:id/services
190+
func (sc *ServiceController) AddService(c *gin.Context) {
191+
// Get service listing ID from URL
192+
listingID, err := strconv.ParseUint(c.Param("id"), 10, 32)
193+
if err != nil {
194+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service listing ID"})
195+
return
196+
}
197+
198+
// Get seller ID from auth middleware
199+
sellerIDStr := middleware.UIDFrom(c)
200+
if sellerIDStr == "" {
201+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
202+
return
203+
}
204+
205+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
206+
if err != nil {
207+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
208+
return
209+
}
210+
211+
// Validate input
212+
var input CreateServiceInput
213+
if err := c.ShouldBindJSON(&input); err != nil {
214+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
215+
return
216+
}
217+
218+
// Use service to add service to listing
219+
service, err := sc.listingService.AddService(uint(listingID), uint(sellerID), input.Title, input.Price, input.PriceUnit)
220+
if err == models.ErrNotFound {
221+
c.JSON(http.StatusNotFound, gin.H{"error": "Service listing not found or unauthorized"})
222+
return
223+
} else if err != nil {
224+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create service"})
225+
return
226+
}
227+
228+
c.JSON(http.StatusCreated, gin.H{"data": service})
229+
}
230+
231+
// GetServices retrieves all services for a service listing
232+
// GET /service-listings/:id/services
233+
func (sc *ServiceController) GetServices(c *gin.Context) {
234+
// Get service listing ID from URL
235+
listingID, err := strconv.ParseUint(c.Param("id"), 10, 32)
236+
if err != nil {
237+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service listing ID"})
238+
return
239+
}
240+
241+
services, err := sc.listingService.GetServices(uint(listingID))
242+
if err != nil {
243+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch services"})
244+
return
245+
}
246+
247+
c.JSON(http.StatusOK, gin.H{"data": services})
248+
}
249+
250+
// UpdateService updates a service
251+
// PATCH /services/:id
252+
func (sc *ServiceController) UpdateService(c *gin.Context) {
253+
// Get service ID from URL
254+
serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
255+
if err != nil {
256+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service ID"})
257+
return
258+
}
259+
260+
// Get seller ID from auth middleware
261+
sellerIDStr := middleware.UIDFrom(c)
262+
if sellerIDStr == "" {
263+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
264+
return
265+
}
266+
267+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
268+
if err != nil {
269+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
270+
return
271+
}
272+
273+
// Validate input
274+
var input CreateServiceInput
275+
if err := c.ShouldBindJSON(&input); err != nil {
276+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
277+
return
278+
}
279+
280+
// Use service to update service
281+
service, err := sc.listingService.UpdateService(uint(serviceID), uint(sellerID), input.Title, input.Price, input.PriceUnit)
282+
if err == models.ErrNotFound {
283+
c.JSON(http.StatusNotFound, gin.H{"error": "Service not found or unauthorized"})
284+
return
285+
} else if err != nil {
286+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update service"})
287+
return
288+
}
289+
290+
c.JSON(http.StatusOK, gin.H{"data": service})
291+
}
292+
293+
// DeleteService deletes a service
294+
// DELETE /services/:id
295+
func (sc *ServiceController) DeleteService(c *gin.Context) {
296+
// Get service ID from URL
297+
serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
298+
if err != nil {
299+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service ID"})
300+
return
301+
}
302+
303+
// Get seller ID from auth middleware
304+
sellerIDStr := middleware.UIDFrom(c)
305+
if sellerIDStr == "" {
306+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
307+
return
308+
}
309+
310+
sellerID, err := strconv.ParseUint(sellerIDStr, 10, 32)
311+
if err != nil {
312+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID"})
313+
return
314+
}
315+
316+
// Use service to delete service
317+
err = sc.listingService.DeleteService(uint(serviceID), uint(sellerID))
318+
if err == models.ErrNotFound {
319+
c.JSON(http.StatusNotFound, gin.H{"error": "Service not found or unauthorized"})
320+
return
321+
} else if err != nil {
322+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete service"})
323+
return
324+
}
325+
326+
c.JSON(http.StatusOK, gin.H{"message": "Service deleted successfully"})
327+
}

main.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ func main() {
6868
authd.POST("/fcm/register", controllers.RegisterFCMToken)
6969
authd.DELETE("/fcm/delete", controllers.DeleteFCMToken)
7070
authd.POST("/fcm/test", controllers.SendTestNotification)
71+
72+
// Initialize service controller
73+
serviceController := controllers.NewServiceController()
74+
75+
// Service Listing routes
76+
serviceListings := authd.Group("/service-listings")
77+
{
78+
serviceListings.GET("", serviceController.GetServiceListings)
79+
serviceListings.POST("", serviceController.CreateServiceListing)
80+
serviceListings.GET("/:id", serviceController.GetServiceListing)
81+
serviceListings.PATCH("/:id", serviceController.UpdateServiceListing)
82+
serviceListings.DELETE("/:id", serviceController.DeleteServiceListing)
83+
84+
// Nested services routes
85+
serviceListings.GET("/:id/services", serviceController.GetServices)
86+
serviceListings.POST("/:id/services", serviceController.AddService)
87+
}
88+
89+
// Individual service routes
90+
services := authd.Group("/services")
91+
{
92+
services.PATCH("/:id", serviceController.UpdateService)
93+
services.DELETE("/:id", serviceController.DeleteService)
94+
}
7195
}
7296
log.Println("Server starting on :8080")
7397

models/errors.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package models
2+
3+
import "errors"
4+
5+
var (
6+
// ErrNotFound is returned when a record is not found in the database
7+
ErrNotFound = errors.New("record not found")
8+
// ErrInvalidInput is returned when input validation fails
9+
ErrInvalidInput = errors.New("invalid input")
10+
// ErrUnauthorized is returned when a user doesn't have permission
11+
ErrUnauthorized = errors.New("unauthorized access")
12+
)

0 commit comments

Comments
 (0)