Compare commits

...

14 Commits

Author SHA1 Message Date
2f05a2f8e7 add news events 2023-10-11 16:31:52 +07:00
f8ecd20220 add update users and refactor 2023-10-09 16:25:39 +07:00
eb9774cce5 migration for users table 2023-10-09 16:25:08 +07:00
b31bc76a24 add user profile stats 2023-10-04 19:51:29 +07:00
a2d4dfb282 update todo 2023-10-03 19:44:40 +07:00
8dbebd9f17 updaet add location into trasnaction 2023-10-03 19:44:31 +07:00
641831a3ae add follow flow 2023-10-03 16:14:58 +07:00
5a5d157882 adjust with multiple image locations 2023-10-03 14:56:57 +07:00
110cbfeec0 fix schema and csv imports 2023-10-03 14:56:08 +07:00
47e692bf30 add new TODO 2023-10-03 14:55:42 +07:00
6cd35d7ffa add region, regency, province 2023-10-02 08:49:41 +07:00
ddca119e45 update location data type not null 2023-10-02 08:49:19 +07:00
198e311407 add location type 2023-09-29 14:45:25 +07:00
1f055e92d4 add error message user not found 2023-09-27 22:34:52 +07:00
47 changed files with 1434 additions and 242 deletions

10
TODO
View File

@ -1,3 +1,9 @@
- COMMENTS FLOW
- USER PROFILE FLOW
- BEST LOCATIONS FILTER ( MIN REVIEWS AND TAGS)
- ADMIN FLOW (SUBMISSION, USER REPORTS)
- BEST LOCATIONS FILTER ( MIN REVIEWS AND TAGS)
- PAGINATION
- OPEN GRAPH PARSER
- USERS FEEED
## IF THERES TIME LEFT
- STORIES FLOW

6
api/api.go Normal file
View File

@ -0,0 +1,6 @@
package api
type BaseGetListRequest struct {
Page int32 `form:"page" binding:"required,min=1"`
PageSize int32 `form:"page_size" binding:"required,min=5"`
}

View File

