Compare commits
17 Commits
a130a826bc
...
bee8f6e5b8
Author | SHA1 | Date | |
---|---|---|---|
bee8f6e5b8 | |||
f6442fca2e | |||
8809aa18ce | |||
902247b6ed | |||
a5138ed6a5 | |||
de860d9173 | |||
d05a9671ee | |||
a866730e37 | |||
9a4f8e15b4 | |||
fa86c375c5 | |||
548c086079 | |||
bf61b6c0ea | |||
8861de969a | |||
93bd5fb3e9 | |||
5e07a75fc8 | |||
d1e5e7deb7 | |||
06d1284bbe |
6
Makefile
6
Makefile
@ -8,6 +8,10 @@ migratedown:
|
||||
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down $n
|
||||
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose down $n
|
||||
|
||||
migraterestart:
|
||||
$(MAKE) migratedown
|
||||
$(MAKE) migrateup
|
||||
|
||||
seed:
|
||||
./import_csv.sh
|
||||
|
||||
@ -23,4 +27,4 @@ test:
|
||||
server:
|
||||
go run main.go
|
||||
|
||||
.PHONY: server migrateup migratedown sqlc
|
||||
.PHONY: server migrateup migratedown sqlc migraterestart
|
@ -13,10 +13,18 @@ type APIValidationError struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func validationErrorMsg(field string, param string, tag string) string {
|
||||
func validationErrorMsg(field string, param string, tag string, typeName string) string {
|
||||
switch tag {
|
||||
case "min":
|
||||
return fmt.Sprintf("%s character min %s", field, param)
|
||||
if typeName == "int16" || typeName == "int64" {
|
||||
return fmt.Sprintf("%s(number) value min %s", field, param)
|
||||
}
|
||||
return fmt.Sprintf("%s character min %s character", field, param)
|
||||
case "max":
|
||||
if typeName == "int16" || typeName == "int64" {
|
||||
return fmt.Sprintf("%s(number) value max is %s", field, param)
|
||||
}
|
||||
return fmt.Sprintf("%s character max %s character", field, param)
|
||||
case "required":
|
||||
return fmt.Sprintf("%s is %s", field, tag)
|
||||
default:
|
||||
@ -30,7 +38,7 @@ func ValidationErrorResponse(err error) gin.H {
|
||||
if errors.As(err, &ves) {
|
||||
out := make([]APIValidationError, len(ves))
|
||||
for i, ve := range ves {
|
||||
out[i] = APIValidationError{ve.Field(), validationErrorMsg(ve.Field(), ve.Param(), ve.ActualTag())}
|
||||
out[i] = APIValidationError{ve.Field(), validationErrorMsg(ve.Field(), ve.Param(), ve.ActualTag(), ve.Type().Name())}
|
||||
}
|
||||
temp = out
|
||||
}
|
||||
|
44
api/image.go
Normal file
44
api/image.go
Normal file
@ -0,0 +1,44 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type getAllImagesReq struct {
|
||||
Page int32 `form:"page" binding:"required,min=1"`
|
||||
PageSize int32 `form:"page_size" binding:"required,min=5"`
|
||||
LocationId int32 `form:"location_id" binding:"required,numeric"`
|
||||
}
|
||||
|
||||
func (server *Server) getAllImagesByLocation(ctx *gin.Context) {
|
||||
var req getAllImagesReq
|
||||
if err := ctx.BindQuery(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
|
||||
return
|
||||
}
|
||||
|
||||
arg := db.GetImagesByLocationParams{
|
||||
Limit: req.PageSize,
|
||||
Offset: (req.Page - 1) * req.PageSize,
|
||||
LocationId: req.LocationId,
|
||||
}
|
||||
|
||||
count, err := server.Store.GetCountImageByLocation(ctx, arg.LocationId)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
|
||||
images, err := server.Store.GetImagesByLocation(ctx, arg)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"total_image": count,
|
||||
"images": images,
|
||||
})
|
||||
}
|
@ -110,6 +110,67 @@ func (server *Server) getListLocations(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, locations)
|
||||
}
|
||||
|
||||
type getTopListLocationsReq struct {
|
||||
Page int32 `form:"page" binding:"required,min=1"`
|
||||
PageSize int32 `form:"page_size" binding:"required,min=5"`
|
||||
OrderBy int16 `form:"order_by" binding:"numeric,min=1,max=3"`
|
||||
RegionType int16 `form:"region_type" binding:"numeric,min=0,max=7"`
|
||||
}
|
||||
|
||||
func (server *Server) getTopListLocations(ctx *gin.Context) {
|
||||
var req getTopListLocationsReq
|
||||
var orderby string
|
||||
|
||||
if err := ctx.ShouldBindQuery(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
|
||||
return
|
||||
}
|
||||
|
||||
switch req.OrderBy {
|
||||
case 1:
|
||||
orderby = "iq2.avg_bayes"
|
||||
case 2:
|
||||
orderby = "iq2.critic_bayes"
|
||||
case 3:
|
||||
orderby = "iq2.user_bayes"
|
||||
}
|
||||
|
||||
arg := db.GetTopListLocationsParams{
|
||||
Limit: req.PageSize,
|
||||
Offset: (req.Page - 1) * req.PageSize,
|
||||
OrderBy: orderby,
|
||||
RegionType: sql.NullInt16{Valid: req.RegionType > 0, Int16: req.RegionType},
|
||||
}
|
||||
|
||||
locations, err := server.Store.GetTopListLocations(ctx, arg)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, locations)
|
||||
}
|
||||
|
||||
type getListRecentLocationsWithRatingsReq struct {
|
||||
Page int32 `form:"page_size" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
func (server *Server) getListRecentLocationsWithRatings(ctx *gin.Context) {
|
||||
var req getListRecentLocationsWithRatingsReq
|
||||
if err := ctx.ShouldBindQuery(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
|
||||
return
|
||||
}
|
||||
|
||||
locations, err := server.Store.GetListRecentLocationsWithRatings(ctx, req.Page)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, locations)
|
||||
}
|
||||
|
||||
type getLocationReq struct {
|
||||
ID int32 `uri:"location_id" binding:"required"`
|
||||
}
|
||||
@ -127,9 +188,20 @@ func (server *Server) getLocation(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while retrieving location detail"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, location)
|
||||
tags, err := server.Store.GetLocationTag(ctx, req.ID)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ErrorResponse(err, "Something went wrong while retrieving location tags"))
|
||||
return
|
||||
}
|
||||
|
||||
res := gin.H{
|
||||
"tags": tags,
|
||||
"detail": location,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
19
api/middleware.go
Normal file
19
api/middleware.go
Normal file
@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -35,12 +35,20 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
|
||||
func (server *Server) getRoutes() {
|
||||
router := gin.Default()
|
||||
|
||||
router.Use(CORSMiddleware())
|
||||
|
||||
router.POST("/user/signup", server.createUser)
|
||||
|
||||
// LOCATION
|
||||
router.POST("/locations", server.createLocation)
|
||||
router.GET("/locations/recent", server.getListRecentLocationsWithRatings)
|
||||
router.GET("/locations/top-ratings", server.getTopListLocations)
|
||||
router.GET("/locations", server.getListLocations)
|
||||
router.GET("/location/:location_id", server.getLocation)
|
||||
router.GET("/location/tags/:location_id", server.getTagsByLocation)
|
||||
|
||||
//IMAGES
|
||||
router.GET("/images/location", server.getAllImagesByLocation)
|
||||
|
||||
server.Router = router
|
||||
}
|
||||
|
28
api/tags.go
Normal file
28
api/tags.go
Normal file
@ -0,0 +1,28 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type getTagsByLocationReq struct {
|
||||
LocationID int32 `uri:"location_id" binding:"required,numeric"`
|
||||
}
|
||||
|
||||
func (server *Server) getTagsByLocation(ctx *gin.Context) {
|
||||
var req getTagsByLocationReq
|
||||
|
||||
if err := ctx.BindUri(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := server.Store.GetLocationTag(ctx, req.LocationID)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, tags)
|
||||
}
|
8
db/csv_seeder/images.csv
Normal file
8
db/csv_seeder/images.csv
Normal file
@ -0,0 +1,8 @@
|
||||
id,image_url,uploaded_by,image_type,image_of
|
||||
1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/ee/b0/7b/murni-s-warung-shop-ubud.jpg?w=1200&h=-1&s=1#1#ocations#1
|
||||
2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/4f/fc/65/getlstd-property-photo.jpg?w=1100&h=-1&s=1#1#locations#1
|
||||
3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/ee/b0/43/murni-s-warung-shop-ubud.jpg?w=1200&h=-1&s=1#1#locations#1
|
||||
4#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1b/ee/af/d0/murni-s-warung-shop-ubud.jpg?w=1200&h=-1&s=1#1#locations#1
|
||||
5#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/04/0f/7e/53/istiqlal-mosque-mesjid.jpg?w=1200&h=-1&s=1#1#locations#2
|
||||
6#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/28/27/1a/5a/istiqlal-mosque.jpg?w=1200&h=-1&s=1#1#locations#2
|
||||
7#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/28/27/1a/5a/istiqlal-mosque.jpg?w=1200&h=-1&s=1#1#locations#2
|
|
@ -8,3 +8,6 @@ id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by
|
||||
7#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1
|
||||
8#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1
|
||||
9#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu#1
|
||||
10#Pulau Padar#Pulau Padar#2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/4e/11/f3/padar-island.jpg?w=800&h=-1&s=1#5315#https://www.google.com/maps/place/Pulau+Padar/@-8.6554183,119.570686,14.83z/data=!4m6!3m5!1s0x2db4f84ff6cd01ab:0xf7e6fd33b692a898!8m2!3d-8.6489909!4d119.5832593!16s%2Fg%2F121d16wd?entry=ttu#1
|
||||
11#Jl. Ahmad Yani 18, Kel. Kelimutu, Kec. Ende Tengah#Sari rasa#3#https://cdn.discordapp.com/attachments/743422487882104837/1151903310295601172/image.png#5315#https://www.google.com/maps/place/Rumah+Makan+Cha+Cha/@-8.6153697,120.4652533,21z/data=!4m6!3m5!1s0x2db37469e54c0dd3:0x36ac988c726ed544!8m2!3d-8.6153631!4d120.465275!16s%2Fg%2F11bwpclp91?entry=ttu#1
|
||||
12#Danau Sentani#Danau Sentani#3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/c7/e8/17/20190630-143008-largejpg.jpg?w=800&h=-1&s=1#9403#https://www.google.com/maps/place/Danau+Sentani/@-2.639976,140.3889748,12.46z/data=!4m7!3m6!1s0x686cf33c72660fbf:0x7e5c1e7d20d930d7!4b1!8m2!3d-2.6133004!4d140.518734!16s%2Fm%2F0tkjf6v?entry=ttu#1
|
|
16
db/csv_seeder/reviews.csv
Normal file
16
db/csv_seeder/reviews.csv
Normal file
@ -0,0 +1,16 @@
|
||||
id,submitted_by,is_from_critic,comments,score,is_hided,location_id
|
||||
1#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
2#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
3#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
4#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#2
|
||||
5#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#3
|
||||
6#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#9
|
||||
7#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
8#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#9
|
||||
9#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#4
|
||||
10#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#4
|
||||
11#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#2
|
||||
12#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
13#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
14#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
15#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
|
7
db/csv_seeder/tags.csv
Normal file
7
db/csv_seeder/tags.csv
Normal file
@ -0,0 +1,7 @@
|
||||
id,name,submitted_by,target_id,tags_type
|
||||
1,makanan jos,1,1,location
|
||||
2,masjid,1,2,location
|
||||
3,islam,1,2,location
|
||||
4,mancing mania mantab,1,10,location
|
||||
5,olahraga,1,6,location
|
||||
6,hotel,1,7,location
|
|
@ -1,2 +1,4 @@
|
||||
id,username,password
|
||||
1,user123,password
|
||||
2,critics,password
|
||||
3,plebs,password
|
|
@ -91,10 +91,10 @@ CREATE TABLE locations(
|
||||
CREATE TABLE tags (
|
||||
"id" serial primary key not null,
|
||||
"name" varchar(50) not null,
|
||||
"target_id" integer,
|
||||
"tags_type" varchar(20),
|
||||
"created_at" timestamp default(now()),
|
||||
"updated_at" timestamp default(now())
|
||||
"submitted_by" integer references "users"("id") not null,
|
||||
"target_id" integer not null, -- location_id, story_id
|
||||
"tags_type" varchar(20) not null, -- locations, stories
|
||||
"approved_by" integer references "users"("id")
|
||||
);
|
||||
|
||||
CREATE TABLE location_images (
|
||||
@ -111,6 +111,7 @@ CREATE TABLE reviews (
|
||||
"submitted_by" integer references "users"("id") not null,
|
||||
"comments" text not null,
|
||||
"score" smallint not null,
|
||||
"is_from_critic" boolean not null,
|
||||
"is_hided" boolean, -- if comments violate TOS just hide the reviews
|
||||
"location_id" integer references "locations"("id") not null,
|
||||
"created_at" timestamp default(now()),
|
||||
|
1
db/migrations/000003_create_user_activities.down.sql
Normal file
1
db/migrations/000003_create_user_activities.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS user_activities;
|
10
db/migrations/000003_create_user_activities.up.sql
Normal file
10
db/migrations/000003_create_user_activities.up.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE user_activities (
|
||||
id serial primary key not null,
|
||||
target_id integer not null, -- location_id, comment_id, stories_id
|
||||
target varchar not null, -- locations, comments
|
||||
action varchar not null, -- "comments on some stories with id, submmited a locations, submitted a stories"
|
||||
link text,
|
||||
comment text,
|
||||
created_at timestamp default(now()) not null,
|
||||
updated_at timestamp default(now()) not null
|
||||
)
|
1
db/migrations/000004_create_images_table.down.sql
Normal file
1
db/migrations/000004_create_images_table.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS images;
|
9
db/migrations/000004_create_images_table.up.sql
Normal file
9
db/migrations/000004_create_images_table.up.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE images(
|
||||
id serial primary key not null,
|
||||
image_url varchar not null,
|
||||
uploaded_by integer references "users"("id") not null,
|
||||
image_type varchar not null, -- Locations, Stories
|
||||
image_of integer not null, -- Location_id, stories_id
|
||||
created_at timestamp default (now()) not null,
|
||||
updated_at timestamp default (now()) not null
|
||||
);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE tags ALTER COLUMN approved_by DROP NOT NULL;
|
@ -64,6 +64,36 @@ func (mr *MockStoreMockRecorder) CreateUser(arg0, arg1 interface{}) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetCountImageByLocation mocks base method.
|
||||
func (m *MockStore) GetCountImageByLocation(arg0 context.Context, arg1 int32) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCountImageByLocation", arg0, arg1)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCountImageByLocation indicates an expected call of GetCountImageByLocation.
|
||||
func (mr *MockStoreMockRecorder) GetCountImageByLocation(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCountImageByLocation", reflect.TypeOf((*MockStore)(nil).GetCountImageByLocation), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetImagesByLocation mocks base method.
|
||||
func (m *MockStore) GetImagesByLocation(arg0 context.Context, arg1 db.GetImagesByLocationParams) ([]db.GetImagesByLocationRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetImagesByLocation", arg0, arg1)
|
||||
ret0, _ := ret[0].([]db.GetImagesByLocationRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetImagesByLocation indicates an expected call of GetImagesByLocation.
|
||||
func (mr *MockStoreMockRecorder) GetImagesByLocation(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImagesByLocation", reflect.TypeOf((*MockStore)(nil).GetImagesByLocation), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetListLocations mocks base method.
|
||||
func (m *MockStore) GetListLocations(arg0 context.Context) ([]db.Location, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -79,11 +109,26 @@ func (mr *MockStoreMockRecorder) GetListLocations(arg0 interface{}) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocations", reflect.TypeOf((*MockStore)(nil).GetListLocations), arg0)
|
||||
}
|
||||
|
||||
// GetListRecentLocationsWithRatings mocks base method.
|
||||
func (m *MockStore) GetListRecentLocationsWithRatings(arg0 context.Context, arg1 int32) ([]db.GetListRecentLocationsWithRatingsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetListRecentLocationsWithRatings", arg0, arg1)
|
||||
ret0, _ := ret[0].([]db.GetListRecentLocationsWithRatingsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetListRecentLocationsWithRatings indicates an expected call of GetListRecentLocationsWithRatings.
|
||||
func (mr *MockStoreMockRecorder) GetListRecentLocationsWithRatings(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListRecentLocationsWithRatings", reflect.TypeOf((*MockStore)(nil).GetListRecentLocationsWithRatings), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetLocation mocks base method.
|
||||
func (m *MockStore) GetLocation(arg0 context.Context, arg1 int32) (db.Location, error) {
|
||||
func (m *MockStore) GetLocation(arg0 context.Context, arg1 int32) (db.GetLocationRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLocation", arg0, arg1)
|
||||
ret0, _ := ret[0].(db.Location)
|
||||
ret0, _ := ret[0].(db.GetLocationRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@ -94,6 +139,36 @@ func (mr *MockStoreMockRecorder) GetLocation(arg0, arg1 interface{}) *gomock.Cal
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocation", reflect.TypeOf((*MockStore)(nil).GetLocation), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetLocationTag mocks base method.
|
||||
func (m *MockStore) GetLocationTag(arg0 context.Context, arg1 int32) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLocationTag", arg0, arg1)
|
||||
ret0, _ := ret[0].([]string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetLocationTag indicates an expected call of GetLocationTag.
|
||||
func (mr *MockStoreMockRecorder) GetLocationTag(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationTag", reflect.TypeOf((*MockStore)(nil).GetLocationTag), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetTopListLocations mocks base method.
|
||||
func (m *MockStore) GetTopListLocations(arg0 context.Context, arg1 db.GetTopListLocationsParams) ([]db.GetTopListLocationsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTopListLocations", arg0, arg1)
|
||||
ret0, _ := ret[0].([]db.GetTopListLocationsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetTopListLocations indicates an expected call of GetTopListLocations.
|
||||
func (mr *MockStoreMockRecorder) GetTopListLocations(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTopListLocations", reflect.TypeOf((*MockStore)(nil).GetTopListLocations), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdatePassword mocks base method.
|
||||
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
6
db/queries/images.sql
Normal file
6
db/queries/images.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- name: GetCountImageByLocation :one
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM images
|
||||
WHERE image_type = 'locations'
|
||||
AND image_of = $1;
|
@ -1,9 +1,42 @@
|
||||
-- name: GetListLocations :many
|
||||
SELECT * FROM locations;
|
||||
|
||||
-- name: GetListRecentLocationsWithRatings :many
|
||||
SELECT
|
||||
l.id,
|
||||
name,
|
||||
thumbnail,
|
||||
COALESCE(re.regency_name, '') as regency_name,
|
||||
COALESCE(pr.province_name, '') as province_name,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count
|
||||
FROM locations l
|
||||
JOIN regencies re on re.id = l.regency_id
|
||||
JOIN provinces pr on re.province_id = pr.id
|
||||
WHERE approved_by IS NOT NULL
|
||||
ORDER BY l.created_at ASC
|
||||
LIMIT $1;
|
||||
|
||||
-- name: GetLocation :one
|
||||
SELECT * FROM locations
|
||||
WHERE id = $1;
|
||||
SELECT
|
||||
l.id,
|
||||
l.name,
|
||||
l.address,
|
||||
COALESCE(l.google_maps_link, '') as google_maps_link,
|
||||
l.thumbnail,
|
||||
l.submitted_by,
|
||||
COALESCE(r.regency_name, '') as regency_name,
|
||||
COALESCE(p.province_name, '') as province_name,
|
||||
COALESCE(r2.region_name, '') as region_name,
|
||||
u.username as submitted_by_user
|
||||
FROM locations l
|
||||
JOIN regencies r on r.id = l.regency_id
|
||||
JOIN provinces p on p.id = r.province_id
|
||||
JOIN regions r2 on r2.id = p.region_id
|
||||
JOIN users u on l.approved_by = u.id
|
||||
WHERE l.id = $1;
|
||||
|
||||
-- name: CreateLocation :exec
|
||||
INSERT INTO locations(
|
||||
@ -15,3 +48,13 @@ INSERT INTO locations(
|
||||
) values (
|
||||
$1, $2, $3, $4, $5
|
||||
);
|
||||
|
||||
-- name: GetLocationTag :many
|
||||
SELECT
|
||||
name
|
||||
FROM tags
|
||||
WHERE tags_type = 'location'
|
||||
AND
|
||||
target_id = $1
|
||||
AND
|
||||
approved_by IS NOT NULL;
|
61
db/sqlc/images.go
Normal file
61
db/sqlc/images.go
Normal file
@ -0,0 +1,61 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GetImagesByLocationParams struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
LocationId int32
|
||||
}
|
||||
|
||||
type GetImagesByLocationRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Src string `json:"src"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UploadedBy string `json:"uploaded_by"`
|
||||
}
|
||||
|
||||
const getImagesByLocationQ = `
|
||||
SELECT
|
||||
i.id,
|
||||
i.image_url as src,
|
||||
i.created_at,
|
||||
u.username as uploaded_by
|
||||
FROM images i
|
||||
JOIN users u on i.uploaded_by = u.id
|
||||
WHERE i.image_type = 'locations' AND image_of = $1
|
||||
LIMIT $2
|
||||
OFFSET $3
|
||||
`
|
||||
|
||||
func (q *Queries) GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getImagesByLocationQ, arg.LocationId, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetImagesByLocationRow{}
|
||||
for rows.Next() {
|
||||
var i GetImagesByLocationRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Src,
|
||||
&i.CreatedAt,
|
||||
&i.UploadedBy,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
25
db/sqlc/images.sql.go
Normal file
25
db/sqlc/images.sql.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
// source: images.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const getCountImageByLocation = `-- name: GetCountImageByLocation :one
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM images
|
||||
WHERE image_type = 'locations'
|
||||
AND image_of = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCountImageByLocation(ctx context.Context, imageOf int32) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getCountImageByLocation, imageOf)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
111
db/sqlc/locations.go
Normal file
111
db/sqlc/locations.go
Normal file
@ -0,0 +1,111 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type GetTopListLocationsParams struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
OrderBy string
|
||||
RegionType sql.NullInt16
|
||||
}
|
||||
|
||||
type GetTopListLocationsRow struct {
|
||||
RowNumber int32 `json:"row_number"`
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
RegionName string `json:"region_name"`
|
||||
Thumbnail sql.NullString `json:"thumbnail"`
|
||||
Address string `json:"address"`
|
||||
GoogleMapsLink string `json:"google_maps_link"`
|
||||
RegencyName string `json:"regency_name"`
|
||||
CriticScore int16 `json:"critic_score"`
|
||||
CriticCount int16 `json:"critic_count"`
|
||||
UserScore int16 `json:"user_score"`
|
||||
UserCount int16 `json:"user_count"`
|
||||
TotalCount int16 `json:"total_count"`
|
||||
CriticBayes int16 `json:"critic_bayes"`
|
||||
UserBayes int16 `json:"user_bayes"`
|
||||
AvgBayes int16 `json:"avg_bayes"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, error) {
|
||||
regionType := ""
|
||||
|
||||
if arg.RegionType.Valid {
|
||||
regionType = fmt.Sprintf("AND reg.id = %d", arg.RegionType.Int16)
|
||||
}
|
||||
|
||||
// https://fulmicoton.com/posts/bayesian_rating/
|
||||
getTopListQ := fmt.Sprintf(`SELECT
|
||||
row_number() over (ORDER BY %s DESC) as row_number,
|
||||
*
|
||||
FROM(
|
||||
SELECT
|
||||
*,
|
||||
(SELECT 5 * 4 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) as critic_bayes,
|
||||
(SELECT 50 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) as user_bayes,
|
||||
((SELECT 50 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) + (SELECT 5 * 4 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) ) / 2 as avg_bayes
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
l.id,
|
||||
name,
|
||||
l.address,
|
||||
reg.region_name as region_name,
|
||||
l.google_maps_link,
|
||||
thumbnail,
|
||||
re.regency_name,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count
|
||||
FROM locations l
|
||||
JOIN regencies re on re.id = l.regency_id
|
||||
JOIN provinces prov on prov.id = re.province_id
|
||||
JOIN regions reg on reg.id = prov.region_id
|
||||
WHERE approved_by IS NOT NULL %s ) iq1 ) iq2
|
||||
LIMIT $1
|
||||
OFFSET $2;`, arg.OrderBy, regionType)
|
||||
|
||||
rows, err := q.db.QueryContext(ctx, getTopListQ, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetTopListLocationsRow{}
|
||||
for rows.Next() {
|
||||
var i GetTopListLocationsRow
|
||||
if err := rows.Scan(
|
||||
&i.RowNumber,
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Address,
|
||||
&i.RegionName,
|
||||
&i.GoogleMapsLink,
|
||||
&i.Thumbnail,
|
||||
&i.RegencyName,
|
||||
&i.CriticScore,
|
||||
&i.CriticCount,
|
||||
&i.UserScore,
|
||||
&i.UserCount,
|
||||
&i.CriticBayes,
|
||||
&i.UserBayes,
|
||||
&i.AvgBayes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.TotalCount = i.UserCount + i.CriticCount
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
@ -82,28 +82,151 @@ func (q *Queries) GetListLocations(ctx context.Context) ([]Location, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getLocation = `-- name: GetLocation :one
|
||||
SELECT id, address, name, google_maps_link, submitted_by, total_visited, thumbnail, regency_id, is_deleted, created_at, updated_at, approved_by, approved_at FROM locations
|
||||
WHERE id = $1
|
||||
const getListRecentLocationsWithRatings = `-- name: GetListRecentLocationsWithRatings :many
|
||||
SELECT
|
||||
l.id,
|
||||
name,
|
||||
thumbnail,
|
||||
COALESCE(re.regency_name, '') as regency_name,
|
||||
COALESCE(pr.province_name, '') as province_name,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count,
|
||||
(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score,
|
||||
(SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count
|
||||
FROM locations l
|
||||
JOIN regencies re on re.id = l.regency_id
|
||||
JOIN provinces pr on re.province_id = pr.id
|
||||
WHERE approved_by IS NOT NULL
|
||||
ORDER BY l.created_at ASC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetLocation(ctx context.Context, id int32) (Location, error) {
|
||||
type GetListRecentLocationsWithRatingsRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Thumbnail sql.NullString `json:"thumbnail"`
|
||||
RegencyName string `json:"regency_name"`
|
||||
ProvinceName string `json:"province_name"`
|
||||
CriticScore interface{} `json:"critic_score"`
|
||||
CriticCount int64 `json:"critic_count"`
|
||||
UserScore interface{} `json:"user_score"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetListRecentLocationsWithRatings(ctx context.Context, limit int32) ([]GetListRecentLocationsWithRatingsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getListRecentLocationsWithRatings, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetListRecentLocationsWithRatingsRow{}
|
||||
for rows.Next() {
|
||||
var i GetListRecentLocationsWithRatingsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Thumbnail,
|
||||
&i.RegencyName,
|
||||
&i.ProvinceName,
|
||||
&i.CriticScore,
|
||||
&i.CriticCount,
|
||||
&i.UserScore,
|
||||
&i.UserCount,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getLocation = `-- name: GetLocation :one
|
||||
SELECT
|
||||
l.id,
|
||||
l.name,
|
||||
l.address,
|
||||
COALESCE(l.google_maps_link, '') as google_maps_link,
|
||||
l.thumbnail,
|
||||
l.submitted_by,
|
||||
COALESCE(r.regency_name, '') as regency_name,
|
||||
COALESCE(p.province_name, '') as province_name,
|
||||
COALESCE(r2.region_name, '') as region_name,
|
||||
u.username as submitted_by_user
|
||||
FROM locations l
|
||||
JOIN regencies r on r.id = l.regency_id
|
||||
JOIN provinces p on p.id = r.province_id
|
||||
JOIN regions r2 on r2.id = p.region_id
|
||||
JOIN users u on l.approved_by = u.id
|
||||
WHERE l.id = $1
|
||||
`
|
||||
|
||||
type GetLocationRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
GoogleMapsLink string `json:"google_maps_link"`
|
||||
Thumbnail sql.NullString `json:"thumbnail"`
|
||||
SubmittedBy int32 `json:"submitted_by"`
|
||||
RegencyName string `json:"regency_name"`
|
||||
ProvinceName string `json:"province_name"`
|
||||
RegionName string `json:"region_name"`
|
||||
SubmittedByUser string `json:"submitted_by_user"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetLocation(ctx context.Context, id int32) (GetLocationRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLocation, id)
|
||||
var i Location
|
||||
var i GetLocationRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Address,
|
||||
&i.Name,
|
||||
&i.Address,
|
||||
&i.GoogleMapsLink,
|
||||
&i.SubmittedBy,
|
||||
&i.TotalVisited,
|
||||
&i.Thumbnail,
|
||||
&i.RegencyID,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ApprovedBy,
|
||||
&i.ApprovedAt,
|
||||
&i.SubmittedBy,
|
||||
&i.RegencyName,
|
||||
&i.ProvinceName,
|
||||
&i.RegionName,
|
||||
&i.SubmittedByUser,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLocationTag = `-- name: GetLocationTag :many
|
||||
SELECT
|
||||
name
|
||||
FROM tags
|
||||
WHERE tags_type = 'location'
|
||||
AND
|
||||
target_id = $1
|
||||
AND
|
||||
approved_by IS NOT NULL
|
||||
`
|
||||
|
||||
func (q *Queries) GetLocationTag(ctx context.Context, targetID int32) ([]string, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLocationTag, targetID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []string{}
|
||||
for rows.Next() {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, name)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
@ -124,6 +124,16 @@ type Comment struct {
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
ID int32 `json:"id"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
UploadedBy int32 `json:"uploaded_by"`
|
||||
ImageType string `json:"image_type"`
|
||||
ImageOf int32 `json:"image_of"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
ID int32 `json:"id"`
|
||||
Address string `json:"address"`
|
||||
@ -177,6 +187,7 @@ type Review struct {
|
||||
SubmittedBy int32 `json:"submitted_by"`
|
||||
Comments string `json:"comments"`
|
||||
Score int16 `json:"score"`
|
||||
IsFromCritic bool `json:"is_from_critic"`
|
||||
IsHided sql.NullBool `json:"is_hided"`
|
||||
LocationID int32 `json:"location_id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
@ -186,10 +197,10 @@ type Review struct {
|
||||
type Tag struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TargetID sql.NullInt32 `json:"target_id"`
|
||||
TagsType sql.NullString `json:"tags_type"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
SubmittedBy int32 `json:"submitted_by"`
|
||||
TargetID int32 `json:"target_id"`
|
||||
TagsType string `json:"tags_type"`
|
||||
ApprovedBy sql.NullInt32 `json:"approved_by"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
@ -212,6 +223,17 @@ type User struct {
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserActivity struct {
|
||||
ID int32 `json:"id"`
|
||||
TargetID int32 `json:"target_id"`
|
||||
Target string `json:"target"`
|
||||
Action string `json:"action"`
|
||||
Link sql.NullString `json:"link"`
|
||||
Comment sql.NullString `json:"comment"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserReport struct {
|
||||
ID int32 `json:"id"`
|
||||
Message string `json:"message"`
|
||||
|
@ -11,8 +11,11 @@ import (
|
||||
type Querier interface {
|
||||
CreateLocation(ctx context.Context, arg CreateLocationParams) error
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
GetCountImageByLocation(ctx context.Context, imageOf int32) (int64, error)
|
||||
GetListLocations(ctx context.Context) ([]Location, error)
|
||||
GetLocation(ctx context.Context, id int32) (Location, error)
|
||||
GetListRecentLocationsWithRatings(ctx context.Context, limit int32) ([]GetListRecentLocationsWithRatingsRow, error)
|
||||
GetLocation(ctx context.Context, id int32) (GetLocationRow, error)
|
||||
GetLocationTag(ctx context.Context, targetID int32) ([]string, error)
|
||||
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
||||
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/yiplee/sqlc"
|
||||
@ -8,6 +9,8 @@ import (
|
||||
|
||||
type Store interface {
|
||||
Querier
|
||||
GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, error)
|
||||
GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error)
|
||||
}
|
||||
|
||||
type SQLStore struct {
|
||||
|
@ -6,6 +6,9 @@ sudo -u postgres psql \
|
||||
-c '\copy provinces(id, province_name, region_id) FROM '"'/tmp/provinsi.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-c '\copy regencies(id, province_id, regency_name) FROM '"'/tmp/kabupaten.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-c '\copy locations(id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy reviews(id,submitted_by,is_from_critic,comments,score,is_hided,location_id) FROM '"'/tmp/reviews.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy images(id,image_url,uploaded_by,image_type,image_of) FROM '"'/tmp/images.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy tags(id,name,submitted_by,target_id,tags_type) FROM '"'/tmp/tags.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-d hiling_dev &&
|
||||
sudo -u postgres psql \
|
||||
-c '\copy users(id,username,password) FROM '"'/tmp/user.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
@ -13,6 +16,9 @@ sudo -u postgres psql \
|
||||
-c '\copy provinces(id, province_name, region_id) FROM '"'/tmp/provinsi.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-c '\copy regencies(id, province_id, regency_name) FROM '"'/tmp/kabupaten.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-c '\copy locations(id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy reviews(id,submitted_by,is_from_critic,comments,score,is_hided,location_id) FROM '"'/tmp/reviews.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy images(id,image_url,uploaded_by,image_type,image_of) FROM '"'/tmp/images.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||
-c '\copy tags(id,name,submitted_by,target_id,tags_type) FROM '"'/tmp/tags.csv'"' DELIMITER '"','"' CSV HEADER;' \
|
||||
-d hiling_dev_test
|
||||
|
||||
# FIXING SEQUENCES AFTER SEEDING
|
||||
@ -21,10 +27,28 @@ sudo -u postgres psql \
|
||||
-d hiling_dev_test &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'users_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'users_id_seq'"'))-1 FROM users))' \
|
||||
-d hiling_dev_test
|
||||
-d hiling_dev_test &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'reviews_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'reviews_id_seq'"'))-1 FROM reviews))' \
|
||||
-d hiling_dev_test &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'images_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'images_id_seq'"'))-1 FROM images))' \
|
||||
-d hiling_dev_test &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'tags_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'tags_id_seq'"'))-1 FROM tags))' \
|
||||
-d hiling_dev_test &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'locations_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'locations_id_seq'"'))-1 FROM locations))' \
|
||||
-d hiling_dev &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'users_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'users_id_seq'"'))-1 FROM users))' \
|
||||
-d hiling_dev &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'reviews_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'reviews_id_seq'"'))-1 FROM reviews))' \
|
||||
-d hiling_dev &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'images_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'images_id_seq'"'))-1 FROM images))' \
|
||||
-d hiling_dev &&
|
||||
sudo -u postgres psql \
|
||||
-c 'SELECT setval('"'tags_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'tags_id_seq'"'))-1 FROM tags))' \
|
||||
-d hiling_dev
|
19
notes
19
notes
@ -59,3 +59,22 @@ THE REST SHOUDL HAVE AUTHORIVAOZOIANITON
|
||||
|
||||
|
||||
##########################################################################################
|
||||
|
||||
|
||||
##########################################################################################
|
||||
|
||||
https://github.com/discourse/discourse
|
||||
USE THIS FOR DISCSUSSSINON
|
||||
|
||||
|
||||
##########################################################################################
|
||||
|
||||
|
||||
##########################################################################################
|
||||
|
||||
OK FOR THE RATINGS IMMA USE THAT FLOW FIRST LIKE JOINING THE LOCAITONS AND REVIEWS
|
||||
CALCULATE ALL THE RATINGS SHIT,
|
||||
AFTER I FINISHED THE WHOLE THING IMMA TRY TO COMPARE IF EACH LOCATIONS HAVE user_ratings
|
||||
user_counts etc etc
|
||||
|
||||
##########################################################################################
|
Loading…
Reference in New Issue
Block a user