Compare commits

...

11 Commits

Author SHA1 Message Date
675abcdb18 fix auth, add reviews, fix cors, fix queries 2023-09-27 21:58:14 +07:00
8a677a2130 set max db connection 2023-09-27 21:57:21 +07:00
4e5bcddb4b add new notes 2023-09-27 21:57:05 +07:00
e33d243810 fix .env comment 2023-09-27 21:56:38 +07:00
7100a3618f fix column type 2023-09-24 15:01:46 +07:00
3f18f0e1c2 update todo 2023-09-24 15:01:01 +07:00
89ea569f36 update makefile commands 2023-09-24 15:00:52 +07:00
5368e934b6 add get user detail 2023-09-23 15:24:21 +07:00
cbf7a41c80 change datatypea and remove sqlc query 2023-09-23 15:23:50 +07:00
4564085075 new note 2023-09-23 15:23:31 +07:00
bcb242c0aa add user sessions 2023-09-21 21:38:41 +07:00
36 changed files with 1114 additions and 191 deletions

View File

@ -8,12 +8,13 @@ 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}?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 migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose down $n
seed:
./import_csv.sh
migraterestart: migraterestart:
$(MAKE) migratedown $(MAKE) migratedown
$(MAKE) migrateup $(MAKE) migrateup
$(MAKE) seed
seed:
./import_csv.sh
mock-generate: mock-generate:
mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/hiling_go/db/sqlc Store mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/hiling_go/db/sqlc Store

3
TODO Normal file
View File

@ -0,0 +1,3 @@
- COMMENTS FLOW
- USER PROFILE FLOW
- BEST LOCATIONS FILTER ( MIN REVIEWS AND TAGS)

View File

@ -149,6 +149,17 @@ func (server *Server) getTopListLocations(ctx *gin.Context) {
} }
ctx.JSON(http.StatusOK, locations) ctx.JSON(http.StatusOK, locations)
// str, err := ctx.Cookie("kek123429")
// if err != nil {
// ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// }
// ctx.JSON(http.StatusOK, gin.H{
// "str": str,
// "res": locations,
// })
} }
type getListRecentLocationsWithRatingsReq struct { type getListRecentLocationsWithRatingsReq struct {
@ -198,10 +209,104 @@ func (server *Server) getLocation(ctx *gin.Context) {
return return
} }
users_reviews, err := server.Store.GetListLocationReviews(ctx, db.GetListLocationReviewsParams{
LocationID: req.ID,
Limit: 5,
Offset: 0,
IsCritics: false,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to receive user reviews for this location"))
return
}
critics_reviews, err := server.Store.GetListLocationReviews(ctx, db.GetListLocationReviewsParams{
LocationID: req.ID,
Limit: 20,
Offset: 0,
IsCritics: true,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to receive user reviews for this location"))
return
}
res := gin.H{ res := gin.H{
"tags": tags, "tags": tags,
"detail": location, "detail": location,
"users_review": users_reviews,
"critics_review": critics_reviews,
} }
ctx.JSON(http.StatusOK, res) ctx.JSON(http.StatusOK, res)
} }
type getListLocationReviewsReq struct {
PageSize int8 `form:"page_size" binding:"required"`
Page int8 `form:"page" binding:"required,min=1"`
Type int8 `form:"type" binding:"required"` // 0 = all, 1 = critics,2 = users
LocationID int32 `form:"location_id" binding:"required"`
}
type getListLocationReviewsRes struct {
CriticsReviews []db.GetListLocationReviewsRow `json:"critics_reviews"`
UsersReviews []db.GetListLocationReviewsRow `json:"users_reviews"`
}
func (server *Server) getListLocationReviews(ctx *gin.Context) {
var req getListLocationReviewsReq
var res getListLocationReviewsRes
if err := ctx.ShouldBindQuery(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
if req.Type-1 == 1 || req.Type-1 == 0 {
arg := db.GetListLocationReviewsParams{
LocationID: req.LocationID,
Offset: (req.Page - 1) * req.PageSize,
Limit: req.PageSize,
IsCritics: true,
}
reviews, err := server.Store.GetListLocationReviews(ctx, arg)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, ErrorResponse(err, "There's no critics reviews for this location yet"))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get critics reviews for this location"))
return
}
res.CriticsReviews = reviews
}
if req.Type-1 == 2 || req.Type-1 == 0 {
arg := db.GetListLocationReviewsParams{
LocationID: req.LocationID,
Limit: req.PageSize,
Offset: (req.Page - 1) * req.PageSize,
IsCritics: false,
}
reviews, err := server.Store.GetListLocationReviews(ctx, arg)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, ErrorResponse(err, "There's no critics reviews for this location yet"))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get critics reviews for this location"))
return
}
res.UsersReviews = reviews
}
ctx.JSON(http.StatusOK, res)
}

View File

@ -1,19 +1,50 @@
package api package api
import "github.com/gin-gonic/gin" import (
"net/http"
func CORSMiddleware() gin.HandlerFunc { "git.nochill.in/nochill/hiling_go/util/token"
return func(c *gin.Context) { "github.com/gin-gonic/gin"
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" { // func CORSMiddleware() gin.HandlerFunc {
c.AbortWithStatus(204) // return func(ctx *gin.Context) {
// ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
// ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
// ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
// ctx.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
// if ctx.Request.Method == "OPTIONS" {
// ctx.AbortWithStatus(204)
// return
// }
// ctx.Next()
// }
// }
const (
authorizationPayloadKey = "authorization_payload"
)
func authMiddleware(tokenMaker token.Maker) gin.HandlerFunc {
return func(ctx *gin.Context) {
str, err := ctx.Cookie("paseto")
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, ErrorResponse(err, "Unauthorized"))
return return
} }
c.Next() payload, err := tokenMaker.VerifyToken(str)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to verify token"))
return
}
ctx.Set(authorizationPayloadKey, payload)
ctx.Next()
} }
} }