@ -11,7 +11,6 @@ import (
db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util"
"github.com/gin-gonic/gin"
"github.com/lib/pq"
ysqlc "github.com/yiplee/sqlc"
)
@ -20,64 +19,60 @@ type createLocationReq struct {
Name string `form:"name" binding:"required"`
SubmittedBy int32 `form:"submitted_by" binding:"required,number"`
RegencyID int16 `form:"regency_id" binding:"required,number"`
LocationType string `form:"location_type" binding:"required"`
GoogleMapsLink string `form:"google_maps_link"`
}
func (server *Server) createLocation(ctx *gin.Context) {
var req createLocationReq
var imgPath string
var thumbnail, _ = ctx.FormFile("thumbnail")
var tempImg []db.CreateImageParams
if err := ctx.Bind(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
if thumbnail != nil {
img := thumbnail
fileExt := filepath.Ext(img.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/locations/%s/thumbnail", req.Name)
osFilename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imgPath = fmt.Sprintf("%s%s", dir, osFilename)
form, _ := ctx.MultipartForm()
thumbnails := form.File["thumbnail"]
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
if len(thumbnails) > 0 {
for _, img := range thumbnails {
fileExt := filepath.Ext(img.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/locations/%s/thumbnail", req.Name)
osFilename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imgPath = fmt.Sprintf("%s/%s", dir, osFilename)
if err := ctx.SaveUploadedFile(img, imgPath); err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
return
}
}
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
arg := db.CreateLocationParams{
Address: req.Address,
Name: req.Name,
SubmittedBy: req.SubmittedBy,
RegencyID: req.RegencyID,
GoogleMapsLink: sql.NullString{Valid: len(req.GoogleMapsLink) > 0, String: req.GoogleMapsLink},
}
err := server.Store.CreateLocation(ctx, arg)
if err != nil {
if pqErr, ok := err.(*pq.Error); ok {
switch pqErr.Code.Name() {
case "foreign_key_violation", "unique_violation":
if pqErr.Constraint == "locations_regency_id_fkey" {
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no regency with id: %d", arg.RegencyID)))
return
}
if pqErr.Constraint == "submitted_by_fkey" {
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no user with id: %d", arg.SubmittedBy)))
return
}
if err := ctx.SaveUploadedFile(img, imgPath); err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
return
}
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
return
arg := db.CreateLocationTxParams{
Address: req.Address,
Name: req.Name,
LocationType: db.LocationType(req.LocationType),
SubmittedBy: req.SubmittedBy,
RegencyID: req.RegencyID,
IsDeleted: false,
ApprovedBy: sql.NullInt32{Int32: 0, Valid: false},
GoogleMapsLink: sql.NullString{Valid: len(req.GoogleMapsLink) > 0, String: req.GoogleMapsLink},
Thumbnail: tempImg,
}
err := server.Store.CreateLocationTx(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to save Location"))
return
}
}
ctx.Writer.WriteHeader(http.StatusOK)

71
api/news_event.go Normal file
View File

@ -0,0 +1,71 @@
package api
import (
"database/sql"
"net/http"
db "git.nochill.in/nochill/hiling_go/db/sqlc"
"github.com/gin-gonic/gin"
)
type CreateNewsEventsReq struct {
Url string `json:"url" binding:"required,url"`
Title string `json:"title" binding:"required"`
Source string `json:"source"`
Description string `json:"description"`
SubmittedBy int32 `json:"submitted_by" binding:"required,numeric"`
}
func (server *Server) createNews(ctx *gin.Context) {
var req CreateNewsEventsReq
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
err := server.Store.CreateNewsEvents(ctx, db.CreateNewsEventsParams{
Title: req.Title,
Url: req.Url,
Source: req.Source,
Description: sql.NullString{Valid: len(req.Description) > 0, String: req.Description},
SubmittedBy: req.SubmittedBy,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to save news/evnts"))
return
}
ctx.Writer.WriteHeader(http.StatusCreated)
}
type GetNewsEventsListReq struct {
BaseGetListRequest
Approved int8 `form:"is_with_approval"`
}
func (server *Server) GetNewsEventsList(ctx *gin.Context) {
var req GetNewsEventsListReq
isWithApproval := ""
if err := ctx.ShouldBindQuery(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
if req.Approved > 0 {
isWithApproval = "WHERE approved_by IS NOT NULL"
}
news, err := server.Store.GetNewsEventsList(ctx, db.GetNewsEventsListParams{
Limit: req.PageSize,
Offset: (req.Page - 1) * req.PageSize,
IsWithApproved: isWithApproval,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get news / events"))
return
}
ctx.JSON(http.StatusOK, news)
}

37
api/region.go Normal file
View File

@ -0,0 +1,37 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
)
func (server *Server) getListRegions(ctx *gin.Context) {
regions, err := server.Store.GetListRegions(ctx)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get regions"))
return
}
ctx.JSON(http.StatusOK, regions)
}
func (server *Server) getListProvinces(ctx *gin.Context) {
provinces, err := server.Store.GetListProvinces(ctx)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get regions"))
return
}
ctx.JSON(http.StatusOK, provinces)
}
func (server *Server) getListRegencies(ctx *gin.Context) {
regencies, err := server.Store.GetListRegencies(ctx)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get regions"))
return
}
ctx.JSON(http.StatusOK, regencies)
}

View File

@ -39,15 +39,16 @@ func (server *Server) getRoutes() {
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"},
AllowHeaders: []string{"Content-Type", "Content-Length", "Accept-Encoding", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With", "X-XSRF-TOKEN"},
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)
router.GET("/regions", server.getListRegions)
router.GET("/region/provinces", server.getListProvinces)
router.GET("/region/regencies", server.getListRegencies)
// LOCATION
router.POST("/locations", server.createLocation)
@ -61,10 +62,18 @@ func (server *Server) getRoutes() {
//IMAGES
router.GET("/images/location", server.getAllImagesByLocation)
// NEWS / EVENTS
router.GET("/news-events", server.GetNewsEventsList)
// REQUIRE AUTH TOKEN
authRoutes := router.Use(authMiddleware(server.TokenMaker))
authRoutes.POST("/review/location", server.createReview)
authRoutes.PATCH("/user", server.updateUser)
authRoutes.GET("/user/review/location/:location_id", server.getUserReviewByLocation)
authRoutes.GET("/user/profile", server.getUserStats)
authRoutes.PATCH("/user/avatar", server.updateUserAvatar)
authRoutes.DELETE("/user/avatar", server.removeAvatar)
authRoutes.POST("/news-events", server.createNews)
server.Router = router
}

View File

@ -157,7 +157,7 @@ func randomLocation() db.Location {
TotalVisited: sql.NullInt32{Valid: true, Int32: int32(util.RandomInt(0, 32))},
Thumbnail: sql.NullString{Valid: false, String: ""},
RegencyID: 1305,
IsDeleted: sql.NullBool{Valid: true, Bool: false},
IsDeleted: false,
CreatedAt: sql.NullTime{Valid: true, Time: time.Now()},
UpdatedAt: sql.NullTime{Valid: true, Time: time.Now()},
ApprovedBy: sql.NullInt32{Valid: true, Int32: 1},

View File

@ -2,13 +2,19 @@ package api
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
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-gonic/gin"
"github.com/lib/pq"
"github.com/sqlc-dev/pqtype"
)
type createUserRequest struct {
@ -124,6 +130,134 @@ func (server *Server) createUser(ctx *gin.Context) {
ctx.JSON(http.StatusOK, res)
}
func (server *Server) getUserStats(ctx *gin.Context) {
var scoreDistribution []map[string]int8
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
userStats, err := server.Store.GetUserStats(ctx, int32(authPayload.UserID))
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get user stats"))
return
}
err = json.Unmarshal(userStats.ScoresDistribution, &scoreDistribution)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong whilet try to parse score distribution"))
return
}
var userReviews []map[string]any
if userStats.Reviews != nil {
err = json.Unmarshal(userStats.Reviews, &userReviews)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something wrong while try to parse user reviwes stats"))
return
}
} else {
userReviews = make([]map[string]any, 0)
}
ctx.JSON(http.StatusOK, gin.H{
"reviews": userReviews,
"scores_distribution": scoreDistribution,
"user_stats": userStats,
})
}
type UpdateUserRequest struct {
About string `json:"about"`
Website string `json:"website"`
SocialMedia []map[string]string `json:"social_media"`
}
func (server *Server) updateUser(ctx *gin.Context) {
var req UpdateUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
fmt.Println(req.About)
social, _ := json.Marshal(req.SocialMedia)
socialArr := pqtype.NullRawMessage{
RawMessage: social,
Valid: len(req.SocialMedia) > 0,
}
user, err := server.Store.UpdateUser(ctx, db.UpdateUserParams{
About: sql.NullString{String: req.About, Valid: true},
SocialMedia: socialArr,
Website: sql.NullString{String: req.Website, Valid: len(req.Website) > 0},
ID: int32(authPayload.UserID),
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to update user"))
return
}
ctx.JSON(http.StatusOK, user)
}
func (server *Server) updateUserAvatar(ctx *gin.Context) {
file, err := ctx.FormFile("file")
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to parse file"))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
fileExt := filepath.Ext(file.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/user/%d/avatar", authPayload.UserID)
osFilename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imgPath := fmt.Sprintf("%s/%s", dir, osFilename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
if err := ctx.SaveUploadedFile(file, imgPath); err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
return
}
url, err := server.Store.UpdateAvatar(ctx, db.UpdateAvatarParams{
ID: int32(authPayload.UserID),
AvatarPicture: sql.NullString{Valid: true, String: imgPath},
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to save image to database"))
return
}
ctx.JSON(http.StatusOK, gin.H{"image_url": url.String})
}
func (server *Server) removeAvatar(ctx *gin.Context) {
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
_, err := server.Store.UpdateAvatar(ctx, db.UpdateAvatarParams{
AvatarPicture: sql.NullString{String: "", Valid: false},
ID: int32(authPayload.UserID),
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to update user avatar"))
return
}
ctx.Writer.WriteHeader(http.StatusNoContent)
}
func (server *Server) login(ctx *gin.Context) {
var req createUserRequest
@ -135,7 +269,7 @@ func (server *Server) login(ctx *gin.Context) {
user, err := server.Store.GetUser(ctx, req.Username)
if err != nil {
if err == sql.ErrNoRows {
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
ctx.JSON(http.StatusNotFound, ErrorResponse(err, "User not found"))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong whlie try to get user"))

View File

@ -1,13 +1,13 @@
id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by
1#Jalan Raya Beside the bridge Ubud#Murnis Warung#1#https://cdn.discordapp.com/attachments/743422487882104837/1150972798320267304/image.png#5104#https://www.google.com/maps/place/Murni's+Warung/@-8.5048696,115.2553417,19z/data=!4m6!3m5!1s0x2dd23d3e0ffaa071:0xf2fa69b4cb211e41!8m2!3d-8.5051184!4d115.2547196!16s%2Fg%2F1tdsmcq7?entry=ttu#1
2#Jl.Taman Wijaya Kusuma Ps. Baru Kecamatan Sawah Besar#Masjid Istiqlal#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/04/11/31/33/istiqlal-mosque-mesjid.jpg?w=500&h=-1&s=1#3173#https://www.google.com/maps/place/Masjid+Istiqlal/@-6.1703155,106.8308434,19z/data=!4m15!1m7!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!2sMasjid+Istiqlal!8m2!3d-6.17017!4d106.83139!16zL20vMDRzam1q!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!8m2!3d-6.17017!4d106.83139!15sCg9tYXNqaWQgaXN0aXFsYWxaESIPbWFzamlkIGlzdGlxbGFskgEGbW9zcXVl4AEA!16zL20vMDRzam1q?entry=ttu#1
3#Jl. Mayjend Sungkono no. 89#Hotel Ciputra World Surabaya#1#https://lh5.googleusercontent.com/p/AF1QipOvHDO-M6riRoqBrWU3MskhwL_bue8JmN9faq7Q=w500-h500-k-no#3578#https://www.google.com/maps/place/Ciputra+World+Hotel+Surabaya/@-7.2923061,112.7191552,15z/data=!4m2!3m1!1s0x0:0x736a9c49dcc2ac42?sa=X&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgVEAA&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgjEAc#1
4#Jl. Taman Safari No.101 . B Cibeureum Kec. Cisarua#Club Huis#1#https://media-cdn.tripadvisor.com/media/photo-o/0d/6a/5d/63/our-peaceful-backyard.jpg#3201#https://www.google.com/maps/place/Club+Huis/@-6.7027857,106.9453741,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69b679d7a09e01:0xf9fc2df396f09977!8m2!3d-6.7027857!4d106.947949!16s%2Fg%2F11c57lh8ky?entry=ttu#1
5#Desa Tambakrejo Kecamatan Sumbermanjing Wetan#Pulau Sempu#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/11/3b/06/a5/pulau-sempu.jpg?w=500&h=-1&s=1#3507#https://www.google.com/maps/place/Pulau+Sempu/@-8.446621,112.6746143,14z/data=!3m1!4b1!4m6!3m5!1s0x2dd60120edbc901f:0x8efd89687a308993!8m2!3d-8.4428564!4d112.6973355!16s%2Fm%2F0r8k540?entry=ttu#1
6#Jl. Bukit Golf I BSD Sektor VI Lengkong Karya Kec. Serpong Utara#Damai Indah Golf#1#https://lh3.googleusercontent.com/p/AF1QipN5Z-0J6vIfIO6gqPO0z5HDWlNKqp0t816XIJPS=s680-w500-h500#3674#https://www.google.com/maps/place/Damai+Indah+Golf+-+BSD+Course/@-6.2815644,106.6496566,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69fb152983d973:0x89e58e219f8b93ef!8m2!3d-6.2815644!4d106.6522315!16s%2Fg%2F11c54c9r94?entry=ttu#1
7#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1
8#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1
9#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu#1
10#Pulau Padar#Pulau Padar#2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/4e/11/f3/padar-island.jpg?w=800&h=-1&s=1#5315#https://www.google.com/maps/place/Pulau+Padar/@-8.6554183,119.570686,14.83z/data=!4m6!3m5!1s0x2db4f84ff6cd01ab:0xf7e6fd33b692a898!8m2!3d-8.6489909!4d119.5832593!16s%2Fg%2F121d16wd?entry=ttu#1
11#Jl. Ahmad Yani 18, Kel. Kelimutu, Kec. Ende Tengah#Sari rasa#3#https://cdn.discordapp.com/attachments/743422487882104837/1151903310295601172/image.png#5315#https://www.google.com/maps/place/Rumah+Makan+Cha+Cha/@-8.6153697,120.4652533,21z/data=!4m6!3m5!1s0x2db37469e54c0dd3:0x36ac988c726ed544!8m2!3d-8.6153631!4d120.465275!16s%2Fg%2F11bwpclp91?entry=ttu#1
12#Danau Sentani#Danau Sentani#3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/c7/e8/17/20190630-143008-largejpg.jpg?w=800&h=-1&s=1#9403#https://www.google.com/maps/place/Danau+Sentani/@-2.639976,140.3889748,12.46z/data=!4m7!3m6!1s0x686cf33c72660fbf:0x7e5c1e7d20d930d7!4b1!8m2!3d-2.6133004!4d140.518734!16s%2Fm%2F0tkjf6v?entry=ttu#1
id,location_type,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by,is_deleted
1#culinary#Jalan Raya Beside the bridge Ubud#Murnis Warung#1#https://cdn.discordapp.com/attachments/743422487882104837/1150972798320267304/image.png#5104#https://www.google.com/maps/place/Murni's+Warung/@-8.5048696,115.2553417,19z/data=!4m6!3m5!1s0x2dd23d3e0ffaa071:0xf2fa69b4cb211e41!8m2!3d-8.5051184!4d115.2547196!16s%2Fg%2F1tdsmcq7?entry=ttu#1#false
2#other#Jl.Taman Wijaya Kusuma Ps. Baru Kecamatan Sawah Besar#Masjid Istiqlal#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/04/11/31/33/istiqlal-mosque-mesjid.jpg?w=500&h=-1&s=1#3173#https://www.google.com/maps/place/Masjid+Istiqlal/@-6.1703155,106.8308434,19z/data=!4m15!1m7!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!2sMasjid+Istiqlal!8m2!3d-6.17017!4d106.83139!16zL20vMDRzam1q!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!8m2!3d-6.17017!4d106.83139!15sCg9tYXNqaWQgaXN0aXFsYWxaESIPbWFzamlkIGlzdGlxbGFskgEGbW9zcXVl4AEA!16zL20vMDRzam1q?entry=ttu#1#false
3#other#Jl. Mayjend Sungkono no. 89#Hotel Ciputra World Surabaya#1#https://lh5.googleusercontent.com/p/AF1QipOvHDO-M6riRoqBrWU3MskhwL_bue8JmN9faq7Q=w500-h500-k-no#3578#https://www.google.com/maps/place/Ciputra+World+Hotel+Surabaya/@-7.2923061,112.7191552,15z/data=!4m2!3m1!1s0x0:0x736a9c49dcc2ac42?sa=X&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgVEAA&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgjEAc#1#false
4#amusement park#Jl. Taman Safari No.101 . B Cibeureum Kec. Cisarua#Club Huis#1#https://media-cdn.tripadvisor.com/media/photo-o/0d/6a/5d/63/our-peaceful-backyard.jpg#3201#https://www.google.com/maps/place/Club+Huis/@-6.7027857,106.9453741,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69b679d7a09e01:0xf9fc2df396f09977!8m2!3d-6.7027857!4d106.947949!16s%2Fg%2F11c57lh8ky?entry=ttu#1#false
5#other#Desa Tambakrejo Kecamatan Sumbermanjing Wetan#Pulau Sempu#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/11/3b/06/a5/pulau-sempu.jpg?w=500&h=-1&s=1#3507#https://www.google.com/maps/place/Pulau+Sempu/@-8.446621,112.6746143,14z/data=!3m1!4b1!4m6!3m5!1s0x2dd60120edbc901f:0x8efd89687a308993!8m2!3d-8.4428564!4d112.6973355!16s%2Fm%2F0r8k540?entry=ttu#1#false
6#other#Jl. Bukit Golf I BSD Sektor VI Lengkong Karya Kec. Serpong Utara#Damai Indah Golf#1#https://lh3.googleusercontent.com/p/AF1QipN5Z-0J6vIfIO6gqPO0z5HDWlNKqp0t816XIJPS=s680-w500-h500#3674#https://www.google.com/maps/place/Damai+Indah+Golf+-+BSD+Course/@-6.2815644,106.6496566,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69fb152983d973:0x89e58e219f8b93ef!8m2!3d-6.2815644!4d106.6522315!16s%2Fg%2F11c54c9r94?entry=ttu#1#false
7#other#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1#false
8#other#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1#false
9#beach#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu#1#false
10#other#Pulau Padar#Pulau Padar#2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/4e/11/f3/padar-island.jpg?w=800&h=-1&s=1#5315#https://www.google.com/maps/place/Pulau+Padar/@-8.6554183,119.570686,14.83z/data=!4m6!3m5!1s0x2db4f84ff6cd01ab:0xf7e6fd33b692a898!8m2!3d-8.6489909!4d119.5832593!16s%2Fg%2F121d16wd?entry=ttu#1#false
11#culinary#Jl. Ahmad Yani 18, Kel. Kelimutu, Kec. Ende Tengah#Sari rasa#3#https://cdn.discordapp.com/attachments/743422487882104837/1151903310295601172/image.png#5315#https://www.google.com/maps/place/Rumah+Makan+Cha+Cha/@-8.6153697,120.4652533,21z/data=!4m6!3m5!1s0x2db37469e54c0dd3:0x36ac988c726ed544!8m2!3d-8.6153631!4d120.465275!16s%2Fg%2F11bwpclp91?entry=ttu#1#false
12#other#Danau Sentani#Danau Sentani#3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/c7/e8/17/20190630-143008-largejpg.jpg?w=800&h=-1&s=1#9403#https://www.google.com/maps/place/Danau+Sentani/@-2.639976,140.3889748,12.46z/data=!4m7!3m6!1s0x686cf33c72660fbf:0x7e5c1e7d20d930d7!4b1!8m2!3d-2.6133004!4d140.518734!16s%2Fm%2F0tkjf6v?entry=ttu#1#false
1 id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by id,location_type,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by,is_deleted
2 1#Jalan Raya Beside the bridge Ubud#Murni’s Warung#1#https://cdn.discordapp.com/attachments/743422487882104837/1150972798320267304/image.png#5104#https://www.google.com/maps/place/Murni's+Warung/@-8.5048696,115.2553417,19z/data=!4m6!3m5!1s0x2dd23d3e0ffaa071:0xf2fa69b4cb211e41!8m2!3d-8.5051184!4d115.2547196!16s%2Fg%2F1tdsmcq7?entry=ttu#1 1#culinary#Jalan Raya Beside the bridge Ubud#Murni’s Warung#1#https://cdn.discordapp.com/attachments/743422487882104837/1150972798320267304/image.png#5104#https://www.google.com/maps/place/Murni's+Warung/@-8.5048696,115.2553417,19z/data=!4m6!3m5!1s0x2dd23d3e0ffaa071:0xf2fa69b4cb211e41!8m2!3d-8.5051184!4d115.2547196!16s%2Fg%2F1tdsmcq7?entry=ttu#1#false
3 2#Jl.Taman Wijaya Kusuma Ps. Baru Kecamatan Sawah Besar#Masjid Istiqlal#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/04/11/31/33/istiqlal-mosque-mesjid.jpg?w=500&h=-1&s=1#3173#https://www.google.com/maps/place/Masjid+Istiqlal/@-6.1703155,106.8308434,19z/data=!4m15!1m7!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!2sMasjid+Istiqlal!8m2!3d-6.17017!4d106.83139!16zL20vMDRzam1q!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!8m2!3d-6.17017!4d106.83139!15sCg9tYXNqaWQgaXN0aXFsYWxaESIPbWFzamlkIGlzdGlxbGFskgEGbW9zcXVl4AEA!16zL20vMDRzam1q?entry=ttu#1 2#other#Jl.Taman Wijaya Kusuma Ps. Baru Kecamatan Sawah Besar#Masjid Istiqlal#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/04/11/31/33/istiqlal-mosque-mesjid.jpg?w=500&h=-1&s=1#3173#https://www.google.com/maps/place/Masjid+Istiqlal/@-6.1703155,106.8308434,19z/data=!4m15!1m7!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!2sMasjid+Istiqlal!8m2!3d-6.17017!4d106.83139!16zL20vMDRzam1q!3m6!1s0x2e69f5ce68b5e01d:0xcafaf042d5840c6c!8m2!3d-6.17017!4d106.83139!15sCg9tYXNqaWQgaXN0aXFsYWxaESIPbWFzamlkIGlzdGlxbGFskgEGbW9zcXVl4AEA!16zL20vMDRzam1q?entry=ttu#1#false
4 3#Jl. Mayjend Sungkono no. 89#Hotel Ciputra World Surabaya#1#https://lh5.googleusercontent.com/p/AF1QipOvHDO-M6riRoqBrWU3MskhwL_bue8JmN9faq7Q=w500-h500-k-no#3578#https://www.google.com/maps/place/Ciputra+World+Hotel+Surabaya/@-7.2923061,112.7191552,15z/data=!4m2!3m1!1s0x0:0x736a9c49dcc2ac42?sa=X&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgVEAA&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgjEAc#1 3#other#Jl. Mayjend Sungkono no. 89#Hotel Ciputra World Surabaya#1#https://lh5.googleusercontent.com/p/AF1QipOvHDO-M6riRoqBrWU3MskhwL_bue8JmN9faq7Q=w500-h500-k-no#3578#https://www.google.com/maps/place/Ciputra+World+Hotel+Surabaya/@-7.2923061,112.7191552,15z/data=!4m2!3m1!1s0x0:0x736a9c49dcc2ac42?sa=X&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgVEAA&ved=2ahUKEwjJlbf8gqSBAxWtzzgGHUIkBFYQ_BJ6BAgjEAc#1#false
5 4#Jl. Taman Safari No.101 . B Cibeureum Kec. Cisarua#Club Huis#1#https://media-cdn.tripadvisor.com/media/photo-o/0d/6a/5d/63/our-peaceful-backyard.jpg#3201#https://www.google.com/maps/place/Club+Huis/@-6.7027857,106.9453741,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69b679d7a09e01:0xf9fc2df396f09977!8m2!3d-6.7027857!4d106.947949!16s%2Fg%2F11c57lh8ky?entry=ttu#1 4#amusement park#Jl. Taman Safari No.101 . B Cibeureum Kec. Cisarua#Club Huis#1#https://media-cdn.tripadvisor.com/media/photo-o/0d/6a/5d/63/our-peaceful-backyard.jpg#3201#https://www.google.com/maps/place/Club+Huis/@-6.7027857,106.9453741,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69b679d7a09e01:0xf9fc2df396f09977!8m2!3d-6.7027857!4d106.947949!16s%2Fg%2F11c57lh8ky?entry=ttu#1#false
6 5#Desa Tambakrejo Kecamatan Sumbermanjing Wetan#Pulau Sempu#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/11/3b/06/a5/pulau-sempu.jpg?w=500&h=-1&s=1#3507#https://www.google.com/maps/place/Pulau+Sempu/@-8.446621,112.6746143,14z/data=!3m1!4b1!4m6!3m5!1s0x2dd60120edbc901f:0x8efd89687a308993!8m2!3d-8.4428564!4d112.6973355!16s%2Fm%2F0r8k540?entry=ttu#1 5#other#Desa Tambakrejo Kecamatan Sumbermanjing Wetan#Pulau Sempu#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/11/3b/06/a5/pulau-sempu.jpg?w=500&h=-1&s=1#3507#https://www.google.com/maps/place/Pulau+Sempu/@-8.446621,112.6746143,14z/data=!3m1!4b1!4m6!3m5!1s0x2dd60120edbc901f:0x8efd89687a308993!8m2!3d-8.4428564!4d112.6973355!16s%2Fm%2F0r8k540?entry=ttu#1#false
7 6#Jl. Bukit Golf I BSD Sektor VI Lengkong Karya Kec. Serpong Utara#Damai Indah Golf#1#https://lh3.googleusercontent.com/p/AF1QipN5Z-0J6vIfIO6gqPO0z5HDWlNKqp0t816XIJPS=s680-w500-h500#3674#https://www.google.com/maps/place/Damai+Indah+Golf+-+BSD+Course/@-6.2815644,106.6496566,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69fb152983d973:0x89e58e219f8b93ef!8m2!3d-6.2815644!4d106.6522315!16s%2Fg%2F11c54c9r94?entry=ttu#1 6#other#Jl. Bukit Golf I BSD Sektor VI Lengkong Karya Kec. Serpong Utara#Damai Indah Golf#1#https://lh3.googleusercontent.com/p/AF1QipN5Z-0J6vIfIO6gqPO0z5HDWlNKqp0t816XIJPS=s680-w500-h500#3674#https://www.google.com/maps/place/Damai+Indah+Golf+-+BSD+Course/@-6.2815644,106.6496566,17z/data=!3m1!4b1!4m6!3m5!1s0x2e69fb152983d973:0x89e58e219f8b93ef!8m2!3d-6.2815644!4d106.6522315!16s%2Fg%2F11c54c9r94?entry=ttu#1#false
8 7#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1 7#other#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1#false
9 8#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1 8#other#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1#false
10 9#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu#1 9#beach#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu#1#false
11 10#Pulau Padar#Pulau Padar#2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/4e/11/f3/padar-island.jpg?w=800&h=-1&s=1#5315#https://www.google.com/maps/place/Pulau+Padar/@-8.6554183,119.570686,14.83z/data=!4m6!3m5!1s0x2db4f84ff6cd01ab:0xf7e6fd33b692a898!8m2!3d-8.6489909!4d119.5832593!16s%2Fg%2F121d16wd?entry=ttu#1 10#other#Pulau Padar#Pulau Padar#2#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/18/4e/11/f3/padar-island.jpg?w=800&h=-1&s=1#5315#https://www.google.com/maps/place/Pulau+Padar/@-8.6554183,119.570686,14.83z/data=!4m6!3m5!1s0x2db4f84ff6cd01ab:0xf7e6fd33b692a898!8m2!3d-8.6489909!4d119.5832593!16s%2Fg%2F121d16wd?entry=ttu#1#false
12 11#Jl. Ahmad Yani 18, Kel. Kelimutu, Kec. Ende Tengah#Sari rasa#3#https://cdn.discordapp.com/attachments/743422487882104837/1151903310295601172/image.png#5315#https://www.google.com/maps/place/Rumah+Makan+Cha+Cha/@-8.6153697,120.4652533,21z/data=!4m6!3m5!1s0x2db37469e54c0dd3:0x36ac988c726ed544!8m2!3d-8.6153631!4d120.465275!16s%2Fg%2F11bwpclp91?entry=ttu#1 11#culinary#Jl. Ahmad Yani 18, Kel. Kelimutu, Kec. Ende Tengah#Sari rasa#3#https://cdn.discordapp.com/attachments/743422487882104837/1151903310295601172/image.png#5315#https://www.google.com/maps/place/Rumah+Makan+Cha+Cha/@-8.6153697,120.4652533,21z/data=!4m6!3m5!1s0x2db37469e54c0dd3:0x36ac988c726ed544!8m2!3d-8.6153631!4d120.465275!16s%2Fg%2F11bwpclp91?entry=ttu#1#false
13 12#Danau Sentani#Danau Sentani#3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/c7/e8/17/20190630-143008-largejpg.jpg?w=800&h=-1&s=1#9403#https://www.google.com/maps/place/Danau+Sentani/@-2.639976,140.3889748,12.46z/data=!4m7!3m6!1s0x686cf33c72660fbf:0x7e5c1e7d20d930d7!4b1!8m2!3d-2.6133004!4d140.518734!16s%2Fm%2F0tkjf6v?entry=ttu#1 12#other#Danau Sentani#Danau Sentani#3#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/c7/e8/17/20190630-143008-largejpg.jpg?w=800&h=-1&s=1#9403#https://www.google.com/maps/place/Danau+Sentani/@-2.639976,140.3889748,12.46z/data=!4m7!3m6!1s0x686cf33c72660fbf:0x7e5c1e7d20d930d7!4b1!8m2!3d-2.6133004!4d140.518734!16s%2Fm%2F0tkjf6v?entry=ttu#1#false

View File

@ -1,4 +1,4 @@
id,username,password
1,user123,password
2,critics,password
3,plebs,password
1,user123,$2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a
2,critics,$2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a
3,plebs,$2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a
1 id username password
2 1 user123 password $2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a
3 2 critics password $2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a
4 3 plebs password $2a$10$5jbejbMXCs7noqPBt1v9K.TCjnqDp.GtTkMiRRRwU8/t78GiGjY9a

View File

@ -12,4 +12,5 @@ DROP TABLE IF EXISTS users;
DROP TYPE IF EXISTS user_reports_type;
DROP TYPE IF EXISTS comment_type;
DROP TYPE IF EXISTS comment_type;
DROP TYPE IF EXISTS location_type;

View File

@ -53,14 +53,14 @@ CREATE TABLE user_reports(
CREATE TABLE regions(
"id" serial primary key not null,
"region_name" varchar,
"region_name" varchar not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TABLE provinces(
"id" serial primary key not null,
"province_name" varchar,
"province_name" varchar not null,
"region_id" smallint references "regions"("id") not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
@ -68,22 +68,31 @@ CREATE TABLE provinces(
CREATE TABLE regencies(
"id" serial primary key not null,
"regency_name" varchar,
"regency_name" varchar not null,
"province_id" smallint references "provinces"("id") not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
CREATE TYPE location_type AS ENUM(
'beach',
'amusement park',
'culinary',
'hiking / camping',
'other'
);
CREATE TABLE locations(
"id" serial primary key not null,
"address" varchar not null,
"name" varchar not null,
"google_maps_link" varchar,
"location_type" location_type not null,
"submitted_by" integer references "users"("id") not null,
"total_visited" integer,
"thumbnail" varchar,
"regency_id" smallint references "regencies"("id") not null,
"is_deleted" boolean,
"is_deleted" boolean not null,
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
);
@ -112,6 +121,7 @@ CREATE TABLE reviews (
"comments" text not null,
"score" smallint not null,
"is_from_critic" boolean not null,
"cost_approx" integer,
"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()),

View File

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

View File

@ -0,0 +1,7 @@
CREATE TABLE user_follow(
id serial primary key not null,
follower_id integer references "users"("id") not null,
followee_id integer references "users"("id") not null,
created_at timestamp default (now()) not null,
updated_at timestamp default (now()) not null
)

View File

@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN about;

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN about text;

View File

@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN IF EXISTS website;

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN website varchar;

View File

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

View File

@ -0,0 +1,13 @@
CREATE TABLE news_events (
"id" serial primary key not null,
"title" varchar not null,
"url" varchar not null,
"source" varchar not null,
"thumbnail" varchar,
"description" text,
"is_deleted" boolean not null default(false),
"submitted_by" int references "users"("id") not null,
"approved_by" int references "users"("id"),
"created_at" timestamp default(now()),
"updated_at" timestamp default(now())
)

View File

@ -6,6 +6,7 @@ package mockdb
import (
context "context"
sql "database/sql"
reflect "reflect"
db "git.nochill.in/nochill/hiling_go/db/sqlc"
@ -35,6 +36,20 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder
}
// AddFollowUser mocks base method.
func (m *MockStore) AddFollowUser(arg0 context.Context, arg1 db.AddFollowUserParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddFollowUser", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// AddFollowUser indicates an expected call of AddFollowUser.
func (mr *MockStoreMockRecorder) AddFollowUser(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFollowUser", reflect.TypeOf((*MockStore)(nil).AddFollowUser), arg0, arg1)
}
// CheckIfReviewExists mocks base method.
func (m *MockStore) CheckIfReviewExists(arg0 context.Context, arg1 db.CheckIfReviewExistsParams) (int64, error) {
m.ctrl.T.Helper()
@ -50,20 +65,63 @@ func (mr *MockStoreMockRecorder) CheckIfReviewExists(arg0, arg1 interface{}) *go
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 {
// CreateImage mocks base method.
func (m *MockStore) CreateImage(arg0 context.Context, arg1 []db.CreateImageParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLocation", arg0, arg1)
ret := m.ctrl.Call(m, "CreateImage", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CreateImage indicates an expected call of CreateImage.
func (mr *MockStoreMockRecorder) CreateImage(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateImage", reflect.TypeOf((*MockStore)(nil).CreateImage), arg0, arg1)
}
// CreateLocation mocks base method.
func (m *MockStore) CreateLocation(arg0 context.Context, arg1 db.CreateLocationParams) (int32, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLocation", arg0, arg1)
ret0, _ := ret[0].(int32)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateLocation indicates an expected call of CreateLocation.
func (mr *MockStoreMockRecorder) CreateLocation(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocation", reflect.TypeOf((*MockStore)(nil).CreateLocation), arg0, arg1)
}
// CreateLocationTx mocks base method.
func (m *MockStore) CreateLocationTx(arg0 context.Context, arg1 db.CreateLocationTxParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLocationTx", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CreateLocationTx indicates an expected call of CreateLocationTx.
func (mr *MockStoreMockRecorder) CreateLocationTx(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocationTx", reflect.TypeOf((*MockStore)(nil).CreateLocationTx), arg0, arg1)
}
// CreateNewsEvents mocks base method.
func (m *MockStore) CreateNewsEvents(arg0 context.Context, arg1 db.CreateNewsEventsParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNewsEvents", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CreateNewsEvents indicates an expected call of CreateNewsEvents.
func (mr *MockStoreMockRecorder) CreateNewsEvents(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewsEvents", reflect.TypeOf((*MockStore)(nil).CreateNewsEvents), arg0, arg1)
}
// CreateReview mocks base method.
func (m *MockStore) CreateReview(arg0 context.Context, arg1 db.CreateReviewParams) (db.Review, error) {
m.ctrl.T.Helper()
@ -169,6 +227,21 @@ func (mr *MockStoreMockRecorder) GetListLocations(arg0 interface{}) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocations", reflect.TypeOf((*MockStore)(nil).GetListLocations), arg0)
}
// GetListProvinces mocks base method.
func (m *MockStore) GetListProvinces(arg0 context.Context) ([]db.GetListProvincesRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetListProvinces", arg0)
ret0, _ := ret[0].([]db.GetListProvincesRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetListProvinces indicates an expected call of GetListProvinces.
func (mr *MockStoreMockRecorder) GetListProvinces(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListProvinces", reflect.TypeOf((*MockStore)(nil).GetListProvinces), arg0)
}
// GetListRecentLocationsWithRatings mocks base method.
func (m *MockStore) GetListRecentLocationsWithRatings(arg0 context.Context, arg1 int32) ([]db.GetListRecentLocationsWithRatingsRow, error) {
m.ctrl.T.Helper()
@ -184,6 +257,36 @@ func (mr *MockStoreMockRecorder) GetListRecentLocationsWithRatings(arg0, arg1 in
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListRecentLocationsWithRatings", reflect.TypeOf((*MockStore)(nil).GetListRecentLocationsWithRatings), arg0, arg1)
}
// GetListRegencies mocks base method.
func (m *MockStore) GetListRegencies(arg0 context.Context) ([]db.GetListRegenciesRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetListRegencies", arg0)
ret0, _ := ret[0].([]db.GetListRegenciesRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetListRegencies indicates an expected call of GetListRegencies.
func (mr *MockStoreMockRecorder) GetListRegencies(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListRegencies", reflect.TypeOf((*MockStore)(nil).GetListRegencies), arg0)
}
// GetListRegions mocks base method.
func (m *MockStore) GetListRegions(arg0 context.Context) ([]db.GetListRegionsRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetListRegions", arg0)
ret0, _ := ret[0].([]db.GetListRegionsRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetListRegions indicates an expected call of GetListRegions.
func (mr *MockStoreMockRecorder) GetListRegions(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListRegions", reflect.TypeOf((*MockStore)(nil).GetListRegions), arg0)
}
// GetLocation mocks base method.
func (m *MockStore) GetLocation(arg0 context.Context, arg1 int32) (db.GetLocationRow, error) {
m.ctrl.T.Helper()
@ -214,6 +317,21 @@ func (mr *MockStoreMockRecorder) GetLocationTag(arg0, arg1 interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocationTag", reflect.TypeOf((*MockStore)(nil).GetLocationTag), arg0, arg1)
}
// GetNewsEventsList mocks base method.
func (m *MockStore) GetNewsEventsList(arg0 context.Context, arg1 db.GetNewsEventsListParams) ([]db.NewsEventRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNewsEventsList", arg0, arg1)
ret0, _ := ret[0].([]db.NewsEventRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNewsEventsList indicates an expected call of GetNewsEventsList.
func (mr *MockStoreMockRecorder) GetNewsEventsList(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNewsEventsList", reflect.TypeOf((*MockStore)(nil).GetNewsEventsList), arg0, arg1)
}
// GetSession mocks base method.
func (m *MockStore) GetSession(arg0 context.Context, arg1 int32) (db.UserSession, error) {
m.ctrl.T.Helper()
@ -274,6 +392,64 @@ func (mr *MockStoreMockRecorder) GetUserReviewByLocation(arg0, arg1 interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserReviewByLocation", reflect.TypeOf((*MockStore)(nil).GetUserReviewByLocation), arg0, arg1)
}
// GetUserStats mocks base method.
func (m *MockStore) GetUserStats(arg0 context.Context, arg1 int32) (db.GetUserStatsRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserStats", arg0, arg1)
ret0, _ := ret[0].(db.GetUserStatsRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserStats indicates an expected call of GetUserStats.
func (mr *MockStoreMockRecorder) GetUserStats(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserStats", reflect.TypeOf((*MockStore)(nil).GetUserStats), arg0, arg1)
}
// RemoveFollowUser mocks base method.
func (m *MockStore) RemoveFollowUser(arg0 context.Context, arg1 db.RemoveFollowUserParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoveFollowUser", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// RemoveFollowUser indicates an expected call of RemoveFollowUser.
func (mr *MockStoreMockRecorder) RemoveFollowUser(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowUser", reflect.TypeOf((*MockStore)(nil).RemoveFollowUser), arg0, arg1)
}
// UpdateAvatar mocks base method.
func (m *MockStore) UpdateAvatar(arg0 context.Context, arg1 db.UpdateAvatarParams) (sql.NullString, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAvatar", arg0, arg1)
ret0, _ := ret[0].(sql.NullString)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAvatar indicates an expected call of UpdateAvatar.
func (mr *MockStoreMockRecorder) UpdateAvatar(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAvatar", reflect.TypeOf((*MockStore)(nil).UpdateAvatar), arg0, arg1)
}
// UpdateLocationThumbnail mocks base method.
func (m *MockStore) UpdateLocationThumbnail(arg0 context.Context, arg1 db.UpdateLocationThumbnailParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateLocationThumbnail", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateLocationThumbnail indicates an expected call of UpdateLocationThumbnail.
func (mr *MockStoreMockRecorder) UpdateLocationThumbnail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLocationThumbnail", reflect.TypeOf((*MockStore)(nil).UpdateLocationThumbnail), arg0, arg1)
}
// UpdatePassword mocks base method.
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
m.ctrl.T.Helper()
@ -289,10 +465,10 @@ func (mr *MockStoreMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.
}
// UpdateUser mocks base method.
func (m *MockStore) UpdateUser(arg0 context.Context, arg1 db.UpdateUserParams) (db.User, error) {
func (m *MockStore) UpdateUser(arg0 context.Context, arg1 db.UpdateUserParams) (db.UpdateUserRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0, arg1)
ret0, _ := ret[0].(db.User)
ret0, _ := ret[0].(db.UpdateUserRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}

10
db/queries/follow.sql Normal file
View File

@ -0,0 +1,10 @@
-- name: AddFollowUser :exec
INSERT INTO user_follow (
follower_id,
followee_id
) VALUES($1, $2);
-- name: RemoveFollowUser :exec
DELETE FROM
user_follow
WHERE follower_id = $1 AND followee_id = $2;

View File

@ -19,17 +19,6 @@ WHERE approved_by IS NOT NULL
ORDER BY l.created_at ASC
LIMIT $1;
-- name: CreateLocation :exec
INSERT INTO locations(
address,
name,
submitted_by,
regency_id,
google_maps_link
) values (
$1, $2, $3, $4, $5
);
-- name: GetLocationTag :many
SELECT
name
@ -38,4 +27,10 @@ WHERE tags_type = 'location'
AND
target_id = $1
AND
approved_by IS NOT NULL;
approved_by IS NOT NULL;
-- name: UpdateLocationThumbnail :exec
UPDATE locations
SET thumbnail = $1
WHERE id = $2;

View File

@ -0,0 +1,8 @@
-- name: CreateNewsEvents :exec
INSERT INTO news_events(
title,
url,
source,
description,
submitted_by
) VALUES ( $1, $2, $3, $4, $5);

6
db/queries/provinces.sql Normal file
View File

@ -0,0 +1,6 @@
-- name: GetListProvinces :many
SELECT
id,
province_name,
region_id
FROM PROVINCES;

6
db/queries/regencies.sql Normal file
View File

@ -0,0 +1,6 @@
-- name: GetListRegencies :many
SELECT
id,
regency_name,
province_id
FROM regencies;

5
db/queries/regions.sql Normal file
View File

@ -0,0 +1,5 @@
-- name: GetListRegions :many
SELECT
id,
region_name
FROM regions;

View File

@ -5,17 +5,13 @@ INSERT INTO users (
) VALUES ($1, $2)
RETURNING *;
-- name: UpdateUser :one
UPDATE users
SET
email = COALESCE(sqlc.narg(email), email),
username = COALESCE(sqlc.narg(username), username),
avatar_picture = COALESCE(sqlc.narg(avatar_picture), avatar_picture)
WHERE
id = sqlc.arg(id)
RETURNING *;
-- name: UpdatePassword :exec
UPDATE users
SET password = $1
WHERE id = $2;
-- name: UpdateAvatar :one
UPDATE users
SET avatar_picture = $1
WHERE id = $2
RETURNING avatar_picture;

43
db/sqlc/follow.sql.go Normal file
View File

@ -0,0 +1,43 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: follow.sql
package db
import (
"context"
)
const addFollowUser = `-- name: AddFollowUser :exec
INSERT INTO user_follow (
follower_id,
followee_id
) VALUES($1, $2)
`
type AddFollowUserParams struct {
FollowerID int32 `json:"follower_id"`
FolloweeID int32 `json:"followee_id"`
}
func (q *Queries) AddFollowUser(ctx context.Context, arg AddFollowUserParams) error {
_, err := q.db.ExecContext(ctx, addFollowUser, arg.FollowerID, arg.FolloweeID)
return err
}
const removeFollowUser = `-- name: RemoveFollowUser :exec
DELETE FROM
user_follow
WHERE follower_id = $1 AND followee_id = $2
`
type RemoveFollowUserParams struct {
FollowerID int32 `json:"follower_id"`
FolloweeID int32 `json:"followee_id"`
}
func (q *Queries) RemoveFollowUser(ctx context.Context, arg RemoveFollowUserParams) error {
_, err := q.db.ExecContext(ctx, removeFollowUser, arg.FollowerID, arg.FolloweeID)
return err
}

View File

@ -2,7 +2,10 @@ package db
import (
"context"
"strings"
"time"
"git.nochill.in/nochill/hiling_go/util"
)
type GetImagesByLocationParams struct {
@ -59,3 +62,38 @@ func (q *Queries) GetImagesByLocation(ctx context.Context, arg GetImagesByLocati
}
return items, nil
}
type CreateImageParams struct {
ImageUrl string `json:"url"`
UploadedBy int32 `json:"uploaded_by"`
ImageType string `json:"image_type"`
ImageOf int32 `json:"image_of"`
}
func (q *Queries) CreateImage(ctx context.Context, arg []CreateImageParams) error {
values := []interface{}{}
queryStr := ` INSERT INTO
images(image_url, uploaded_by, image_type, image_of)
VALUES
`
for _, row := range arg {
queryStr += "(?, ?, ?, ?),"
values = append(values, row.ImageUrl, row.UploadedBy, row.ImageType, row.ImageOf)
}
queryStr = strings.TrimSuffix(queryStr, ",")
// Replacing ? with $n for postgres
queryStr = util.ReplaceSQL(queryStr, "?")
// prepare the statement
stmt, _ := q.db.PrepareContext(ctx, queryStr)
// format all vals at once
_, err := stmt.ExecContext(ctx, values...)
return err
}

View File

@ -46,9 +46,9 @@ func (q *Queries) GetTopListLocations(ctx context.Context, arg GetTopListLocatio
FROM(
SELECT
*,
(SELECT 5 * 4 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) as critic_bayes,
(SELECT 50 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) as user_bayes,
((SELECT 50 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) + (SELECT 5 * 4 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) ) / 2 as avg_bayes
(SELECT 5 * 5 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) as critic_bayes,
(SELECT 5 * 5 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 5 + COALESCE(user_count, 0)) as user_bayes,
((SELECT 5 * 5 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) + (SELECT 5 * 5 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) ) / 2 as avg_bayes
FROM (
SELECT
@ -171,3 +171,53 @@ func (q *Queries) GetLocation(ctx context.Context, location_id int32) (GetLocati
return i, err
}
const createLocation = `-- name: CreateLocation :exec
INSERT INTO locations(
address,
name,
submitted_by,
location_type,
regency_id,
google_maps_link,
approved_by,
is_deleted
) values (
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING id
`
type CreateLocationParams struct {
Address string `json:"address"`
Name string `json:"name"`
SubmittedBy int32 `json:"submitted_by"`
LocationType LocationType `json:"location_type"`
RegencyID int16 `json:"regency_id"`
GoogleMapsLink sql.NullString `json:"google_maps_link"`
IsDeleted bool `json:"is_deleted"`
ApprovedBy sql.NullInt32 `json:"approved_by"`
}
func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) (int32, error) {
row := q.db.QueryRowContext(ctx, createLocation,
arg.Address,
arg.Name,
arg.SubmittedBy,
arg.LocationType,
arg.RegencyID,
arg.GoogleMapsLink,
arg.ApprovedBy,
arg.IsDeleted,
)
var i int32
err := row.Scan(
&i,
)
fmt.Println(i)
return i, err
}

View File

@ -10,39 +10,8 @@ import (
"database/sql"
)
const createLocation = `-- name: CreateLocation :exec
INSERT INTO locations(
address,
name,
submitted_by,
regency_id,
google_maps_link
) values (
$1, $2, $3, $4, $5
)
`
type CreateLocationParams struct {
Address string `json:"address"`
Name string `json:"name"`
SubmittedBy int32 `json:"submitted_by"`
RegencyID int16 `json:"regency_id"`
GoogleMapsLink sql.NullString `json:"google_maps_link"`
}
func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) error {
_, err := q.db.ExecContext(ctx, createLocation,
arg.Address,
arg.Name,
arg.SubmittedBy,
arg.RegencyID,
arg.GoogleMapsLink,
)
return err
}
const getListLocations = `-- name: GetListLocations :many
SELECT id, address, name, google_maps_link, submitted_by, total_visited, thumbnail, regency_id, is_deleted, created_at, updated_at, approved_by, approved_at FROM locations
SELECT id, address, name, google_maps_link, location_type, submitted_by, total_visited, thumbnail, regency_id, is_deleted, created_at, updated_at, approved_by, approved_at FROM locations
`
func (q *Queries) GetListLocations(ctx context.Context) ([]Location, error) {
@ -59,6 +28,7 @@ func (q *Queries) GetListLocations(ctx context.Context) ([]Location, error) {
&i.Address,
&i.Name,
&i.GoogleMapsLink,
&i.LocationType,
&i.SubmittedBy,
&i.TotalVisited,
&i.Thumbnail,
@ -179,3 +149,19 @@ func (q *Queries) GetLocationTag(ctx context.Context, targetID int32) ([]string,
}
return items, nil
}
const updateLocationThumbnail = `-- name: UpdateLocationThumbnail :exec
UPDATE locations
SET thumbnail = $1
WHERE id = $2
`
type UpdateLocationThumbnailParams struct {
Thumbnail sql.NullString `json:"thumbnail"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateLocationThumbnail(ctx context.Context, arg UpdateLocationThumbnailParams) error {
_, err := q.db.ExecContext(ctx, updateLocationThumbnail, arg.Thumbnail, arg.ID)
return err
}

View File

@ -57,6 +57,51 @@ func (ns NullCommentType) Value() (driver.Value, error) {
return string(ns.CommentType), nil
}
type LocationType string
const (
LocationTypeBeach LocationType = "beach"
LocationTypeAmusementpark LocationType = "amusement park"
LocationTypeCulinary LocationType = "culinary"
LocationTypeHikingCamping LocationType = "hiking / camping"
LocationTypeOther LocationType = "other"
)
func (e *LocationType) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = LocationType(s)
case string:
*e = LocationType(s)
default:
return fmt.Errorf("unsupported scan type for LocationType: %T", src)
}
return nil
}
type NullLocationType struct {
LocationType LocationType `json:"location_type"`
Valid bool `json:"valid"` // Valid is true if LocationType is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullLocationType) Scan(value interface{}) error {
if value == nil {
ns.LocationType, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.LocationType.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullLocationType) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.LocationType), nil
}
type UserReportsType string
const (
@ -140,11 +185,12 @@ type Location struct {
Address string `json:"address"`
Name string `json:"name"`
GoogleMapsLink sql.NullString `json:"google_maps_link"`
LocationType LocationType `json:"location_type"`
SubmittedBy int32 `json:"submitted_by"`
TotalVisited sql.NullInt32 `json:"total_visited"`
Thumbnail sql.NullString `json:"thumbnail"`
RegencyID int16 `json:"regency_id"`
IsDeleted sql.NullBool `json:"is_deleted"`
IsDeleted bool `json:"is_deleted"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ApprovedBy sql.NullInt32 `json:"approved_by"`
@ -160,39 +206,54 @@ type LocationImage struct {
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Province struct {
ID int32 `json:"id"`
ProvinceName sql.NullString `json:"province_name"`
RegionID int16 `json:"region_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Regency struct {
type NewsEvent struct {
ID int32 `json:"id"`
RegencyName sql.NullString `json:"regency_name"`
ProvinceID int16 `json:"province_id"`
Title string `json:"title"`
Url string `json:"url"`
Source string `json:"source"`
Thumbnail sql.NullString `json:"thumbnail"`
Description sql.NullString `json:"description"`
IsDeleted bool `json:"is_deleted"`
SubmittedBy int32 `json:"submitted_by"`
ApprovedBy sql.NullInt32 `json:"approved_by"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Province struct {
ID int32 `json:"id"`
ProvinceName string `json:"province_name"`
RegionID int16 `json:"region_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Regency struct {
ID int32 `json:"id"`
RegencyName string `json:"regency_name"`
ProvinceID int16 `json:"province_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Region struct {
ID int32 `json:"id"`
RegionName sql.NullString `json:"region_name"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID int32 `json:"id"`
RegionName string `json:"region_name"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Review struct {
ID int32 `json:"id"`
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"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID int32 `json:"id"`
SubmittedBy int32 `json:"submitted_by"`
Comments string `json:"comments"`
Score int16 `json:"score"`
IsFromCritic bool `json:"is_from_critic"`
CostApprox sql.NullInt32 `json:"cost_approx"`
IsHided bool `json:"is_hided"`
LocationID int32 `json:"location_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
type Tag struct {
@ -222,6 +283,8 @@ type User struct {
SocialMedia pqtype.NullRawMessage `json:"social_media"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
About sql.NullString `json:"about"`
Website sql.NullString `json:"website"`
}
type UserActivity struct {
@ -235,6 +298,14 @@ type UserActivity struct {
UpdatedAt time.Time `json:"updated_at"`
}
type UserFollow struct {
ID int32 `json:"id"`
FollowerID int32 `json:"follower_id"`
FolloweeID int32 `json:"followee_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type UserReport struct {
ID int32 `json:"id"`
Message string `json:"message"`

87
db/sqlc/news_events.go Normal file
View File

@ -0,0 +1,87 @@
package db
import (
"context"
"fmt"
"time"
)
type GetNewsEventsListParams struct {
Limit int32
Offset int32
IsWithApproved string
}
type NewsEventRow struct {
ID int32 `json:"id"`
Title string `json:"title"`
Url string `json:"url"`
Source string `json:"source"`
Thumbnail string `json:"thumbnail"`
Description string `json:"description"`
IsDeleted bool `json:"is_deleted"`
SubmittedBy string `json:"submitted_by"`
ApprovedBy int32 `json:"approved_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (q *Queries) GetNewsEventsList(ctx context.Context, arg GetNewsEventsListParams) ([]NewsEventRow, error) {
getNewsEventsListQ := fmt.Sprintf(`
SELECT
n.id,
n.title,
n.url,
n.source,
COALESCE(n.thumbnail, '') as thumbnail,
COALESCE(n.description, '') as description,
n.is_deleted,
u.username as submitted_by,
COALESCE(n.approved_by, 0) as approved_by,
n.created_at,
n.updated_at
FROM news_events n
JOIN users u ON n.submitted_by = u.id
%s
ORDER BY n.created_at DESC
LIMIT $1
OFFSET $2
`, arg.IsWithApproved)
rows, err := q.db.QueryContext(ctx, getNewsEventsListQ, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []NewsEventRow{}
for rows.Next() {
var i NewsEventRow
if err := rows.Scan(
&i.ID,
&i.Title,
&i.Url,
&i.Source,
&i.Thumbnail,
&i.Description,
&i.IsDeleted,
&i.SubmittedBy,
&i.ApprovedBy,
&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
}

View File

@ -0,0 +1,40 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: news_events.sql
package db
import (
"context"
"database/sql"
)
const createNewsEvents = `-- name: CreateNewsEvents :exec
INSERT INTO news_events(
title,
url,
source,
description,
submitted_by
) VALUES ( $1, $2, $3, $4, $5)
`
type CreateNewsEventsParams struct {
Title string `json:"title"`
Url string `json:"url"`
Source string `json:"source"`
Description sql.NullString `json:"description"`
SubmittedBy int32 `json:"submitted_by"`
}
func (q *Queries) CreateNewsEvents(ctx context.Context, arg CreateNewsEventsParams) error {
_, err := q.db.ExecContext(ctx, createNewsEvents,
arg.Title,
arg.Url,
arg.Source,
arg.Description,
arg.SubmittedBy,
)
return err
}

47
db/sqlc/provinces.sql.go Normal file
View File

@ -0,0 +1,47 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: provinces.sql
package db
import (
"context"
)
const getListProvinces = `-- name: GetListProvinces :many
SELECT
id,
province_name,
region_id
FROM PROVINCES
`
type GetListProvincesRow struct {
ID int32 `json:"id"`
ProvinceName string `json:"province_name"`
RegionID int16 `json:"region_id"`
}
func (q *Queries) GetListProvinces(ctx context.Context) ([]GetListProvincesRow, error) {
rows, err := q.db.QueryContext(ctx, getListProvinces)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetListProvincesRow{}
for rows.Next() {
var i GetListProvincesRow
if err := rows.Scan(&i.ID, &i.ProvinceName, &i.RegionID); 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
}

View File

@ -6,21 +6,28 @@ package db
import (
"context"
"database/sql"
)
type Querier interface {
AddFollowUser(ctx context.Context, arg AddFollowUserParams) error
CheckIfReviewExists(ctx context.Context, arg CheckIfReviewExistsParams) (int64, error)
CreateLocation(ctx context.Context, arg CreateLocationParams) error
CreateNewsEvents(ctx context.Context, arg CreateNewsEventsParams) 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)
GetListProvinces(ctx context.Context) ([]GetListProvincesRow, error)
GetListRecentLocationsWithRatings(ctx context.Context, limit int32) ([]GetListRecentLocationsWithRatingsRow, error)
GetListRegencies(ctx context.Context) ([]GetListRegenciesRow, error)
GetListRegions(ctx context.Context) ([]GetListRegionsRow, 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)
RemoveFollowUser(ctx context.Context, arg RemoveFollowUserParams) error
UpdateAvatar(ctx context.Context, arg UpdateAvatarParams) (sql.NullString, error)
UpdateLocationThumbnail(ctx context.Context, arg UpdateLocationThumbnailParams) error
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
}
var _ Querier = (*Queries)(nil)

47
db/sqlc/regencies.sql.go Normal file
View File

@ -0,0 +1,47 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: regencies.sql
package db
import (
"context"
)
const getListRegencies = `-- name: GetListRegencies :many
SELECT
id,
regency_name,
province_id
FROM regencies
`
type GetListRegenciesRow struct {
ID int32 `json:"id"`
RegencyName string `json:"regency_name"`
ProvinceID int16 `json:"province_id"`
}
func (q *Queries) GetListRegencies(ctx context.Context) ([]GetListRegenciesRow, error) {
rows, err := q.db.QueryContext(ctx, getListRegencies)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetListRegenciesRow{}
for rows.Next() {
var i GetListRegenciesRow
if err := rows.Scan(&i.ID, &i.RegencyName, &i.ProvinceID); 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
}

45
db/sqlc/regions.sql.go Normal file
View File

@ -0,0 +1,45 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: regions.sql
package db
import (
"context"
)
const getListRegions = `-- name: GetListRegions :many
SELECT
id,
region_name
FROM regions
`
type GetListRegionsRow struct {
ID int32 `json:"id"`
RegionName string `json:"region_name"`
}
func (q *Queries) GetListRegions(ctx context.Context) ([]GetListRegionsRow, error) {
rows, err := q.db.QueryContext(ctx, getListRegions)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetListRegionsRow{}
for rows.Next() {
var i GetListRegionsRow
if err := rows.Scan(&i.ID, &i.RegionName); 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
}

View File

@ -3,6 +3,7 @@ package db
import (
"context"
"database/sql"
"fmt"
"github.com/yiplee/sqlc"
)
@ -13,8 +14,14 @@ type Store interface {
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)
UpdateUser(ctx context.Context, arg UpdateUserParams) (UpdateUserRow, error)
GetUserStats(ctx context.Context, user_id int32) (GetUserStatsRow, error)
CreateReview(ctx context.Context, arg CreateReviewParams) (Review, error)
GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error)
CreateLocation(ctx context.Context, arg CreateLocationParams) (int32, error)
CreateImage(ctx context.Context, arg []CreateImageParams) error
CreateLocationTx(ctx context.Context, arg CreateLocationTxParams) error
GetNewsEventsList(ctx context.Context, arg GetNewsEventsListParams) ([]NewsEventRow, error)
}
type SQLStore struct {
@ -30,19 +37,19 @@ func NewStore(db *sql.DB) Store {
}
// TRANSACTION QUERY FUNCTION
// func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) error {
// tx, err := store.db.BeginTx(ctx, nil)
// if err != nil {
// return err
// }
func (store *SQLStore) execTx(ctx context.Context, fn func(*Queries) error) error {
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
return err
}
// q := New(tx)
// err = fn(q)
// if err != nil {
// if rbErr := tx.Rollback(); rbErr != nil {
// return fmt.Errorf("tx err: %v, rb err : %v", err, rbErr)
// }
// return err
// }
// return tx.Commit()
// }
q := New(tx)
err = fn(q)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("tx err: %v, rb err : %v", err, rbErr)
}
return err
}
return tx.Commit()
}

View File

@ -31,6 +31,6 @@ func TestCreateLocation(t *testing.T) {
GoogleMapsLink: sql.NullString{Valid: true, String: util.RandomString(10)},
}
err := testQueries.CreateLocation(context.Background(), arg)
_, err := testQueries.CreateLocation(context.Background(), arg)
require.NoError(t, err)
}

59
db/sqlc/tx_location.go Normal file
View File

@ -0,0 +1,59 @@
package db
import (
"context"
"database/sql"
)
type CreateLocationTxParams struct {
Address string `json:"address"`
Name string `json:"name"`
SubmittedBy int32 `json:"submitted_by"`
LocationType LocationType `json:"location_type"`
RegencyID int16 `json:"regency_id"`
GoogleMapsLink sql.NullString `json:"google_maps_link"`
IsDeleted bool `json:"is_deleted"`
ApprovedBy sql.NullInt32 `json:"approved_by"`
Thumbnail []CreateImageParams `json:"thumbnails"`
}
func (store *SQLStore) CreateLocationTx(ctx context.Context, arg CreateLocationTxParams) error {
err := store.execTx(ctx, func(q *Queries) error {
var err error
location_id, err := q.CreateLocation(ctx, CreateLocationParams{
Address: arg.Address,
Name: arg.Name,
SubmittedBy: arg.SubmittedBy,
LocationType: arg.LocationType,
RegencyID: arg.RegencyID,
GoogleMapsLink: arg.GoogleMapsLink,
IsDeleted: arg.IsDeleted,
ApprovedBy: arg.ApprovedBy,
})
if err != nil {
return err
}
if len(arg.Thumbnail) > 0 {
err := q.CreateImage(ctx, arg.Thumbnail)
if err != nil {
return err
}
err = q.UpdateLocationThumbnail(ctx, UpdateLocationThumbnailParams{
Thumbnail: sql.NullString{Valid: true, String: arg.Thumbnail[0].ImageUrl},
ID: location_id,
})
if err != nil {
return err
}
}
return nil
})
return err
}

View File

@ -3,16 +3,21 @@ package db
import (
"context"
"database/sql"
"encoding/json"
"time"
"github.com/sqlc-dev/pqtype"
)
const getUser = `-- name: GetUser :one
const getUserQ = `-- name: GetUser :one
SELECT
id,
COALESCE(email, '') as email,
password,
username,
COALESCE(google_sign_in_payload, '') as google_sign_in_payload,
COALESCE(about, '') as about,
COALESCE(website, '') as website,
COALESCE(avatar_picture, '') as avatar_picture,
banned_at,
banned_until,
@ -21,35 +26,45 @@ SELECT
is_admin,
is_critics,
is_verified,
social_media
COALESCE(social_media, '[]'),
created_at,
updated_at
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"`
ID int32 `json:"id"`
Email string `json:"email"`
Password string `json:"-"`
About string `json:"about"`
Website string `json:"website"`
Username string `json:"username"`
GoogleSignInPayload string `json:"google_sign_in_payload"`
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 json.RawMessage `json:"social_media"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, error) {
row := q.db.QueryRowContext(ctx, getUser, username)
row := q.db.QueryRowContext(ctx, getUserQ, username)
var i GetUserRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Password,
&i.Username,
&i.GoogleSignInPayload,
&i.About,
&i.Website,
&i.AvatarPicture,
&i.BannedAt,
&i.BannedUntil,
@ -59,6 +74,161 @@ func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, err
&i.IsCritics,
&i.IsVerified,
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getUserStatsQ = `
SELECT
json_agg(ur.*) AS reviews,
( SELECT COUNT(id) FROM user_follow u WHERE u.followee_id = $1) as followers,
( SELECT COUNT(id) FROM reviews r WHERE r.submitted_by = $1 ) as score_count,
( SELECT
json_agg(r1.*) AS scores_distribution
FROM
(
SELECT
*
FROM
( SELECT COUNT(id) as "0" FROM reviews r WHERE r.score >= 0 AND r.score <= 9 AND r.submitted_by = $1) as score0,
( SELECT COUNT(id) as "1" FROM reviews r WHERE r.score >= 10 AND r.score <= 19 AND r.submitted_by = $1) as score1,
( SELECT COUNT(id) as "2" FROM reviews r WHERE r.score >= 20 AND r.score <= 29 AND r.submitted_by = $1) as score2,
( SELECT COUNT(id) as "3" FROM reviews r WHERE r.score >= 30 AND r.score <= 39 AND r.submitted_by = $1) as score3,
( SELECT COUNT(id) as "4" FROM reviews r WHERE r.score >= 40 AND r.score <= 49 AND r.submitted_by = $1) as score4,
( SELECT COUNT(id) as "5" FROM reviews r WHERE r.score >= 50 AND r.score <= 59 AND r.submitted_by = $1) as score5,
( SELECT COUNT(id) as "6" FROM reviews r WHERE r.score >= 60 AND r.score <= 69 AND r.submitted_by = $1) as score6,
( SELECT COUNT(id) as "7" FROM reviews r WHERE r.score >= 70 AND r.score <= 79 AND r.submitted_by = $1) as score7,
( SELECT COUNT(id) as "8" FROM reviews r WHERE r.score >= 80 AND r.score <= 89 AND r.submitted_by = $1) as score8,
( SELECT COUNT(id) as "9" FROM reviews r WHERE r.score >= 90 AND r.score <= 99 AND r.submitted_by = $1) as score9,
( SELECT COUNT(id) as "99" FROM reviews r WHERE r.score = 100 AND r.submitted_by = $1) as score10
) r1
)
FROM (
SELECT
reviews.id,
reviews.comments,
score,
l.name,
p.province_name,
COALESCE(l.thumbnail, '') as thumbnail
FROM
reviews
JOIN locations l on reviews.location_id = l.id
JOIN regencies r on l.regency_id = r.id
JOIN provinces p on r.province_id = p.id
WHERE reviews.submitted_by = $1
ORDER BY score ASC
LIMIT 10
) ur
`
type GetUserStatsRow struct {
Reviews []byte `json:"-"`
Followers int32 `json:"followers"`
ScoreCount int32 `json:"score_count"`
ScoresDistribution []byte `json:"-"`
}
func (q *Queries) GetUserStats(ctx context.Context, user_id int32) (GetUserStatsRow, error) {
var i GetUserStatsRow
row := q.db.QueryRowContext(ctx, getUserStatsQ, user_id)
err := row.Scan(
&i.Reviews,
&i.Followers,
&i.ScoreCount,
&i.ScoresDistribution,
)
return i, err
}
const updateUser = `-- name: UpdateUser :one
UPDATE users
SET
about = $1,
social_media = $2,
website = $3
WHERE
id = $4
RETURNING
id,
COALESCE(email, ''),
username,
COALESCE(avatar_picture, ''),
COALESCE(about, ''),
COALESCE(website, ''),
COALESCE(google_sign_in_payload, ''),
banned_at,
banned_until,
COALESCE(ban_reason, ''),
is_permaban,
is_admin,
is_critics,
is_verified,
is_active,
COALESCE(social_media, '[]'),
created_at,
updated_at
`
type UpdateUserParams struct {
About sql.NullString `json:"about"`
SocialMedia pqtype.NullRawMessage `json:"social_media"`
Website sql.NullString `json:"website"`
ID int32 `json:"id"`
}
type UpdateUserRow struct {
ID int32 `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
AvatarPicture string `json:"avatar_picture"`
About string `json:"about"`
Website string `json:"website"`
GoogleSignInPayload string `json:"google_sign_in_payload"`
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"`
IsActive bool `json:"is_active"`
SocialMedia json.RawMessage `json:"social_media"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (UpdateUserRow, error) {
row := q.db.QueryRowContext(ctx, updateUser,
arg.About,
arg.SocialMedia,
arg.Website,
arg.ID,
)
var i UpdateUserRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.AvatarPicture,
&i.About,
&i.Website,
&i.GoogleSignInPayload,
&i.BannedAt,
&i.BannedUntil,
&i.BanReason,
&i.IsPermaban,
&i.IsAdmin,
&i.IsCritics,
&i.IsVerified,
&i.IsActive,
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -15,7 +15,7 @@ INSERT INTO users (
username,
password
) VALUES ($1, $2)
RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at
RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at, about, website
`
type CreateUserParams struct {
@ -44,10 +44,31 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
&i.About,
&i.Website,
)
return i, err
}
const updateAvatar = `-- name: UpdateAvatar :one
UPDATE users
SET avatar_picture = $1
WHERE id = $2
RETURNING avatar_picture
`
type UpdateAvatarParams struct {
AvatarPicture sql.NullString `json:"avatar_picture"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateAvatar(ctx context.Context, arg UpdateAvatarParams) (sql.NullString, error) {
row := q.db.QueryRowContext(ctx, updateAvatar, arg.AvatarPicture, arg.ID)
var avatar_picture sql.NullString
err := row.Scan(&avatar_picture)
return avatar_picture, err
}
const updatePassword = `-- name: UpdatePassword :exec
UPDATE users
SET password = $1
@ -63,51 +84,3 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams)
_, err := q.db.ExecContext(ctx, updatePassword, arg.Password, arg.ID)
return err
}
const updateUser = `-- name: UpdateUser :one
UPDATE users
SET
email = COALESCE($1, email),
username = COALESCE($2, username),
avatar_picture = COALESCE($3, avatar_picture)
WHERE
id = $4
RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at
`
type UpdateUserParams struct {
Email sql.NullString `json:"email"`
Username sql.NullString `json:"username"`
AvatarPicture sql.NullString `json:"avatar_picture"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, updateUser,
arg.Email,
arg.Username,
arg.AvatarPicture,
arg.ID,
)
var i User
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.Password,
&i.AvatarPicture,
&i.GoogleSignInPayload,
&i.BannedAt,
&i.BannedUntil,
&i.BanReason,
&i.IsPermaban,
&i.IsAdmin,
&i.IsCritics,
&i.IsVerified,
&i.IsActive,
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -5,7 +5,7 @@ sudo -u postgres psql \
-c '\copy regions(id, region_name) FROM '"'/tmp/regions.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy provinces(id, province_name, region_id) FROM '"'/tmp/provinsi.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy regencies(id, province_id, regency_name) FROM '"'/tmp/kabupaten.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy locations(id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy locations(id,location_type,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by,is_deleted) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy reviews(id,submitted_by,is_from_critic,comments,score,is_hided,location_id) FROM '"'/tmp/reviews.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy images(id,image_url,uploaded_by,image_type,image_of) FROM '"'/tmp/images.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy tags(id,name,submitted_by,target_id,tags_type) FROM '"'/tmp/tags.csv'"' DELIMITER '"','"' CSV HEADER;' \
@ -15,7 +15,7 @@ sudo -u postgres psql \
-c '\copy regions(id, region_name) FROM '"'/tmp/regions.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy provinces(id, province_name, region_id) FROM '"'/tmp/provinsi.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy regencies(id, province_id, regency_name) FROM '"'/tmp/kabupaten.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy locations(id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy locations(id,location_type,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by,is_deleted) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy reviews(id,submitted_by,is_from_critic,comments,score,is_hided,location_id) FROM '"'/tmp/reviews.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy images(id,image_url,uploaded_by,image_type,image_of) FROM '"'/tmp/images.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
-c '\copy tags(id,name,submitted_by,target_id,tags_type) FROM '"'/tmp/tags.csv'"' DELIMITER '"','"' CSV HEADER;' \

11
notes
View File

@ -104,4 +104,15 @@ PAGINATION
http://andreyzavadskiy.com/2016/12/03/pagination-and-total-number-of-rows-from-one-select/
##########################################################################################
##########################################################################################
PAGINATION
http://andreyzavadskiy.com/2016/12/03/pagination-and-total-number-of-rows-from-one-select/
https://medium.easyread.co/how-to-do-pagination-in-postgres-with-golang-in-4-common-ways-12365b9fb528?gi=a4683590d1d2
##########################################################################################

14
util/common.go Normal file
View File

@ -0,0 +1,14 @@
package util
import (
"strconv"
"strings"
)
func ReplaceSQL(old, searchPattern string) string {
tmpCount := strings.Count(old, searchPattern)
for m := 1; m <= tmpCount; m++ {
old = strings.Replace(old, searchPattern, "$"+strconv.Itoa(m), 1)
}
return old
}