fix grades
This commit is contained in:
parent
305beabacb
commit
8b47394e8d
@ -260,3 +260,82 @@ func (server *Server) getUserReviewByLocation(ctx *gin.Context) {
|
|||||||
Images: images,
|
Images: images,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getLocationReviewGradesReq struct {
|
||||||
|
LocationID int32 `uri:"location_id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type locationReviewGradesGroup struct {
|
||||||
|
AvgTasteGrade *float64 `json:"avg_taste_grade"`
|
||||||
|
TasteGradeCount int64 `json:"taste_grade_count"`
|
||||||
|
AvgComfortGrade *float64 `json:"avg_comfort_grade"`
|
||||||
|
ComfortGradeCount int64 `json:"comfort_grade_count"`
|
||||||
|
AvgCleanlinessGrade *float64 `json:"avg_cleanliness_grade"`
|
||||||
|
CleanlinessGradeCount int64 `json:"cleanliness_grade_count"`
|
||||||
|
AvgServiceGrade *float64 `json:"avg_service_grade"`
|
||||||
|
ServiceGradeCount int64 `json:"service_grade_count"`
|
||||||
|
AvgFacilitiesGrade *float64 `json:"avg_facilities_grade"`
|
||||||
|
FacilitiesGradeCount int64 `json:"facilities_grade_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getLocationReviewGradesRes struct {
|
||||||
|
CriticsGrades locationReviewGradesGroup `json:"critics_grades"`
|
||||||
|
UsersGrades locationReviewGradesGroup `json:"users_grades"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableFloat(v pgtype.Float8) *float64 {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &v.Float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Get aggregated review grades for a location
|
||||||
|
// @Tags reviews
|
||||||
|
// @Produce json
|
||||||
|
// @Param location_id path int true "Location ID"
|
||||||
|
// @Success 200 {object} getLocationReviewGradesRes
|
||||||
|
// @Failure 400 {object} map[string]any
|
||||||
|
// @Failure 500 {object} map[string]any
|
||||||
|
// @Router /location/{location_id}/reviews/grades [get]
|
||||||
|
func (server *Server) getLocationReviewGrades(ctx *gin.Context) {
|
||||||
|
var req getLocationReviewGradesReq
|
||||||
|
|
||||||
|
if err := ctx.ShouldBindUri(&req); err != nil {
|
||||||
|
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
grades, err := server.Store.GetLocationReviewGrades(ctx, req.LocationID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while trying to retrieve review grades"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, getLocationReviewGradesRes{
|
||||||
|
CriticsGrades: locationReviewGradesGroup{
|
||||||
|
AvgTasteGrade: nullableFloat(grades.CriticAvgTasteGrade),
|
||||||
|
TasteGradeCount: grades.CriticTasteGradeCount,
|
||||||
|
AvgComfortGrade: nullableFloat(grades.CriticAvgComfortGrade),
|
||||||
|
ComfortGradeCount: grades.CriticComfortGradeCount,
|
||||||
|
AvgCleanlinessGrade: nullableFloat(grades.CriticAvgCleanlinessGrade),
|
||||||
|
CleanlinessGradeCount: grades.CriticCleanlinessGradeCount,
|
||||||
|
AvgServiceGrade: nullableFloat(grades.CriticAvgServiceGrade),
|
||||||
|
ServiceGradeCount: grades.CriticServiceGradeCount,
|
||||||
|
AvgFacilitiesGrade: nullableFloat(grades.CriticAvgFacilitiesGrade),
|
||||||
|
FacilitiesGradeCount: grades.CriticFacilitiesGradeCount,
|
||||||
|
},
|
||||||
|
UsersGrades: locationReviewGradesGroup{
|
||||||
|
AvgTasteGrade: nullableFloat(grades.UserAvgTasteGrade),
|
||||||
|
TasteGradeCount: grades.UserTasteGradeCount,
|
||||||
|
AvgComfortGrade: nullableFloat(grades.UserAvgComfortGrade),
|
||||||
|
ComfortGradeCount: grades.UserComfortGradeCount,
|
||||||
|
AvgCleanlinessGrade: nullableFloat(grades.UserAvgCleanlinessGrade),
|
||||||
|
CleanlinessGradeCount: grades.UserCleanlinessGradeCount,
|
||||||
|
AvgServiceGrade: nullableFloat(grades.UserAvgServiceGrade),
|
||||||
|
ServiceGradeCount: grades.UserServiceGradeCount,
|
||||||
|
AvgFacilitiesGrade: nullableFloat(grades.UserAvgFacilitiesGrade),
|
||||||
|
FacilitiesGradeCount: grades.UserFacilitiesGradeCount,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -91,6 +91,7 @@ func (server *Server) getRoutes() {
|
|||||||
router.GET("/location/tags/:location_id", server.getTagsByLocation)
|
router.GET("/location/tags/:location_id", server.getTagsByLocation)
|
||||||
router.GET("/location/reviews", server.getListLocationReviews)
|
router.GET("/location/reviews", server.getListLocationReviews)
|
||||||
router.GET("/location/:location_id/review/:review_id", server.getReview)
|
router.GET("/location/:location_id/review/:review_id", server.getReview)
|
||||||
|
router.GET("/location/grades/:location_id", server.getLocationReviewGrades)
|
||||||
router.GET("/locations/search", server.searchLocations)
|
router.GET("/locations/search", server.searchLocations)
|
||||||
router.POST("/location/:location_id/visit", server.recordLocationVisit)
|
router.POST("/location/:location_id/visit", server.recordLocationVisit)
|
||||||
|
|
||||||
|
|||||||
BIN
db/.DS_Store
vendored
BIN
db/.DS_Store
vendored
Binary file not shown.
@ -338,6 +338,21 @@ func (mr *MockStoreMockRecorder) GetLocationLifetimeVisits(ctx, locationID any)
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationLifetimeVisits", reflect.TypeOf((*MockStore)(nil).GetLocationLifetimeVisits), ctx, locationID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationLifetimeVisits", reflect.TypeOf((*MockStore)(nil).GetLocationLifetimeVisits), ctx, locationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLocationReviewGrades mocks base method.
|
||||||
|
func (m *MockStore) GetLocationReviewGrades(ctx context.Context, locationID int32) (db.GetLocationReviewGradesRow, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetLocationReviewGrades", ctx, locationID)
|
||||||
|
ret0, _ := ret[0].(db.GetLocationReviewGradesRow)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocationReviewGrades indicates an expected call of GetLocationReviewGrades.
|
||||||
|
func (mr *MockStoreMockRecorder) GetLocationReviewGrades(ctx, locationID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationReviewGrades", reflect.TypeOf((*MockStore)(nil).GetLocationReviewGrades), ctx, locationID)
|
||||||
|
}
|
||||||
|
|
||||||
// GetLocationTag mocks base method.
|
// GetLocationTag mocks base method.
|
||||||
func (m *MockStore) GetLocationTag(ctx context.Context, targetID int32) ([]string, error) {
|
func (m *MockStore) GetLocationTag(ctx context.Context, targetID int32) ([]string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@ -146,6 +146,84 @@ func (q *Queries) GetReviewByLocationAndId(ctx context.Context, arg GetReviewByL
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetLocationReviewGradesRow struct {
|
||||||
|
CriticAvgTasteGrade pgtype.Float8 `json:"critic_avg_taste_grade"`
|
||||||
|
CriticTasteGradeCount int64 `json:"critic_taste_grade_count"`
|
||||||
|
CriticAvgComfortGrade pgtype.Float8 `json:"critic_avg_comfort_grade"`
|
||||||
|
CriticComfortGradeCount int64 `json:"critic_comfort_grade_count"`
|
||||||
|
CriticAvgCleanlinessGrade pgtype.Float8 `json:"critic_avg_cleanliness_grade"`
|
||||||
|
CriticCleanlinessGradeCount int64 `json:"critic_cleanliness_grade_count"`
|
||||||
|
CriticAvgServiceGrade pgtype.Float8 `json:"critic_avg_service_grade"`
|
||||||
|
CriticServiceGradeCount int64 `json:"critic_service_grade_count"`
|
||||||
|
CriticAvgFacilitiesGrade pgtype.Float8 `json:"critic_avg_facilities_grade"`
|
||||||
|
CriticFacilitiesGradeCount int64 `json:"critic_facilities_grade_count"`
|
||||||
|
|
||||||
|
UserAvgTasteGrade pgtype.Float8 `json:"user_avg_taste_grade"`
|
||||||
|
UserTasteGradeCount int64 `json:"user_taste_grade_count"`
|
||||||
|
UserAvgComfortGrade pgtype.Float8 `json:"user_avg_comfort_grade"`
|
||||||
|
UserComfortGradeCount int64 `json:"user_comfort_grade_count"`
|
||||||
|
UserAvgCleanlinessGrade pgtype.Float8 `json:"user_avg_cleanliness_grade"`
|
||||||
|
UserCleanlinessGradeCount int64 `json:"user_cleanliness_grade_count"`
|
||||||
|
UserAvgServiceGrade pgtype.Float8 `json:"user_avg_service_grade"`
|
||||||
|
UserServiceGradeCount int64 `json:"user_service_grade_count"`
|
||||||
|
UserAvgFacilitiesGrade pgtype.Float8 `json:"user_avg_facilities_grade"`
|
||||||
|
UserFacilitiesGradeCount int64 `json:"user_facilities_grade_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLocationReviewGrades = `
|
||||||
|
SELECT
|
||||||
|
AVG(re.taste_grade) FILTER (WHERE re.is_from_critic = true)::float8 AS critic_avg_taste_grade,
|
||||||
|
COUNT(re.taste_grade) FILTER (WHERE re.is_from_critic = true) AS critic_taste_grade_count,
|
||||||
|
AVG(re.comfort_grade) FILTER (WHERE re.is_from_critic = true)::float8 AS critic_avg_comfort_grade,
|
||||||
|
COUNT(re.comfort_grade) FILTER (WHERE re.is_from_critic = true) AS critic_comfort_grade_count,
|
||||||
|
AVG(re.cleanliness_grade) FILTER (WHERE re.is_from_critic = true)::float8 AS critic_avg_cleanliness_grade,
|
||||||
|
COUNT(re.cleanliness_grade) FILTER (WHERE re.is_from_critic = true) AS critic_cleanliness_grade_count,
|
||||||
|
AVG(re.service_grade) FILTER (WHERE re.is_from_critic = true)::float8 AS critic_avg_service_grade,
|
||||||
|
COUNT(re.service_grade) FILTER (WHERE re.is_from_critic = true) AS critic_service_grade_count,
|
||||||
|
AVG(re.facilities_grade) FILTER (WHERE re.is_from_critic = true)::float8 AS critic_avg_facilities_grade,
|
||||||
|
COUNT(re.facilities_grade) FILTER (WHERE re.is_from_critic = true) AS critic_facilities_grade_count,
|
||||||
|
AVG(re.taste_grade) FILTER (WHERE re.is_from_critic = false)::float8 AS user_avg_taste_grade,
|
||||||
|
COUNT(re.taste_grade) FILTER (WHERE re.is_from_critic = false) AS user_taste_grade_count,
|
||||||
|
AVG(re.comfort_grade) FILTER (WHERE re.is_from_critic = false)::float8 AS user_avg_comfort_grade,
|
||||||
|
COUNT(re.comfort_grade) FILTER (WHERE re.is_from_critic = false) AS user_comfort_grade_count,
|
||||||
|
AVG(re.cleanliness_grade) FILTER (WHERE re.is_from_critic = false)::float8 AS user_avg_cleanliness_grade,
|
||||||
|
COUNT(re.cleanliness_grade) FILTER (WHERE re.is_from_critic = false) AS user_cleanliness_grade_count,
|
||||||
|
AVG(re.service_grade) FILTER (WHERE re.is_from_critic = false)::float8 AS user_avg_service_grade,
|
||||||
|
COUNT(re.service_grade) FILTER (WHERE re.is_from_critic = false) AS user_service_grade_count,
|
||||||
|
AVG(re.facilities_grade) FILTER (WHERE re.is_from_critic = false)::float8 AS user_avg_facilities_grade,
|
||||||
|
COUNT(re.facilities_grade) FILTER (WHERE re.is_from_critic = false) AS user_facilities_grade_count
|
||||||
|
FROM reviews re
|
||||||
|
WHERE re.location_id = $1 AND re.is_hided = false;
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetLocationReviewGrades(ctx context.Context, locationID int32) (GetLocationReviewGradesRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getLocationReviewGrades, locationID)
|
||||||
|
var i GetLocationReviewGradesRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.CriticAvgTasteGrade,
|
||||||
|
&i.CriticTasteGradeCount,
|
||||||
|
&i.CriticAvgComfortGrade,
|
||||||
|
&i.CriticComfortGradeCount,
|
||||||
|
&i.CriticAvgCleanlinessGrade,
|
||||||
|
&i.CriticCleanlinessGradeCount,
|
||||||
|
&i.CriticAvgServiceGrade,
|
||||||
|
&i.CriticServiceGradeCount,
|
||||||
|
&i.CriticAvgFacilitiesGrade,
|
||||||
|
&i.CriticFacilitiesGradeCount,
|
||||||
|
&i.UserAvgTasteGrade,
|
||||||
|
&i.UserTasteGradeCount,
|
||||||
|
&i.UserAvgComfortGrade,
|
||||||
|
&i.UserComfortGradeCount,
|
||||||
|
&i.UserAvgCleanlinessGrade,
|
||||||
|
&i.UserCleanlinessGradeCount,
|
||||||
|
&i.UserAvgServiceGrade,
|
||||||
|
&i.UserServiceGradeCount,
|
||||||
|
&i.UserAvgFacilitiesGrade,
|
||||||
|
&i.UserFacilitiesGradeCount,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
func (q *Queries) GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error) {
|
func (q *Queries) GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error) {
|
||||||
rows, err := q.db.Query(ctx, getListLocationReviews, arg.LocationID, arg.IsCritics, arg.Limit, arg.Offset)
|
rows, err := q.db.Query(ctx, getListLocationReviews, arg.LocationID, arg.IsCritics, arg.Limit, arg.Offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ type Store interface {
|
|||||||
RecordLocationVisit(ctx context.Context, locationID int32) error
|
RecordLocationVisit(ctx context.Context, locationID int32) error
|
||||||
GetLocationLifetimeVisits(ctx context.Context, locationID int32) (int64, error)
|
GetLocationLifetimeVisits(ctx context.Context, locationID int32) (int64, error)
|
||||||
GetTrendingLocations(ctx context.Context, arg GetTrendingLocationsParams) ([]GetTrendingLocationsRow, error)
|
GetTrendingLocations(ctx context.Context, arg GetTrendingLocationsParams) ([]GetTrendingLocationsRow, error)
|
||||||
|
GetLocationReviewGrades(ctx context.Context, locationID int32) (GetLocationReviewGradesRow, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SQLStore struct {
|
type SQLStore struct {
|
||||||
|
|||||||
264
docs/docs.go
264
docs/docs.go
@ -258,6 +258,88 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/location/{location_id}/reviews/grades": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reviews"
|
||||||
|
],
|
||||||
|
"summary": "Get aggregated review grades for a location",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Location ID",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.getLocationReviewGradesRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/location/{location_id}/visit": {
|
||||||
|
"post": {
|
||||||
|
"description": "Increments today's visit bucket. A single client (IP) is rate-limited to one visit per location per 30 minutes via Redis to deter trivial F5 inflation.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"locations"
|
||||||
|
],
|
||||||
|
"summary": "Record a page visit for a location",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Location ID",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/locations": {
|
"/locations": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -356,6 +438,12 @@ const docTemplate = `{
|
|||||||
"name": "amenities",
|
"name": "amenities",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Business hours JSON: [{\\",
|
||||||
|
"name": "business_hours",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Google Maps link",
|
"description": "Google Maps link",
|
||||||
@ -536,6 +624,104 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/locations/trending": {
|
||||||
|
"get": {
|
||||||
|
"description": "Ranks approved, non-deleted locations by total page-visits in the trailing window.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"locations"
|
||||||
|
],
|
||||||
|
"summary": "Trending locations",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Window: week | month | 3month | semester | year",
|
||||||
|
"name": "window",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page (min 1)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page size (1-50)",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/menu-items": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"menu"
|
||||||
|
],
|
||||||
|
"summary": "Get menu items",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by location ID (omit for all)",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/db.GetMenuItemsRow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/news-events": {
|
"/news-events": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -1187,6 +1373,84 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"api.getLocationReviewGradesRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"critics_grades": {
|
||||||
|
"$ref": "#/definitions/api.locationReviewGradesGroup"
|
||||||
|
},
|
||||||
|
"users_grades": {
|
||||||
|
"$ref": "#/definitions/api.locationReviewGradesGroup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.locationReviewGradesGroup": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_cleanliness_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_comfort_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_facilities_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_service_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_taste_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"cleanliness_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"comfort_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"facilities_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"taste_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"db.GetMenuItemsRow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_score": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"is_available": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"location_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"submitted_by": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@ -252,6 +252,88 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/location/{location_id}/reviews/grades": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"reviews"
|
||||||
|
],
|
||||||
|
"summary": "Get aggregated review grades for a location",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Location ID",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.getLocationReviewGradesRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/location/{location_id}/visit": {
|
||||||
|
"post": {
|
||||||
|
"description": "Increments today's visit bucket. A single client (IP) is rate-limited to one visit per location per 30 minutes via Redis to deter trivial F5 inflation.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"locations"
|
||||||
|
],
|
||||||
|
"summary": "Record a page visit for a location",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Location ID",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/locations": {
|
"/locations": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -350,6 +432,12 @@
|
|||||||
"name": "amenities",
|
"name": "amenities",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Business hours JSON: [{\\",
|
||||||
|
"name": "business_hours",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Google Maps link",
|
"description": "Google Maps link",
|
||||||
@ -530,6 +618,104 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/locations/trending": {
|
||||||
|
"get": {
|
||||||
|
"description": "Ranks approved, non-deleted locations by total page-visits in the trailing window.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"locations"
|
||||||
|
],
|
||||||
|
"summary": "Trending locations",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Window: week | month | 3month | semester | year",
|
||||||
|
"name": "window",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page (min 1)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page size (1-50)",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/menu-items": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"menu"
|
||||||
|
],
|
||||||
|
"summary": "Get menu items",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by location ID (omit for all)",
|
||||||
|
"name": "location_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/db.GetMenuItemsRow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/news-events": {
|
"/news-events": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -1181,6 +1367,84 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"api.getLocationReviewGradesRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"critics_grades": {
|
||||||
|
"$ref": "#/definitions/api.locationReviewGradesGroup"
|
||||||
|
},
|
||||||
|
"users_grades": {
|
||||||
|
"$ref": "#/definitions/api.locationReviewGradesGroup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.locationReviewGradesGroup": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_cleanliness_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_comfort_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_facilities_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_service_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"avg_taste_grade": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"cleanliness_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"comfort_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"facilities_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"taste_grade_count": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"db.GetMenuItemsRow": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_score": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"is_available": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"location_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"submitted_by": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@ -58,6 +58,57 @@ definitions:
|
|||||||
- password
|
- password
|
||||||
- username
|
- username
|
||||||
type: object
|
type: object
|
||||||
|
api.getLocationReviewGradesRes:
|
||||||
|
properties:
|
||||||
|
critics_grades:
|
||||||
|
$ref: '#/definitions/api.locationReviewGradesGroup'
|
||||||
|
users_grades:
|
||||||
|
$ref: '#/definitions/api.locationReviewGradesGroup'
|
||||||
|
type: object
|
||||||
|
api.locationReviewGradesGroup:
|
||||||
|
properties:
|
||||||
|
avg_cleanliness_grade:
|
||||||
|
type: number
|
||||||
|
avg_comfort_grade:
|
||||||
|
type: number
|
||||||
|
avg_facilities_grade:
|
||||||
|
type: number
|
||||||
|
avg_service_grade:
|
||||||
|
type: number
|
||||||
|
avg_taste_grade:
|
||||||
|
type: number
|
||||||
|
cleanliness_grade_count:
|
||||||
|
type: integer
|
||||||
|
comfort_grade_count:
|
||||||
|
type: integer
|
||||||
|
facilities_grade_count:
|
||||||
|
type: integer
|
||||||
|
service_grade_count:
|
||||||
|
type: integer
|
||||||
|
taste_grade_count:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
db.GetMenuItemsRow:
|
||||||
|
properties:
|
||||||
|
avg_score:
|
||||||
|
type: number
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
is_available:
|
||||||
|
type: boolean
|
||||||
|
location_id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
price:
|
||||||
|
type: integer
|
||||||
|
submitted_by:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
host: localhost:8888
|
host: localhost:8888
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
@ -164,6 +215,62 @@ paths:
|
|||||||
summary: Get a single review
|
summary: Get a single review
|
||||||
tags:
|
tags:
|
||||||
- reviews
|
- reviews
|
||||||
|
/location/{location_id}/reviews/grades:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Location ID
|
||||||
|
in: path
|
||||||
|
name: location_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.getLocationReviewGradesRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
summary: Get aggregated review grades for a location
|
||||||
|
tags:
|
||||||
|
- reviews
|
||||||
|
/location/{location_id}/visit:
|
||||||
|
post:
|
||||||
|
description: Increments today's visit bucket. A single client (IP) is rate-limited
|
||||||
|
to one visit per location per 30 minutes via Redis to deter trivial F5 inflation.
|
||||||
|
parameters:
|
||||||
|
- description: Location ID
|
||||||
|
in: path
|
||||||
|
name: location_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
summary: Record a page visit for a location
|
||||||
|
tags:
|
||||||
|
- locations
|
||||||
/location/reviews:
|
/location/reviews:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
@ -293,6 +400,10 @@ paths:
|
|||||||
in: formData
|
in: formData
|
||||||
name: amenities
|
name: amenities
|
||||||
type: string
|
type: string
|
||||||
|
- description: 'Business hours JSON: [{\'
|
||||||
|
in: formData
|
||||||
|
name: business_hours
|
||||||
|
type: string
|
||||||
- description: Google Maps link
|
- description: Google Maps link
|
||||||
in: formData
|
in: formData
|
||||||
name: google_maps_link
|
name: google_maps_link
|
||||||
@ -416,6 +527,73 @@ paths:
|
|||||||
summary: Top-rated locations
|
summary: Top-rated locations
|
||||||
tags:
|
tags:
|
||||||
- locations
|
- locations
|
||||||
|
/locations/trending:
|
||||||
|
get:
|
||||||
|
description: Ranks approved, non-deleted locations by total page-visits in the
|
||||||
|
trailing window.
|
||||||
|
parameters:
|
||||||
|
- description: 'Window: week | month | 3month | semester | year'
|
||||||
|
in: query
|
||||||
|
name: window
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Page (min 1)
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Page size (1-50)
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
summary: Trending locations
|
||||||
|
tags:
|
||||||
|
- locations
|
||||||
|
/menu-items:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Filter by location ID (omit for all)
|
||||||
|
in: query
|
||||||
|
name: location_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/db.GetMenuItemsRow'
|
||||||
|
type: array
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
summary: Get menu items
|
||||||
|
tags:
|
||||||
|
- menu
|
||||||
/news-events:
|
/news-events:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user