96
api/reviews.go Normal file
View File

@ -0,0 +1,96 @@
package api
import (
"database/sql"
"net/http"
db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util/token"
"github.com/gin-gonic/gin"
)
type createReviewReq struct {
SubmittedBy int8 `json:"submitted_by" binding:"required"`
Comments string `json:"comments" binding:"required"`
Score int8 `json:"score" binding:"numeric,max=100"`
IsFromCritic *bool `json:"is_from_critic"`
IsHided *bool `json:"is_hided"`
LocationId int32 `json:"location_id" binding:"required"`
}
func (server *Server) createReview(ctx *gin.Context) {
var req createReviewReq
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
checkArg := db.CheckIfReviewExistsParams{
LocationID: req.LocationId,
SubmittedBy: int32(req.SubmittedBy),
}
reviewExist, err := server.Store.CheckIfReviewExists(ctx, checkArg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, "Something went wrong while try to check review")
return
}
if reviewExist != 0 {
ctx.JSON(http.StatusConflict, "User review already exist")
return
}
arg := db.CreateReviewParams{
SubmittedBy: int32(req.SubmittedBy),
Comments: req.Comments,
Score: int16(req.Score),
IsFromCritic: *req.IsFromCritic,
IsHided: *req.IsHided,
LocationID: req.LocationId,
}
review, err := server.Store.CreateReview(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to save review"))
return
}
ctx.JSON(http.StatusOK, review)
}
type getUserReviewByLocationReq struct {
LocationID int32 `uri:"location_id" binding:"required"`
}
func (server *Server) getUserReviewByLocation(ctx *gin.Context) {
var req getUserReviewByLocationReq
if err := ctx.ShouldBindUri(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
arg := db.GetUserReviewByLocationParams{
SubmittedBy: int32(authPayload.UserID),
LocationID: req.LocationID,
}
review, err := server.Store.GetUserReviewByLocation(ctx, arg)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, ErrorResponse(err, "Review not found"))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to recevie current user review"))
return
}
ctx.JSON(http.StatusOK, review)
}

View File

@ -6,6 +6,7 @@ import (
db "git.nochill.in/nochill/hiling_go/db/sqlc" db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util" "git.nochill.in/nochill/hiling_go/util"
"git.nochill.in/nochill/hiling_go/util/token" "git.nochill.in/nochill/hiling_go/util/token"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -35,9 +36,18 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
func (server *Server) getRoutes() { func (server *Server) getRoutes() {
router := gin.Default() router := gin.Default()
router.Use(CORSMiddleware()) router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:5173"},
AllowCredentials: true,
AllowHeaders: []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"},
AllowMethods: []string{"POST", "PUT", "GET", "DELETE", "PATCH"},
}))
// router.Use(CORSMiddleware())
router.POST("/user/signup", server.createUser) router.POST("/user/signup", server.createUser)
router.POST("/user/login", server.login)
router.POST("/user/logout", server.logout)
// LOCATION // LOCATION
router.POST("/locations", server.createLocation) router.POST("/locations", server.createLocation)
@ -46,10 +56,16 @@ func (server *Server) getRoutes() {
router.GET("/locations", server.getListLocations) router.GET("/locations", server.getListLocations)
router.GET("/location/:location_id", server.getLocation) router.GET("/location/:location_id", server.getLocation)
router.GET("/location/tags/:location_id", server.getTagsByLocation) router.GET("/location/tags/:location_id", server.getTagsByLocation)
router.GET("/location/reviews", server.getListLocationReviews)
//IMAGES //IMAGES
router.GET("/images/location", server.getAllImagesByLocation) router.GET("/images/location", server.getAllImagesByLocation)
// REQUIRE AUTH TOKEN
authRoutes := router.Use(authMiddleware(server.TokenMaker))
authRoutes.POST("/review/location", server.createReview)
authRoutes.GET("/user/review/location/:location_id", server.getUserReviewByLocation)
server.Router = router server.Router = router
} }

View File

