Compare commits

...

17 Commits

Author SHA1 Message Date
bee8f6e5b8 add google maps link to location detail response 2023-09-21 15:54:49 +07:00
f6442fca2e add get location tags 2023-09-20 13:37:14 +07:00
8809aa18ce new command for refresh migration 2023-09-20 13:36:47 +07:00
902247b6ed add tags seeder and fix sequence 2023-09-20 12:30:57 +07:00
a5138ed6a5 fix null return query 2023-09-20 11:20:06 +07:00
de860d9173 add get location by detail 2023-09-19 21:50:33 +07:00
d05a9671ee add images 2023-09-19 21:49:48 +07:00
a866730e37 add field type for validation error 2023-09-19 21:49:05 +07:00
9a4f8e15b4 add images seeder 2023-09-19 21:48:23 +07:00
fa86c375c5 add top locations query 2023-09-17 16:32:48 +07:00
548c086079 add max error validation 2023-09-17 16:31:40 +07:00
bf61b6c0ea add user activites 2023-09-17 16:30:48 +07:00
8861de969a add bayes average 2023-09-17 16:30:37 +07:00
93bd5fb3e9 add submmited by in tags 2023-09-17 16:30:19 +07:00
5e07a75fc8 update note 2023-09-17 16:30:01 +07:00
d1e5e7deb7 add get recent locations rating 2023-09-14 22:51:01 +07:00
06d1284bbe add reviews seeder 2023-09-14 22:50:26 +07:00
31 changed files with 807 additions and 50 deletions

View File

@ -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

View File

@ -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
View 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,
})
}

View File

@ -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
View 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()
}
}

View File

@ -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
View 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
View 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
1 id,image_url,uploaded_by,image_type,image_of
2 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
3 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
4 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
5 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
6 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
7 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
8 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

View File

@ -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
1 id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by
8 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
9 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
10 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
11 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
12 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
13 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
View 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
1 id,submitted_by,is_from_critic,comments,score,is_hided,location_id
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
View 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 id name submitted_by target_id tags_type
2 1 makanan jos 1 1 location
3 2 masjid 1 2 location
4 3 islam 1 2 location
5 4 mancing mania mantab 1 10 location
6 5 olahraga 1 6 location
7 6 hotel 1 7 location

View File

@ -1,2 +1,4 @@
id,username,password
1,user123,password
2,critics,password
3,plebs,password
1 id username password
2 1 user123 password
3 2 critics password
4 3 plebs password

View File

@ -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()),

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS user_activities;

View 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
)

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS images;

View 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
);

View File

@ -0,0 +1 @@
ALTER TABLE tags ALTER COLUMN approved_by DROP NOT NULL;

View File

@ -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
View File

@ -0,0 +1,6 @@
-- name: GetCountImageByLocation :one
SELECT
COUNT(id)
FROM images
WHERE image_type = 'locations'
AND image_of = $1;

View File

@ -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
View 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
View 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
View 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
}

View File

@ -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
}

View File

@ -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"`

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
View File

@ -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
##########################################################################################