Compare commits
11 Commits
bee8f6e5b8
...
675abcdb18
Author | SHA1 | Date | |
---|---|---|---|
675abcdb18 | |||
8a677a2130 | |||
4e5bcddb4b | |||
e33d243810 | |||
7100a3618f | |||
3f18f0e1c2 | |||
89ea569f36 | |||
5368e934b6 | |||
cbf7a41c80 | |||
4564085075 | |||
bcb242c0aa |
7
Makefile
7
Makefile
@ -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}_test?sslmode=disable" -verbose down $n
|
||||
|
||||
seed:
|
||||
./import_csv.sh
|
||||
|
||||
migraterestart:
|
||||
$(MAKE) migratedown
|
||||
$(MAKE) migrateup
|
||||
|
||||
seed:
|
||||
./import_csv.sh
|
||||
$(MAKE) seed
|
||||
|
||||
mock-generate:
|
||||
mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/hiling_go/db/sqlc Store
|
||||
|
3
TODO
Normal file
3
TODO
Normal file
@ -0,0 +1,3 @@
|
||||
- COMMENTS FLOW
|
||||
- USER PROFILE FLOW
|
||||
- BEST LOCATIONS FILTER ( MIN REVIEWS AND TAGS)
|
109
api/location.go
109
api/location.go
@ -149,6 +149,17 @@ func (server *Server) getTopListLocations(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -198,10 +209,104 @@ func (server *Server) getLocation(ctx *gin.Context) {
|
||||
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{
|
||||
"tags": tags,
|
||||
"detail": location,
|
||||
"tags": tags,
|
||||
"detail": location,
|
||||
"users_review": users_reviews,
|
||||
"critics_review": critics_reviews,
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,50 @@
|
||||
package api
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
"git.nochill.in/nochill/hiling_go/util/token"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
// func CORSMiddleware() gin.HandlerFunc {
|
||||
// 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
|
||||
}
|
||||
|
||||
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
96
api/reviews.go
Normal 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)
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||
"git.nochill.in/nochill/hiling_go/util"
|
||||
"git.nochill.in/nochill/hiling_go/util/token"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -35,9 +36,18 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
|
||||
func (server *Server) getRoutes() {
|
||||
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/login", server.login)
|
||||
router.POST("/user/logout", server.logout)
|
||||
|
||||
// LOCATION
|
||||
router.POST("/locations", server.createLocation)
|
||||
@ -46,10 +56,16 @@ func (server *Server) getRoutes() {
|
||||
router.GET("/locations", server.getListLocations)
|
||||
router.GET("/location/:location_id", server.getLocation)
|
||||
router.GET("/location/tags/:location_id", server.getTagsByLocation)
|
||||
router.GET("/location/reviews", server.getListLocationReviews)
|
||||
|
||||
//IMAGES
|
||||
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
|
||||
}
|
||||
|
||||
|
139
api/token.go
139
api/token.go
@ -1,7 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type renewAccessRequest struct {
|
||||
@ -13,84 +18,78 @@ type renewAccessResponse struct {
|
||||
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
|
||||
}
|
||||
|
||||
// func (server *Server) renewAccessToken(ctx *gin.Context) {
|
||||
// var req renewAccessRequest
|
||||
// if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
// ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
func (server *Server) renewAccessToken(ctx *gin.Context) {
|
||||
var req renewAccessRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken)
|
||||
// if err != nil {
|
||||
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
refreshPayload, err := server.TokenMaker.VerifyToken(req.RefreshToken)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// session, err := server.store.GetSession(ctx, refreshPayload.ID)
|
||||
// if err != nil {
|
||||
// if err == sql.ErrNoRows {
|
||||
// ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
session, err := server.Store.GetSession(ctx, int32(refreshPayload.UserID))
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// if session.IsBlocked {
|
||||
// err := fmt.Errorf("blocked session")
|
||||
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
if session.IsBlocked {
|
||||
err := fmt.Errorf("blocked session")
|
||||
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// if session.Email != refreshPayload.Email {
|
||||
// err := fmt.Errorf("incorrect session user")
|
||||
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
if session.Username != refreshPayload.Username {
|
||||
err := fmt.Errorf("incorrect session user")
|
||||
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// if session.RefreshToken != req.RefreshToken {
|
||||
// err := fmt.Errorf("mismatched session token")
|
||||
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
if session.RefreshToken != req.RefreshToken {
|
||||
err := fmt.Errorf("mismatched session token")
|
||||
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// if time.Now().After(refreshPayload.ExpiredAt) {
|
||||
// err := fmt.Errorf("Expired session")
|
||||
// ctx.JSON(http.StatusUnauthorized, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
if time.Now().After(refreshPayload.ExpiredAt) {
|
||||
err := fmt.Errorf("expired session")
|
||||
ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// user, err := server.store.GetUserByEmail(ctx, refreshPayload.Email)
|
||||
// if err != nil {
|
||||
// if err == sql.ErrNoRows {
|
||||
// ctx.JSON(http.StatusNotFound, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
user, err := server.Store.GetUser(ctx, refreshPayload.Username)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// merchant, err := server.store.GetMerchantByUserId(ctx, user.ID)
|
||||
// if err != nil {
|
||||
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
accessToken, accessPayload, err := server.TokenMaker.CreateToken(
|
||||
refreshPayload.Username,
|
||||
int(user.ID),
|
||||
server.Config.TokenDuration,
|
||||
)
|
||||
|
||||
// accessToken, accessPayload, err := server.tokenMaker.CreateToken(
|
||||
// refreshPayload.Email,
|
||||
// merchant.ID.String(),
|
||||
// server.config.TokenDuration,
|
||||
// )
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
// if err != nil {
|
||||
// ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
||||
// return
|
||||
// }
|
||||
res := renewAccessResponse{
|
||||
AccesToken: accessToken,
|
||||
AccessTokenExpiresAt: accessPayload.ExpiredAt,
|
||||
}
|
||||
|
||||
// res := renewAccessResponse{
|
||||
// AccesToken: accessToken,
|
||||
// AccessTokenExpiresAt: accessPayload.ExpiredAt,
|
||||
// }
|
||||
|
||||
// ctx.JSON(http.StatusOK, res)
|
||||
// }
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
109
api/user.go
109
api/user.go
@ -65,6 +65,37 @@ func (server *Server) createUser(ctx *gin.Context) {
|
||||
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{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
@ -80,5 +111,83 @@ func (server *Server) createUser(ctx *gin.Context) {
|
||||
UpdatedAt: user.UpdatedAt.Time,
|
||||
}
|
||||
|
||||
ctx.SetCookie(
|
||||
"paseto",
|
||||
accessToken,
|
||||
int(server.Config.CookieDuration),
|
||||
"/",
|
||||
"localhost",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
id,submitted_by,is_from_critic,comments,score,is_hided,location_id
|
||||
1#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
2#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
3#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
4#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#2
|
||||
5#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#3
|
||||
6#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#9
|
||||
7#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
8#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#9
|
||||
9#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#4
|
||||
10#3#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#4
|
||||
11#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#2
|
||||
12#2#true#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#1
|
||||
13#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
14#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
15#1#false#I recently had the opportunity to visit a breathtaking beach that left me in awe. Nestled between rolling waves and golden sands, this hidden gem is a true paradise for beach lovers and nature enthusiasts alike.\nFrom the moment I set foot on the powdery shoreline, I was captivated by the crystal-clear azure waters that stretched as far as the eye could see. The gentle caress of the ocean breeze and the soothing sound of waves crashing created an instant sense of relaxation.\nOne of the most remarkable features of this beach is its untouched beauty. The absence of crowds and commercialization allowed for an authentic and serene experience. I was able to walk along the shoreline, feeling the soft sand beneath my feet, and listen to the symphony of seagulls above. It was a refreshing escape from the hustle and bustle of daily life.#8#false#8
|
||||
1#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.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.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.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.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.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.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.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.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.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.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.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.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.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.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,11 +8,11 @@ CREATE TABLE users(
|
||||
"banned_at" timestamp,
|
||||
"banned_until" timestamp,
|
||||
"ban_reason" varchar,
|
||||
"is_permaban" boolean,
|
||||
"is_admin" boolean,
|
||||
"is_critics" boolean,
|
||||
"is_verified" boolean,
|
||||
"is_active" boolean,
|
||||
"is_permaban" boolean default(false),
|
||||
"is_admin" boolean default(false),
|
||||
"is_critics" boolean default(false),
|
||||
"is_verified" boolean default(false),
|
||||
"is_active" boolean default(true),
|
||||
"social_media" jsonb,
|
||||
"created_at" timestamp default(now()),
|
||||
"updated_at" timestamp default(now())
|
||||
@ -112,7 +112,7 @@ CREATE TABLE reviews (
|
||||
"comments" text not null,
|
||||
"score" smallint not null,
|
||||
"is_from_critic" boolean not null,
|
||||
"is_hided" boolean, -- if comments violate TOS just hide the reviews
|
||||
"is_hided" boolean not null default(false), -- if comments violate TOS just hide the reviews
|
||||
"location_id" integer references "locations"("id") not null,
|
||||
"created_at" timestamp default(now()),
|
||||
"updated_at" timestamp default(now())
|
||||
@ -121,7 +121,8 @@ CREATE TABLE reviews (
|
||||
CREATE TYPE comment_type AS ENUM(
|
||||
'stories',
|
||||
'news',
|
||||
'reviews'
|
||||
'reviews',
|
||||
'locations'
|
||||
);
|
||||
|
||||
CREATE TABLE comments(
|
||||
|
1
db/migrations/000006_create_user_sessions_table.down.sql
Normal file
1
db/migrations/000006_create_user_sessions_table.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS user_sessions;
|
13
db/migrations/000006_create_user_sessions_table.up.sql
Normal file
13
db/migrations/000006_create_user_sessions_table.up.sql
Normal 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");
|
105
db/mock/store.go
105
db/mock/store.go
@ -35,6 +35,21 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
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.
|
||||
func (m *MockStore) CreateLocation(arg0 context.Context, arg1 db.CreateLocationParams) error {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *MockStore) GetListLocations(arg0 context.Context) ([]db.Location, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *MockStore) GetTopListLocations(arg0 context.Context, arg1 db.GetTopListLocationsParams) ([]db.GetTopListLocationsRow, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -19,25 +19,6 @@ WHERE approved_by IS NOT NULL
|
||||
ORDER BY l.created_at ASC
|
||||
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
|
||||
INSERT INTO locations(
|
||||
address,
|
||||
|
16
db/queries/reviews.sql
Normal file
16
db/queries/reviews.sql
Normal 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
16
db/queries/sessions.sql
Normal 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;
|
@ -15,7 +15,6 @@ WHERE
|
||||
id = sqlc.arg(id)
|
||||
RETURNING *;
|
||||
|
||||
|
||||
-- name: UpdatePassword :exec
|
||||
UPDATE users
|
||||
SET password = $1
|
||||
|
@ -109,3 +109,65 @@ func (q *Queries) GetTopListLocations(ctx context.Context, arg GetTopListLocatio
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -146,57 +146,6 @@ func (q *Queries) GetListRecentLocationsWithRatings(ctx context.Context, limit i
|
||||
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
|
||||
SELECT
|
||||
name
|
||||
|
@ -16,9 +16,10 @@ import (
|
||||
type CommentType string
|
||||
|
||||
const (
|
||||
CommentTypeStories CommentType = "stories"
|
||||
CommentTypeNews CommentType = "news"
|
||||
CommentTypeReviews CommentType = "reviews"
|
||||
CommentTypeStories CommentType = "stories"
|
||||
CommentTypeNews CommentType = "news"
|
||||
CommentTypeReviews CommentType = "reviews"
|
||||
CommentTypeLocations CommentType = "locations"
|
||||
)
|
||||
|
||||
func (e *CommentType) Scan(src interface{}) error {
|
||||
@ -188,7 +189,7 @@ type Review struct {
|
||||
Comments string `json:"comments"`
|
||||
Score int16 `json:"score"`
|
||||
IsFromCritic bool `json:"is_from_critic"`
|
||||
IsHided sql.NullBool `json:"is_hided"`
|
||||
IsHided bool `json:"is_hided"`
|
||||
LocationID int32 `json:"location_id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
@ -244,3 +245,15 @@ type UserReport struct {
|
||||
CreatedAt sql.NullTime `json:"created_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"`
|
||||
}
|
||||
|
@ -9,13 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
CheckIfReviewExists(ctx context.Context, arg CheckIfReviewExistsParams) (int64, error)
|
||||
CreateLocation(ctx context.Context, arg CreateLocationParams) error
|
||||
CreateSession(ctx context.Context, arg CreateSessionParams) (UserSession, error)
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
GetCountImageByLocation(ctx context.Context, imageOf int32) (int64, error)
|
||||
GetListLocations(ctx context.Context) ([]Location, error)
|
||||
GetListRecentLocationsWithRatings(ctx context.Context, limit int32) ([]GetListRecentLocationsWithRatingsRow, error)
|
||||
GetLocation(ctx context.Context, id int32) (GetLocationRow, error)
|
||||
GetLocationTag(ctx context.Context, targetID int32) ([]string, error)
|
||||
GetSession(ctx context.Context, id int32) (UserSession, error)
|
||||
GetUserReviewByLocation(ctx context.Context, arg GetUserReviewByLocationParams) (GetUserReviewByLocationRow, error)
|
||||
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
||||
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
|
||||
}
|
||||
|
125
db/sqlc/reviews.go
Normal file
125
db/sqlc/reviews.go
Normal 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
69
db/sqlc/reviews.sql.go
Normal 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
80
db/sqlc/sessions.sql.go
Normal 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
|
||||
}
|
@ -11,6 +11,10 @@ type Store interface {
|
||||
Querier
|
||||
GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, 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 {
|
||||
|
64
db/sqlc/users.go
Normal file
64
db/sqlc/users.go
Normal 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
|
||||
}
|
@ -10,4 +10,5 @@ SERVER_ADDRESS = 0.0.0.0:8888
|
||||
|
||||
TOKEN_SYMMETRIC_KEY=
|
||||
TOKEN_DURATION = 1024h
|
||||
COOKIE_DURATION = # in seconds
|
||||
REFRESH_TOKEN_DURATION = 1024h
|
1
go.mod
1
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // 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-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
|
28
go.sum
28
go.sum
@ -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-20200629203442-efcf912fb354/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
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/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/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/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-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/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/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/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/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/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.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/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/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
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/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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=
|
||||
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/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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/go.mod h1:9V3eNbj9Z53yO7cKB6cSX9f0O7rYdIiuGBhjA1YsQuw=
|
||||
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/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
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.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.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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/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/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/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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-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-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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
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-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-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.5.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.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.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/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 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
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.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-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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
4
main.go
4
main.go
@ -24,6 +24,10 @@ func main() {
|
||||
log.Fatal("cannot connect to db: ", err)
|
||||
}
|
||||
|
||||
// limit db connection
|
||||
dbConn.SetConnMaxLifetime(100)
|
||||
dbConn.SetMaxOpenConns(100)
|
||||
|
||||
store := db.NewStore(dbConn)
|
||||
server, err := api.NewServer(config, store)
|
||||
if err != nil {
|
||||
|
27
notes
27
notes
@ -77,4 +77,31 @@ CALCULATE ALL THE RATINGS SHIT,
|
||||
AFTER I FINISHED THE WHOLE THING IMMA TRY TO COMPARE IF EACH LOCATIONS HAVE user_ratings
|
||||
user_counts etc etc
|
||||
|
||||
##########################################################################################
|
||||
|
||||
|
||||
##########################################################################################
|
||||
|
||||
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/
|
||||
|
||||
##########################################################################################
|
@ -13,6 +13,7 @@ type Config struct {
|
||||
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
|
||||
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
|
||||
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
|
||||
CookieDuration int32 `mapstructure:"COOKIE_DURATION"`
|
||||
RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,6 @@ package token
|
||||
import "time"
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ func NewPasetoMaker(symmetricKey string) (Maker, error) {
|
||||
return maker, nil
|
||||
}
|
||||
|
||||
func (maker *PasetoMaker) CreateToken(email string, UserID int, duration time.Duration) (string, *Payload, error) {
|
||||
payload, err := NewPayload(email, UserID, duration)
|
||||
func (maker *PasetoMaker) CreateToken(Username string, UserID int, duration time.Duration) (string, *Payload, error) {
|
||||
payload, err := NewPayload(Username, UserID, duration)
|
||||
if err != nil {
|
||||
return "", payload, err
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func TestPasetoMaker(t *testing.T) {
|
||||
require.NotEmpty(t, payload)
|
||||
|
||||
// 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, expiredAt, payload.ExpiredAt, time.Second)
|
||||
}
|
||||
|
@ -11,15 +11,15 @@ var (
|
||||
)
|
||||
|
||||
type Payload struct {
|
||||
Email string `json:"email"`
|
||||
Username string `json:"email"`
|
||||
UserID int `json:"user_id"`
|
||||
IssuedAt time.Time `json:"issued_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{
|
||||
Email: email,
|
||||
Username: username,
|
||||
UserID: user_id,
|
||||
IssuedAt: time.Now(),
|
||||
ExpiredAt: time.Now().Add(duration),
|
||||
|
Loading…
Reference in New Issue
Block a user