@ -1,7 +1,12 @@
package api package api
import ( import (
"database/sql"
"fmt"
"net/http"
"time" "time"
"github.com/gin-gonic/gin"
) )
type renewAccessRequest struct { type renewAccessRequest struct {
@ -13,84 +18,78 @@ type renewAccessResponse struct {
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
} }
// func (server *Server) renewAccessToken(ctx *gin.Context) { func (server *Server) renewAccessToken(ctx *gin.Context) {
// var req renewAccessRequest var req renewAccessRequest
// if err := ctx.ShouldBindJSON(&req); err != nil { if err := ctx.ShouldBindJSON(&req); err != nil {
// ctx.JSON(http.StatusBadRequest, errorResponse(err, "")) ctx.JSON(http.StatusBadRequest, ErrorResponse(err, ""))
// return return
// } }
// refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken) refreshPayload, err := server.TokenMaker.VerifyToken(req.RefreshToken)
// if err != nil { if err != nil {
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, "")) ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// return return
// } }
// session, err := server.store.GetSession(ctx, refreshPayload.ID) session, err := server.Store.GetSession(ctx, int32(refreshPayload.UserID))
// if err != nil { if err != nil {
// if err == sql.ErrNoRows { if err == sql.ErrNoRows {
// ctx.JSON(http.StatusNotFound, errorResponse(err, "")) ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
// return return
// } }
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, "")) ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
// return return
// } }
// if session.IsBlocked { if session.IsBlocked {
// err := fmt.Errorf("blocked session") err := fmt.Errorf("blocked session")
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, "")) ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// return return
// } }
// if session.Email != refreshPayload.Email { if session.Username != refreshPayload.Username {
// err := fmt.Errorf("incorrect session user") err := fmt.Errorf("incorrect session user")
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, "")) ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// return return
// } }
// if session.RefreshToken != req.RefreshToken { if session.RefreshToken != req.RefreshToken {
// err := fmt.Errorf("mismatched session token") err := fmt.Errorf("mismatched session token")
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, "")) ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// return return
// } }
// if time.Now().After(refreshPayload.ExpiredAt) { if time.Now().After(refreshPayload.ExpiredAt) {
// err := fmt.Errorf("Expired session") err := fmt.Errorf("expired session")
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, "")) ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
// return return
// } }
// user, err := server.store.GetUserByEmail(ctx, refreshPayload.Email) user, err := server.Store.GetUser(ctx, refreshPayload.Username)
// if err != nil { if err != nil {
// if err == sql.ErrNoRows { if err == sql.ErrNoRows {
// ctx.JSON(http.StatusNotFound, errorResponse(err, "")) ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
// return return
// } }
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, "")) ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
// return return
// } }
// merchant, err := server.store.GetMerchantByUserId(ctx, user.ID) accessToken, accessPayload, err := server.TokenMaker.CreateToken(
// if err != nil { refreshPayload.Username,
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, "")) int(user.ID),
// return server.Config.TokenDuration,
// } )
// accessToken, accessPayload, err := server.tokenMaker.CreateToken( if err != nil {
// refreshPayload.Email, ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
// merchant.ID.String(), return
// server.config.TokenDuration, }
// )
// if err != nil { res := renewAccessResponse{
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, "")) AccesToken: accessToken,
// return AccessTokenExpiresAt: accessPayload.ExpiredAt,
// } }
// res := renewAccessResponse{ ctx.JSON(http.StatusOK, res)
// AccesToken: accessToken, }
// AccessTokenExpiresAt: accessPayload.ExpiredAt,
// }
// ctx.JSON(http.StatusOK, res)
// }

View File

@ -65,6 +65,37 @@ func (server *Server) createUser(ctx *gin.Context) {
return return
} }
accessToken, _, err := server.TokenMaker.CreateToken(
user.Username,
int(user.ID),
server.Config.TokenDuration,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while creating token"))
return
}
// refreshToken, refreshTokenPayload, err := server.TokenMaker.CreateToken(
// user.Username,
// int(user.ID),
// server.Config.RefreshTokenDuration,
// )
_, err = server.Store.CreateSession(ctx, db.CreateSessionParams{
Username: user.Username,
// RefreshToken: refreshToken,
UserAgent: ctx.Request.UserAgent(),
ClientIp: ctx.ClientIP(),
IsBlocked: false,
// ExpiresAt: refreshTokenPayload.ExpiredAt,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while saving sessions"))
return
}
res := createUserResponse{ res := createUserResponse{
ID: user.ID, ID: user.ID,
Username: user.Username, Username: user.Username,
@ -80,5 +111,83 @@ func (server *Server) createUser(ctx *gin.Context) {
UpdatedAt: user.UpdatedAt.Time, UpdatedAt: user.UpdatedAt.Time,
} }
ctx.SetCookie(
"paseto",
accessToken,
int(server.Config.CookieDuration),
"/",
"localhost",
false,
false,
)
ctx.JSON(http.StatusOK, res) ctx.JSON(http.StatusOK, res)
} }
func (server *Server) login(ctx *gin.Context) {
var req createUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
user, err := server.Store.GetUser(ctx, req.Username)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong whlie try to get user"))
return
}
err = util.CheckPassword(req.Password, user.Password)
if err != nil {
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, "Password not match"))
return
}
accessToken, _, err := server.TokenMaker.CreateToken(user.Username, int(user.ID), server.Config.TokenDuration)
if err != nil {
ctx.JSON(http.StatusInternalServerError, "Something went wrong while try to create token")
return
}
_, err = server.Store.CreateSession(ctx, db.CreateSessionParams{
Username: user.Username,
UserAgent: ctx.Request.UserAgent(),
ClientIp: ctx.ClientIP(),
IsBlocked: false,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to create session"))
return
}
ctx.SetCookie(
"paseto",
accessToken,
int(server.Config.CookieDuration),
"/",
"localhost",
false,
false,
)
ctx.JSON(http.StatusOK, user)
}
func (server *Server) logout(ctx *gin.Context) {
ctx.SetCookie(
"paseto",
"",
-1,
"/",
"",
false,
true,
)
ctx.Writer.WriteHeader(http.StatusNoContent)
}

View File

@ -1,16 +1,16 @@
id,submitted_by,is_from_critic,comments,score,is_hided,location_id 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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 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.From 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.One 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

View File

