fix grades
This commit is contained in:
parent
305beabacb
commit
8b47394e8d
@ -260,3 +260,82 @@ func (server *Server) getUserReviewByLocation(ctx *gin.Context) {
|
||||
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/reviews", server.getListLocationReviews)
|
||||
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.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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *MockStore) GetLocationTag(ctx context.Context, targetID int32) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@ -146,6 +146,84 @@ func (q *Queries) GetReviewByLocationAndId(ctx context.Context, arg GetReviewByL
|
||||
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) {
|
||||
rows, err := q.db.Query(ctx, getListLocationReviews, arg.LocationID, arg.IsCritics, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
|
||||
@ -29,6 +29,7 @@ type Store interface {
|
||||
RecordLocationVisit(ctx context.Context, locationID int32) error
|
||||
GetLocationLifetimeVisits(ctx context.Context, locationID int32) (int64, error)
|
||||
GetTrendingLocations(ctx context.Context, arg GetTrendingLocationsParams) ([]GetTrendingLocationsRow, error)
|
||||
GetLocationReviewGrades(ctx context.Context, locationID int32) (GetLocationReviewGradesRow, error)
|
||||
}
|
||||
|
||||
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": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -356,6 +438,12 @@ const docTemplate = `{
|
||||
"name": "amenities",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Business hours JSON: [{\\",
|
||||
"name": "business_hours",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"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": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -1187,6 +1373,84 @@ const docTemplate = `{
|
||||
"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": {
|
||||
|
||||
@ -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": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -350,6 +432,12 @@
|
||||
"name": "amenities",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Business hours JSON: [{\\",
|
||||
"name": "business_hours",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"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": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -1181,6 +1367,84 @@
|
||||
"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": {
|
||||
|
||||
@ -58,6 +58,57 @@ definitions:
|
||||
- password
|
||||
- username
|
||||
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
|
||||
info:
|
||||
contact: {}
|
||||
@ -164,6 +215,62 @@ paths:
|
||||
summary: Get a single review
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
parameters:
|
||||
@ -293,6 +400,10 @@ paths:
|
||||
in: formData
|
||||
name: amenities
|
||||
type: string
|
||||
- description: 'Business hours JSON: [{\'
|
||||
in: formData
|
||||
name: business_hours
|
||||
type: string
|
||||
- description: Google Maps link
|
||||
in: formData
|
||||
name: google_maps_link
|
||||
@ -416,6 +527,73 @@ paths:
|
||||
summary: Top-rated locations
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
parameters:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user