@ -8,11 +8,11 @@ CREATE TABLE users(
"banned_at" timestamp, "banned_at" timestamp,
"banned_until" timestamp, "banned_until" timestamp,
"ban_reason" varchar, "ban_reason" varchar,
"is_permaban" boolean, "is_permaban" boolean default(false),
"is_admin" boolean, "is_admin" boolean default(false),
"is_critics" boolean, "is_critics" boolean default(false),
"is_verified" boolean, "is_verified" boolean default(false),
"is_active" boolean, "is_active" boolean default(true),
"social_media" jsonb, "social_media" jsonb,
"created_at" timestamp default(now()), "created_at" timestamp default(now()),
"updated_at" timestamp default(now()) "updated_at" timestamp default(now())
@ -112,7 +112,7 @@ CREATE TABLE reviews (
"comments" text not null, "comments" text not null,
"score" smallint not null, "score" smallint not null,
"is_from_critic" boolean not null, "is_from_critic" boolean not null,
"is_hided" boolean, -- if comments violate TOS just hide the reviews "is_hided" boolean not null default(false), -- if comments violate TOS just hide the reviews
"location_id" integer references "locations"("id") not null, "location_id" integer references "locations"("id") not null,
"created_at" timestamp default(now()), "created_at" timestamp default(now()),
"updated_at" timestamp default(now()) "updated_at" timestamp default(now())
@ -121,7 +121,8 @@ CREATE TABLE reviews (
CREATE TYPE comment_type AS ENUM( CREATE TYPE comment_type AS ENUM(
'stories', 'stories',
'news', 'news',
'reviews' 'reviews',
'locations'
); );
CREATE TABLE comments( CREATE TABLE comments(

View File

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

View File

@ -0,0 +1,13 @@
CREATE TABLE user_sessions(
"id" serial primary key not null,
"index_id" bigserial not null,
"username" varchar not null references "users"("username") not null,
"refresh_token" varchar not null,
"user_agent" varchar not null,
"client_ip" varchar not null,
"is_blocked" boolean not null default false,
"expires_at" timestamp not null,
"created_at" timestamp default(now())
);
CREATE INDEX ON "user_sessions"("index_id");

View File

@ -35,6 +35,21 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder return m.recorder
} }
// CheckIfReviewExists mocks base method.
func (m *MockStore) CheckIfReviewExists(arg0 context.Context, arg1 db.CheckIfReviewExistsParams) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckIfReviewExists", arg0, arg1)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CheckIfReviewExists indicates an expected call of CheckIfReviewExists.
func (mr *MockStoreMockRecorder) CheckIfReviewExists(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckIfReviewExists", reflect.TypeOf((*MockStore)(nil).CheckIfReviewExists), arg0, arg1)
}
// CreateLocation mocks base method. // CreateLocation mocks base method.
func (m *MockStore) CreateLocation(arg0 context.Context, arg1 db.CreateLocationParams) error { func (m *MockStore) CreateLocation(arg0 context.Context, arg1 db.CreateLocationParams) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -49,6 +64,36 @@ func (mr *MockStoreMockRecorder) CreateLocation(arg0, arg1 interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocation", reflect.TypeOf((*MockStore)(nil).CreateLocation), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocation", reflect.TypeOf((*MockStore)(nil).CreateLocation), arg0, arg1)
} }
// CreateReview mocks base method.
func (m *MockStore) CreateReview(arg0 context.Context, arg1 db.CreateReviewParams) (db.Review, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateReview", arg0, arg1)
ret0, _ := ret[0].(db.Review)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateReview indicates an expected call of CreateReview.
func (mr *MockStoreMockRecorder) CreateReview(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateReview", reflect.TypeOf((*MockStore)(nil).CreateReview), arg0, arg1)
}
// CreateSession mocks base method.
func (m *MockStore) CreateSession(arg0 context.Context, arg1 db.CreateSessionParams) (db.UserSession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSession", arg0, arg1)
ret0, _ := ret[0].(db.UserSession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSession indicates an expected call of CreateSession.
func (mr *MockStoreMockRecorder) CreateSession(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0, arg1)
}
// CreateUser mocks base method. // CreateUser mocks base method.
func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) { func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -94,6 +139,21 @@ func (mr *MockStoreMockRecorder) GetImagesByLocation(arg0, arg1 interface{}) *go
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImagesByLocation", reflect.TypeOf((*MockStore)(nil).GetImagesByLocation), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImagesByLocation", reflect.TypeOf((*MockStore)(nil).GetImagesByLocation), arg0, arg1)
} }
// GetListLocationReviews mocks base method.
func (m *MockStore) GetListLocationReviews(arg0 context.Context, arg1 db.GetListLocationReviewsParams) ([]db.GetListLocationReviewsRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetListLocationReviews", arg0, arg1)
ret0, _ := ret[0].([]db.GetListLocationReviewsRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetListLocationReviews indicates an expected call of GetListLocationReviews.
func (mr *MockStoreMockRecorder) GetListLocationReviews(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocationReviews", reflect.TypeOf((*MockStore)(nil).GetListLocationReviews), arg0, arg1)
}
// GetListLocations mocks base method. // GetListLocations mocks base method.
func (m *MockStore) GetListLocations(arg0 context.Context) ([]db.Location, error) { func (m *MockStore) GetListLocations(arg0 context.Context) ([]db.Location, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -154,6 +214,21 @@ func (mr *MockStoreMockRecorder) GetLocationTag(arg0, arg1 interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationTag", reflect.TypeOf((*MockStore)(nil).GetLocationTag), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationTag", reflect.TypeOf((*MockStore)(nil).GetLocationTag), arg0, arg1)
} }
// GetSession mocks base method.
func (m *MockStore) GetSession(arg0 context.Context, arg1 int32) (db.UserSession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSession", arg0, arg1)
ret0, _ := ret[0].(db.UserSession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSession indicates an expected call of GetSession.
func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1)
}
// GetTopListLocations mocks base method. // GetTopListLocations mocks base method.
func (m *MockStore) GetTopListLocations(arg0 context.Context, arg1 db.GetTopListLocationsParams) ([]db.GetTopListLocationsRow, error) { func (m *MockStore) GetTopListLocations(arg0 context.Context, arg1 db.GetTopListLocationsParams) ([]db.GetTopListLocationsRow, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -169,6 +244,36 @@ func (mr *MockStoreMockRecorder) GetTopListLocations(arg0, arg1 interface{}) *go
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTopListLocations", reflect.TypeOf((*MockStore)(nil).GetTopListLocations), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTopListLocations", reflect.TypeOf((*MockStore)(nil).GetTopListLocations), arg0, arg1)
} }
// GetUser mocks base method.
func (m *MockStore) GetUser(arg0 context.Context, arg1 string) (db.GetUserRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUser", arg0, arg1)
ret0, _ := ret[0].(db.GetUserRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUser indicates an expected call of GetUser.
func (mr *MockStoreMockRecorder) GetUser(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockStore)(nil).GetUser), arg0, arg1)
}
// GetUserReviewByLocation mocks base method.
func (m *MockStore) GetUserReviewByLocation(arg0 context.Context, arg1 db.GetUserReviewByLocationParams) (db.GetUserReviewByLocationRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserReviewByLocation", arg0, arg1)
ret0, _ := ret[0].(db.GetUserReviewByLocationRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserReviewByLocation indicates an expected call of GetUserReviewByLocation.
func (mr *MockStoreMockRecorder) GetUserReviewByLocation(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserReviewByLocation", reflect.TypeOf((*MockStore)(nil).GetUserReviewByLocation), arg0, arg1)
}
// UpdatePassword mocks base method. // UpdatePassword mocks base method.
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error { func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -19,25 +19,6 @@ WHERE approved_by IS NOT NULL
ORDER BY l.created_at ASC ORDER BY l.created_at ASC
LIMIT $1; LIMIT $1;
-- 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;
-- name: CreateLocation :exec -- name: CreateLocation :exec
INSERT INTO locations( INSERT INTO locations(
address, address,

16
db/queries/reviews.sql Normal file
View File

@ -0,0 +1,16 @@
-- name: CheckIfReviewExists :one
SELECT COUNT(1)
FROM reviews
WHERE reviews.location_id = $1 AND reviews.submitted_by = $2;
-- name: GetUserReviewByLocation :one
SELECT
re.id,
re.location_id,
re.score,
re.comments,
re.created_at,
re.updated_at
FROM reviews re
WHERE submitted_by = $1 AND location_id = $2;

16
db/queries/sessions.sql Normal file
View File

@ -0,0 +1,16 @@
-- name: CreateSession :one
INSERT INTO user_sessions (
username,
refresh_token,
user_agent,
client_ip,
is_blocked,
expires_at
) VALUES (
$1, $2, $3, $4, $5, $6
) RETURNING *;
-- name: GetSession :one
SELECT * FROM user_sessions
WHERE id = $1
LIMIT 1;

View File

@ -15,7 +15,6 @@ WHERE
id = sqlc.arg(id) id = sqlc.arg(id)
RETURNING *; RETURNING *;
-- name: UpdatePassword :exec -- name: UpdatePassword :exec
UPDATE users UPDATE users
SET password = $1 SET password = $1

View File

@ -109,3 +109,65 @@ func (q *Queries) GetTopListLocations(ctx context.Context, arg GetTopListLocatio
} }
return items, nil return items, nil
} }
type GetLocationRow struct {
ID int32 `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
RegencyName string `json:"regency_name"`
ProvinceName string `json:"province_name"`
RegionName string `json:"region_name"`
GoogleMapsLink string `json:"google_maps_link"`
Thumbnail sql.NullString `json:"thumbnail"`
SubmittedBy string `json:"submitted_by"`
CriticScore int32 `json:"critic_score"`
CriticCount int32 `json:"critic_count"`
UserScore int32 `json:"user_score"`
UserCount int32 `json:"user_count"`
}
var getLocationQ = `
SELECT
l.id,
name,
l.address,
COALESCE(re.regency_name, '') as regency_name,
COALESCE(prov.province_name, '') as province_name,
COALESCE(reg.region_name, '') as region_name,
COALESCE(l.google_maps_link, '') as google_maps_link,
thumbnail,
u.username as submitted_by,
(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
JOIN users u on u.id = l.submitted_by
WHERE l.id = $1
`
func (q *Queries) GetLocation(ctx context.Context, location_id int32) (GetLocationRow, error) {
row := q.db.QueryRowContext(ctx, getLocationQ, location_id)
var i GetLocationRow
err := row.Scan(
&i.ID,
&i.Name,
&i.Address,
&i.RegencyName,
&i.ProvinceName,
&i.RegionName,
&i.GoogleMapsLink,
&i.Thumbnail,
&i.SubmittedBy,
&i.CriticScore,
&i.CriticCount,
&i.UserScore,
&i.UserCount,
)
return i, err
}

View File

@ -146,57 +146,6 @@ func (q *Queries) GetListRecentLocationsWithRatings(ctx context.Context, limit i
return items, nil 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 GetLocationRow
err := row.Scan(
&i.ID,
&i.Name,
&i.Address,
&i.GoogleMapsLink,
&i.Thumbnail,
&i.SubmittedBy,
&i.RegencyName,
&i.ProvinceName,
&i.RegionName,
&i.SubmittedByUser,
)
return i, err
}
const getLocationTag = `-- name: GetLocationTag :many const getLocationTag = `-- name: GetLocationTag :many
SELECT SELECT
name name

View File

@ -16,9 +16,10 @@ import (
type CommentType string type CommentType string
const ( const (
CommentTypeStories CommentType = "stories" CommentTypeStories CommentType = "stories"
CommentTypeNews CommentType = "news" CommentTypeNews CommentType = "news"
CommentTypeReviews CommentType = "reviews" CommentTypeReviews CommentType = "reviews"
CommentTypeLocations CommentType = "locations"
) )
func (e *CommentType) Scan(src interface{}) error { func (e *CommentType) Scan(src interface{}) error {
@ -188,7 +189,7 @@ type Review struct {
Comments string `json:"comments"` Comments string `json:"comments"`
Score int16 `json:"score"` Score int16 `json:"score"`
IsFromCritic bool `json:"is_from_critic"` IsFromCritic bool `json:"is_from_critic"`
IsHided sql.NullBool `json:"is_hided"` IsHided bool `json:"is_hided"`
LocationID int32 `json:"location_id"` LocationID int32 `json:"location_id"`
CreatedAt sql.NullTime `json:"created_at"` CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt sql.NullTime `json:"updated_at"`
@ -244,3 +245,15 @@ type UserReport struct {
CreatedAt sql.NullTime `json:"created_at"` CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt sql.NullTime `json:"updated_at"`
} }
type UserSession struct {
ID int32 `json:"id"`
IndexID int64 `json:"index_id"`
Username string `json:"username"`
RefreshToken string `json:"refresh_token"`
UserAgent string `json:"user_agent"`
ClientIp string `json:"client_ip"`
IsBlocked bool `json:"is_blocked"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt sql.NullTime `json:"created_at"`
}

View File

@ -9,13 +9,16 @@ import (
) )
type Querier interface { type Querier interface {
CheckIfReviewExists(ctx context.Context, arg CheckIfReviewExistsParams) (int64, error)
CreateLocation(ctx context.Context, arg CreateLocationParams) error CreateLocation(ctx context.Context, arg CreateLocationParams) error
CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error)
CreateUser(ctx context.Context, arg CreateUserParams) (User, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
GetCountImageByLocation(ctx context.Context, imageOf int32) (int64, error) GetCountImageByLocation(ctx context.Context, imageOf int32) (int64, error)
GetListLocations(ctx context.Context) ([]Location, error) GetListLocations(ctx context.Context) ([]Location, error)
GetListRecentLocationsWithRatings(ctx context.Context, limit int32) ([]GetListRecentLocationsWithRatingsRow, 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) GetLocationTag(ctx context.Context, targetID int32) ([]string, error)
GetSession(ctx context.Context, id int32) (UserSession, error)
GetUserReviewByLocation(ctx context.Context, arg GetUserReviewByLocationParams) (GetUserReviewByLocationRow, error)
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
} }

125
db/sqlc/reviews.go Normal file
View File

@ -0,0 +1,125 @@
package db
import (
"context"
"database/sql"
"time"
)
const createReview = `-- name: CreateReview :one
INSERT INTO reviews (
submitted_by,
comments,
score,
is_from_critic,
is_hided,
location_id
) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, submitted_by, comments, score, is_from_critic, is_hided, location_id, created_at, updated_at
`
type CreateReviewParams struct {
SubmittedBy int32 `json:"submitted_by"`
Comments string `json:"comments"`
Score int16 `json:"score"`
IsFromCritic bool `json:"is_from_critic"`
IsHided bool `json:"is_hided"`
LocationID int32 `json:"location_id"`
}
func (q *Queries) CreateReview(ctx context.Context, arg CreateReviewParams) (Review, error) {
row := q.db.QueryRowContext(ctx, createReview,
arg.SubmittedBy,
arg.Comments,
arg.Score,
arg.IsFromCritic,
arg.IsHided,
arg.LocationID,
)
var i Review
err := row.Scan(
&i.ID,
&i.SubmittedBy,
&i.Comments,
&i.Score,
&i.IsFromCritic,
&i.IsHided,
&i.LocationID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
type GetListLocationReviewsParams struct {
LocationID int32 `json:"location_id"`
Limit int8 `json:"limit"`
Offset int8 `json:"offset"`
IsCritics bool `json:"is_critics"`
}
type GetListLocationReviewsRow struct {
ID int32 `json:"id"`
Score int8 `json:"score"`
Comment string `json:"comments"`
UserID int32 `json:"user_id"`
Username string `json:"username"`
UserAvatar sql.NullString `json:"user_avatar"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
const getListLocationReviews = `
SELECT
re.id as id,
re.score as score,
re.comments as comments,
u.id as user_id,
u.username as username,
u.avatar_picture as user_avatar,
re.created_at as created_at,
re.updated_at as updated_at
FROM REVIEWS re
JOIN users u on re.submitted_by = u.id
WHERE re.location_id = $1 AND re.is_from_critic = $2
LIMIT $3
OFFSET $4;
`
func (q *Queries) GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error) {
rows, err := q.db.QueryContext(ctx, getListLocationReviews, arg.LocationID, arg.IsCritics, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetListLocationReviewsRow{}
for rows.Next() {
var i GetListLocationReviewsRow
if err := rows.Scan(
&i.ID,
&i.Score,
&i.Comment,
&i.UserID,
&i.Username,
&i.UserAvatar,
&i.CreatedAt,
&i.UpdatedAt,
); 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
}

69
db/sqlc/reviews.sql.go Normal file
View File

@ -0,0 +1,69 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: reviews.sql
package db
import (
"context"
"database/sql"
)
const checkIfReviewExists = `-- name: CheckIfReviewExists :one
SELECT COUNT(1)
FROM reviews
WHERE reviews.location_id = $1 AND reviews.submitted_by = $2
`
type CheckIfReviewExistsParams struct {
LocationID int32 `json:"location_id"`
SubmittedBy int32 `json:"submitted_by"`
}
func (q *Queries) CheckIfReviewExists(ctx context.Context, arg CheckIfReviewExistsParams) (int64, error) {
row := q.db.QueryRowContext(ctx, checkIfReviewExists, arg.LocationID, arg.SubmittedBy)
var count int64
err := row.Scan(&count)
return count, err
}
const getUserReviewByLocation = `-- name: GetUserReviewByLocation :one
SELECT
re.id,
re.location_id,
re.score,
re.comments,
re.created_at,
re.updated_at
FROM reviews re
WHERE submitted_by = $1 AND location_id = $2
`
type GetUserReviewByLocationParams struct {
SubmittedBy int32 `json:"submitted_by"`
LocationID int32 `json:"location_id"`
}
type GetUserReviewByLocationRow struct {
ID int32 `json:"id"`
LocationID int32 `json:"location_id"`
Score int16 `json:"score"`
Comments string `json:"comments"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
func (q *Queries) GetUserReviewByLocation(ctx context.Context, arg GetUserReviewByLocationParams) (GetUserReviewByLocationRow, error) {
row := q.db.QueryRowContext(ctx, getUserReviewByLocation, arg.SubmittedBy, arg.LocationID)
var i GetUserReviewByLocationRow
err := row.Scan(
&i.ID,
&i.LocationID,
&i.Score,
&i.Comments,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

80
db/sqlc/sessions.sql.go Normal file
View File

@ -0,0 +1,80 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: sessions.sql
package db
import (
"context"
"time"
)
const createSession = `-- name: CreateSession :one
INSERT INTO user_sessions (
username,
refresh_token,
user_agent,
client_ip,
is_blocked,
expires_at
) VALUES (
$1, $2, $3, $4, $5, $6
) RETURNING id, index_id, username, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at
`
type CreateSessionParams struct {
Username string `json:"username"`
RefreshToken string `json:"refresh_token"`
UserAgent string `json:"user_agent"`
ClientIp string `json:"client_ip"`
IsBlocked bool `json:"is_blocked"`
ExpiresAt time.Time `json:"expires_at"`
}
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error) {
row := q.db.QueryRowContext(ctx, createSession,
arg.Username,
arg.RefreshToken,
arg.UserAgent,
arg.ClientIp,
arg.IsBlocked,
arg.ExpiresAt,
)
var i UserSession
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Username,
&i.RefreshToken,
&i.UserAgent,
&i.ClientIp,
&i.IsBlocked,
&i.ExpiresAt,
&i.CreatedAt,
)
return i, err
}
const getSession = `-- name: GetSession :one
SELECT id, index_id, username, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at FROM user_sessions
WHERE id = $1
LIMIT 1
`
func (q *Queries) GetSession(ctx context.Context, id int32) (UserSession, error) {
row := q.db.QueryRowContext(ctx, getSession, id)
var i UserSession
err := row.Scan(
&i.ID,
&i.IndexID,
&i.Username,
&i.RefreshToken,
&i.UserAgent,
&i.ClientIp,
&i.IsBlocked,
&i.ExpiresAt,
&i.CreatedAt,
)
return i, err
}

View File

@ -11,6 +11,10 @@ type Store interface {
Querier Querier
GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, error) GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, error)
GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error) GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error)
GetLocation(ctx context.Context, location_id int32) (GetLocationRow, error)
GetUser(ctx context.Context, username string) (GetUserRow, error)
CreateReview(ctx context.Context, arg CreateReviewParams) (Review, error)
GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error)
} }
type SQLStore struct { type SQLStore struct {

64
db/sqlc/users.go Normal file
View File

@ -0,0 +1,64 @@
package db
import (
"context"
"database/sql"
"github.com/sqlc-dev/pqtype"
)
const getUser = `-- name: GetUser :one
SELECT
id,
COALESCE(email, '') as email,
password,
username,
COALESCE(avatar_picture, '') as avatar_picture,
banned_at,
banned_until,
COALESCE(ban_reason, '') as ban_reason,
is_permaban,
is_admin,
is_critics,
is_verified,
social_media
FROM USERS
WHERE username = $1
`
type GetUserRow struct {
ID int32 `json:"id"`
Email string `json:"email"`
Password string `json:"-"`
Username string `json:"username"`
AvatarPicture string `json:"avatar_picture"`
BannedAt sql.NullTime `json:"banned_at"`
BannedUntil sql.NullTime `json:"banned_until"`
BanReason string `json:"ban_reason"`
IsPermaban bool `json:"is_permaban"`
IsAdmin bool `json:"is_admin"`
IsCritics bool `json:"is_critics"`
IsVerified bool `json:"is_verified"`
SocialMedia pqtype.NullRawMessage `json:"social_media"`
}
func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, error) {
row := q.db.QueryRowContext(ctx, getUser, username)
var i GetUserRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Password,
&i.Username,
&i.AvatarPicture,
&i.BannedAt,
&i.BannedUntil,
&i.BanReason,
&i.IsPermaban,
&i.IsAdmin,
&i.IsCritics,
&i.IsVerified,
&i.SocialMedia,
)
return i, err
}

View File

@ -10,4 +10,5 @@ SERVER_ADDRESS = 0.0.0.0:8888
TOKEN_SYMMETRIC_KEY= TOKEN_SYMMETRIC_KEY=
TOKEN_DURATION = 1024h TOKEN_DURATION = 1024h
COOKIE_DURATION = # in seconds
REFRESH_TOKEN_DURATION = 1024h REFRESH_TOKEN_DURATION = 1024h

1
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/cors v1.4.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect

28
go.sum
View File

@ -62,6 +62,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -75,19 +76,27 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -166,14 +175,19 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@ -186,8 +200,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0= github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=
github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU= github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -198,6 +214,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/ramya-rao-a/go-outline v0.0.0-20210608161538-9736a4bde949 h1:iaD+iVf9xGfajsJp+zYrg9Lrk6gMJ6/hZHO4cYq5D5o= github.com/ramya-rao-a/go-outline v0.0.0-20210608161538-9736a4bde949 h1:iaD+iVf9xGfajsJp+zYrg9Lrk6gMJ6/hZHO4cYq5D5o=
github.com/ramya-rao-a/go-outline v0.0.0-20210608161538-9736a4bde949/go.mod h1:9V3eNbj9Z53yO7cKB6cSX9f0O7rYdIiuGBhjA1YsQuw= github.com/ramya-rao-a/go-outline v0.0.0-20210608161538-9736a4bde949/go.mod h1:9V3eNbj9Z53yO7cKB6cSX9f0O7rYdIiuGBhjA1YsQuw=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
@ -218,6 +236,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -229,6 +248,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yiplee/nap v1.0.1 h1:5p8KAIkYy+PIMGSk+ScF13Hh/OFkIEBHPuD14OFvStg= github.com/yiplee/nap v1.0.1 h1:5p8KAIkYy+PIMGSk+ScF13Hh/OFkIEBHPuD14OFvStg=
@ -258,6 +279,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
@ -395,6 +417,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -557,15 +581,19 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -24,6 +24,10 @@ func main() {
log.Fatal("cannot connect to db: ", err) log.Fatal("cannot connect to db: ", err)
} }
// limit db connection
dbConn.SetConnMaxLifetime(100)
dbConn.SetMaxOpenConns(100)
store := db.NewStore(dbConn) store := db.NewStore(dbConn)
server, err := api.NewServer(config, store) server, err := api.NewServer(config, store)
if err != nil { if err != nil {

27
notes
View File

@ -78,3 +78,30 @@ AFTER I FINISHED THE WHOLE THING IMMA TRY TO COMPARE IF EACH LOCATIONS HAVE user
user_counts etc etc user_counts etc etc
########################################################################################## ##########################################################################################
##########################################################################################
REWRITE COALESCE QUERY CAUSE IT'S SLOW
- https://stackoverflow.com/questions/6426436/postgresql-coalesce-performance-problem
- https://stackoverflow.com/questions/2287642/which-is-quicker-coalesce-or-isnull
##########################################################################################
##########################################################################################
USE THIS FOR NITFICOITON
https://docs.ntfy.sh/install/#docker
##########################################################################################
##########################################################################################
PAGINATION
http://andreyzavadskiy.com/2016/12/03/pagination-and-total-number-of-rows-from-one-select/
##########################################################################################

View File

@ -13,6 +13,7 @@ type Config struct {
ServerAddress string `mapstructure:"SERVER_ADDRESS"` ServerAddress string `mapstructure:"SERVER_ADDRESS"`
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"` TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"` TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
CookieDuration int32 `mapstructure:"COOKIE_DURATION"`
RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"` RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
} }

View File

@ -3,6 +3,6 @@ package token
import "time" import "time"
type Maker interface { type Maker interface {
CreateToken(email string, userID int, duration time.Duration) (string, *Payload, error) CreateToken(username string, userID int, duration time.Duration) (string, *Payload, error)
VerifyToken(token string) (*Payload, error) VerifyToken(token string) (*Payload, error)
} }

View File

@ -26,8 +26,8 @@ func NewPasetoMaker(symmetricKey string) (Maker, error) {
return maker, nil return maker, nil
} }
func (maker *PasetoMaker) CreateToken(email string, UserID int, duration time.Duration) (string, *Payload, error) { func (maker *PasetoMaker) CreateToken(Username string, UserID int, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(email, UserID, duration) payload, err := NewPayload(Username, UserID, duration)
if err != nil { if err != nil {
return "", payload, err return "", payload, err
} }

View File

@ -31,7 +31,7 @@ func TestPasetoMaker(t *testing.T) {
require.NotEmpty(t, payload) require.NotEmpty(t, payload)
// require.NotZero(t, payload.ID) // require.NotZero(t, payload.ID)
require.Equal(t, email, payload.Email) require.Equal(t, email, payload.Username)
require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second) require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second)
require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second) require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second)
} }

View File

@ -11,15 +11,15 @@ var (
) )
type Payload struct { type Payload struct {
Email string `json:"email"` Username string `json:"email"`
UserID int `json:"user_id"` UserID int `json:"user_id"`
IssuedAt time.Time `json:"issued_at"` IssuedAt time.Time `json:"issued_at"`
ExpiredAt time.Time `json:"expired_at"` ExpiredAt time.Time `json:"expired_at"`
} }
func NewPayload(email string, user_id int, duration time.Duration) (*Payload, error) { func NewPayload(username string, user_id int, duration time.Duration) (*Payload, error) {
payload := &Payload{ payload := &Payload{
Email: email, Username: username,
UserID: user_id, UserID: user_id,
IssuedAt: time.Now(), IssuedAt: time.Now(),
ExpiredAt: time.Now().Add(duration), ExpiredAt: time.Now().Add(duration),