Compare commits

..

5 Commits

Author SHA1 Message Date
7add2af0c2 update 2023-09-13 21:42:39 +07:00
831c121983 add locations endpoint and fix some tests 2023-09-13 21:42:31 +07:00
a26328c412 fix database, seeder and seeder script 2023-09-13 21:42:04 +07:00
969f680c29 ignore air 2023-09-13 21:41:08 +07:00
e854f2e81f refactor validation error and user register response 2023-09-13 11:30:15 +07:00
23 changed files with 510 additions and 72 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.env *.env
tmp
public public

View File

@ -5,8 +5,8 @@ migrateup:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose up migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose up
migratedown: migratedown:
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down $N migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose down $n
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose down $N migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}_test?sslmode=disable" -verbose down $n
seed: seed:
./import_csv.sh ./import_csv.sh

View File

@ -1,6 +1,44 @@
package api package api
import "github.com/gin-gonic/gin" import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
type APIValidationError struct {
Field string `json:"field"`
Msg string `json:"msg"`
}
func validationErrorMsg(field string, param string, tag string) string {
switch tag {
case "min":
return fmt.Sprintf("%s character min %s", field, param)
case "required":
return fmt.Sprintf("%s is %s", field, tag)
default:
return fmt.Sprintf("%s %s", field, tag)
}
}
func ValidationErrorResponse(err error) gin.H {
var ves validator.ValidationErrors
var temp []APIValidationError
if errors.As(err, &ves) {
out := make([]APIValidationError, len(ves))
for i, ve := range ves {
out[i] = APIValidationError{ve.Field(), validationErrorMsg(ve.Field(), ve.Param(), ve.ActualTag())}
}
temp = out
}
return gin.H{
"message": "Validation error",
"errors": temp,
}
}
func ErrorResponse(err error, msg string) gin.H { func ErrorResponse(err error, msg string) gin.H {
return gin.H{ return gin.H{

120
api/location.go Normal file
View File

@ -0,0 +1,120 @@
package api
import (
"database/sql"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
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"
)
type createLocationReq struct {
Address string `form:"address" binding:"required"`
Name string `form:"name" binding:"required"`
SubmittedBy int32 `form:"submitted_by" binding:"required,number"`
RegencyID int16 `form:"regency_id" binding:"required,number"`
GoogleMapsLink string `form:"google_maps_link"`
}
func (server *Server) createLocation(ctx *gin.Context) {
var req createLocationReq
var imgPath string
var thumbnail, _ = ctx.FormFile("thumbnail")
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)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
if err := ctx.SaveUploadedFile(img, imgPath); err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
return
}
}
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 {
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Something went wrong, code: %s", pqErr.Code.Name())))
return
}
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
return
}
ctx.Writer.WriteHeader(http.StatusOK)
}
type getListLocationsReq struct {
Page int32 `form:"page" binding:"required,min=1"`
PageSize int32 `form:"page_size" binding:"required,min=5"`
}
func (server *Server) getListLocations(ctx *gin.Context) {
var req getListLocationsReq
if err := ctx.ShouldBindQuery(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
arg := db.GetListLocationsParams{
Limit: req.PageSize,
Offset: (req.Page - 1) * req.PageSize,
}
locations, err := server.Store.GetListLocations(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
return
}
ctx.JSON(http.StatusOK, locations)
}
type getLocationReq struct {
ID int32 `uri:"location_id" binding:"required"`
}
func (server *Server) getLocation(ctx *gin.Context) {
var req getLocationReq
if err := ctx.ShouldBindUri(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
location, err := server.Store.GetLocation(ctx, req.ID)
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
return
}
ctx.JSON(http.StatusOK, location)
}

View File

@ -10,9 +10,9 @@ import (
) )
type Server struct { type Server struct {
config util.Config Config util.Config
store db.Store Store db.Store
tokenMaker token.Maker TokenMaker token.Maker
Router *gin.Engine Router *gin.Engine
} }
@ -23,9 +23,9 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
} }
server := &Server{ server := &Server{
config: config, Config: config,
store: store, Store: store,
tokenMaker: tokenMaker, TokenMaker: tokenMaker,
} }
server.getRoutes() server.getRoutes()
@ -37,6 +37,11 @@ func (server *Server) getRoutes() {
router.POST("/user/signup", server.createUser) router.POST("/user/signup", server.createUser)
// LOCATION
router.POST("/locations", server.createLocation)
router.GET("/locations", server.getListLocations)
router.GET("/location/:location_id", server.getLocation)
server.Router = router server.Router = router
} }

31
api/test/location_test.go Normal file
View File

@ -0,0 +1,31 @@
package api_test
import (
"database/sql"
"testing"
db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util"
"go.uber.org/mock/gomock"
)
func TestGetListLocationsAPI(t *testing.T) {
}
func TestCreateLocationAPI(t *testing.T) {
_ = db.CreateLocationParams{
Address: util.RandomString(10),
Name: util.RandomString(10),
SubmittedBy: int32(util.RandomInt(0, 10)),
RegencyID: 1305,
GoogleMapsLink: sql.NullString{Valid: true, String: util.RandomString(10)},
}
t.Run("OK", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// store := mockdb.MockStore
})
}

View File

@ -22,10 +22,10 @@ func TestSignupAPI(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
store.EXPECT(). // store.EXPECT().
CreateUser(gomock.Any(), gomock.Any()). // CreateUser(gomock.Any(), gomock.Any()).
Times(1). // Times(1).
Return(user, nil) // Return(user, nil)
server := newTestServer(t, store) server := newTestServer(t, store)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -56,5 +56,5 @@ func createUser(t *testing.T) (user db.User, password string) {
Password: hashedPassword, Password: hashedPassword,
} }
return return user, passw
} }

View File

@ -2,14 +2,12 @@ package api
import ( import (
"database/sql" "database/sql"
"errors"
"fmt"
"net/http" "net/http"
"time"
db "git.nochill.in/nochill/hiling_go/db/sqlc" db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util" "git.nochill.in/nochill/hiling_go/util"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/lib/pq" "github.com/lib/pq"
) )
@ -25,40 +23,19 @@ type createUserResponse struct {
BannedAt sql.NullTime `json:"banned_at"` BannedAt sql.NullTime `json:"banned_at"`
BannedUntil sql.NullTime `json:"banned_until"` BannedUntil sql.NullTime `json:"banned_until"`
BanReason string `json:"ban_reason"` BanReason string `json:"ban_reason"`
IsPermaban sql.NullBool `json:"is_permaban"` IsPermaban bool `json:"is_permaban"`
IsAdmin sql.NullBool `json:"is_admin"` IsAdmin bool `json:"is_admin"`
IsCritics sql.NullBool `json:"is_critics"` IsCritics bool `json:"is_critics"`
IsVerified sql.NullBool `json:"is_verified"` IsVerified bool `json:"is_verified"`
CreatedAt sql.NullTime `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
}
type ApiError struct {
Field string
Msg string
}
func msgForTag(field string, param string, tag string) string {
switch tag {
case "min":
return fmt.Sprintf("%s character min %s", field, param)
default:
return fmt.Sprintf("%s %s", field, tag)
}
} }
func (server *Server) createUser(ctx *gin.Context) { func (server *Server) createUser(ctx *gin.Context) {
var req createUserRequest var req createUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil { if err := ctx.ShouldBindJSON(&req); err != nil {
if err != nil { if err != nil {
var ve validator.ValidationErrors ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
if errors.As(err, &ve) {
out := make([]ApiError, len(ve))
for i, fe := range ve {
out[i] = ApiError{fe.Field(), msgForTag(fe.Field(), fe.Param(), fe.Tag())}
}
ctx.JSON(http.StatusBadRequest, gin.H{"errors": out})
}
return return
} }
} }
@ -74,12 +51,13 @@ func (server *Server) createUser(ctx *gin.Context) {
Password: hashedPassword, Password: hashedPassword,
} }
user, err := server.store.CreateUser(ctx, arg) user, err := server.Store.CreateUser(ctx, arg)
if err != nil { if err != nil {
if pqErr, ok := err.(*pq.Error); ok { if pqErr, ok := err.(*pq.Error); ok {
switch pqErr.Code.Name() { switch pqErr.Code.Name() {
case "foreign_key_violation", "unique_violation": case "foreign_key_violation", "unique_violation":
ctx.JSON(http.StatusConflict, ErrorResponse(err, "Something went wrong while try to save")) ctx.JSON(http.StatusConflict, ErrorResponse(err, "Username already used"))
return
} }
} }
@ -94,12 +72,12 @@ func (server *Server) createUser(ctx *gin.Context) {
BannedAt: sql.NullTime{Valid: user.BannedAt.Valid, Time: user.BannedAt.Time}, BannedAt: sql.NullTime{Valid: user.BannedAt.Valid, Time: user.BannedAt.Time},
BannedUntil: sql.NullTime{Valid: user.BannedUntil.Valid, Time: user.BannedUntil.Time}, BannedUntil: sql.NullTime{Valid: user.BannedUntil.Valid, Time: user.BannedUntil.Time},
BanReason: user.BanReason.String, BanReason: user.BanReason.String,
IsPermaban: user.IsPermaban, IsPermaban: user.IsPermaban.Bool,
IsAdmin: user.IsAdmin, IsAdmin: user.IsAdmin.Bool,
IsCritics: user.IsCritics, IsCritics: user.IsCritics.Bool,
IsVerified: user.IsVerified, IsVerified: user.IsVerified.Bool,
CreatedAt: sql.NullTime{Valid: true, Time: user.CreatedAt.Time}, CreatedAt: user.CreatedAt.Time,
UpdatedAt: sql.NullTime{Valid: true, Time: user.UpdatedAt.Time}, UpdatedAt: user.UpdatedAt.Time,
} }
ctx.JSON(http.StatusOK, res) ctx.JSON(http.StatusOK, res)

View File

@ -1,10 +1,10 @@
id#address#name#submitted_by#thumbnail#regency_id#google_maps_link 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#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 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 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 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 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 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 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 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 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
1 id#address#name#submitted_by#thumbnail#regency_id#google_maps_link id,address,name,submitted_by,thumbnail,regency_id,google_maps_link,approved_by
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#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
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 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
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 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
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 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
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 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
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 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
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 7#Jl. P. Mangkubumi No.72A Cokrodiningratan Kec. Jetis#Hotel Tentrem Yogyakarta#1#https://cdn.discordapp.com/attachments/743422487882104837/1150987888553623653/image.png#3471#https://www.google.com/maps?q=Hotel+Tentrem+Yogyakarta&source=lmns&entry=mc&bih=1115&biw=2124&hl=en-US&sa=X&ved=2ahUKEwjjl-HHiKSBAxUu5jgGHTU3BiwQ0pQJKAJ6BAgBEAY#1
9 8#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu 8#Moluo Kec.Kwandang#Pulau Saronde Gorontalo#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/0d/ec/58/21/saronde-island-a-place.jpg?w=700&h=-1&s=1#7505#https://www.google.com/maps/place/Pulau+Saronde+Gorontalo/@0.9263376,122.8613201,17z/data=!3m1!4b1!4m6!3m5!1s0x32795bf34dff4467:0xa8beb2a832ae8176!8m2!3d0.9263376!4d122.863895!16s%2Fg%2F11l241cc1d?hl=id&entry=ttu#1
10 9#Dusun Katiet Desa Bosua Kecamatan Sipora#Pantai Katiet#1#https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/d7/fe/f4/mentawai-islands.jpg?w=500&h=-1&s=1#1301#https://www.google.com/maps/place/Katiet,+Bosua,+Sipora+Selatan,+Mentawai+Islands+Regency,+West+Sumatra/@-2.375793,99.848187,15z/data=!3m1!4b1!4m6!3m5!1s0x2fd27efa8363912f:0x8c9c19bd76cba179!8m2!3d-2.375793!4d99.848187!16s%2Fg%2F1tcwz0mt?hl=en-US&entry=ttu 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

View File

@ -12,6 +12,7 @@ CREATE TABLE users(
"is_admin" boolean, "is_admin" boolean,
"is_critics" boolean, "is_critics" boolean,
"is_verified" boolean, "is_verified" boolean,
"is_active" boolean,
"social_media" jsonb, "social_media" jsonb,
"created_at" timestamp default(now()), "created_at" timestamp default(now()),
"updated_at" timestamp default(now()) "updated_at" timestamp default(now())
@ -80,7 +81,7 @@ CREATE TABLE locations(
"google_maps_link" varchar, "google_maps_link" varchar,
"submitted_by" integer references "users"("id") not null, "submitted_by" integer references "users"("id") not null,
"total_visited" integer, "total_visited" integer,
"thumbnail" varchar not null, "thumbnail" varchar,
"regency_id" smallint references "regencies"("id") not null, "regency_id" smallint references "regencies"("id") not null,
"is_deleted" boolean, "is_deleted" boolean,
"created_at" timestamp default(now()), "created_at" timestamp default(now()),

View File

@ -0,0 +1,2 @@
ALTER TABLE locations DROP COLUMN IF EXISTS approved_by;
ALTER TABLE locations DROP COLUMN IF EXISTS approved_at;

View File

@ -0,0 +1,2 @@
ALTER TABLE locations ADD column approved_by int references "users"("id");
ALTER TABLE locations ADD column approved_at timestamp;

View File

@ -35,6 +35,20 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder return m.recorder
} }
// CreateLocation mocks base method.
func (m *MockStore) CreateLocation(arg0 context.Context, arg1 db.CreateLocationParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLocation", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// 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)
}
// CreateUser mocks base method. // CreateUser mocks base method.
func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) { func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -50,6 +64,36 @@ func (mr *MockStoreMockRecorder) CreateUser(arg0, arg1 interface{}) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0, arg1)
} }
// GetListLocations mocks base method.
func (m *MockStore) GetListLocations(arg0 context.Context, arg1 db.GetListLocationsParams) ([]db.Location, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetListLocations", arg0, arg1)
ret0, _ := ret[0].([]db.Location)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetListLocations indicates an expected call of GetListLocations.
func (mr *MockStoreMockRecorder) GetListLocations(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocations", reflect.TypeOf((*MockStore)(nil).GetListLocations), arg0, arg1)
}
// GetLocation mocks base method.
func (m *MockStore) GetLocation(arg0 context.Context, arg1 int32) (db.Location, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLocation", arg0, arg1)
ret0, _ := ret[0].(db.Location)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetLocation indicates an expected call of GetLocation.
func (mr *MockStoreMockRecorder) GetLocation(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocation", reflect.TypeOf((*MockStore)(nil).GetLocation), arg0, arg1)
}
// UpdatePassword mocks base method. // UpdatePassword mocks base method.
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error { func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -0,0 +1,19 @@
-- name: GetListLocations :many
SELECT * FROM locations
LIMIT $1
OFFSET $2;
-- name: GetLocation :one
SELECT * FROM locations
WHERE id = $1;
-- name: CreateLocation :exec
INSERT INTO locations(
address,
name,
submitted_by,
regency_id,
google_maps_link
) values (
$1, $2, $3, $4, $5
);

116
db/sqlc/locations.sql.go Normal file
View File

@ -0,0 +1,116 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.20.0
// source: locations.sql
package db
import (
"context"
"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
LIMIT $1
OFFSET $2
`
type GetListLocationsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) GetListLocations(ctx context.Context, arg GetListLocationsParams) ([]Location, error) {
rows, err := q.db.QueryContext(ctx, getListLocations, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Location{}
for rows.Next() {
var i Location
if err := rows.Scan(
&i.ID,
&i.Address,
&i.Name,
&i.GoogleMapsLink,
&i.SubmittedBy,
&i.TotalVisited,
&i.Thumbnail,
&i.RegencyID,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
&i.ApprovedBy,
&i.ApprovedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLocation = `-- name: GetLocation :one
SELECT id, address, name, google_maps_link, submitted_by, total_visited, thumbnail, regency_id, is_deleted, created_at, updated_at, approved_by, approved_at FROM locations
WHERE id = $1
`
func (q *Queries) GetLocation(ctx context.Context, id int32) (Location, error) {
row := q.db.QueryRowContext(ctx, getLocation, id)
var i Location
err := row.Scan(
&i.ID,
&i.Address,
&i.Name,
&i.GoogleMapsLink,
&i.SubmittedBy,
&i.TotalVisited,
&i.Thumbnail,
&i.RegencyID,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
&i.ApprovedBy,
&i.ApprovedAt,
)
return i, err
}

View File

@ -126,8 +126,8 @@ type Comment struct {
type Location struct { type Location struct {
ID int32 `json:"id"` ID int32 `json:"id"`
Address sql.NullString `json:"address"` Address string `json:"address"`
Name sql.NullString `json:"name"` Name string `json:"name"`
GoogleMapsLink sql.NullString `json:"google_maps_link"` GoogleMapsLink sql.NullString `json:"google_maps_link"`
SubmittedBy int32 `json:"submitted_by"` SubmittedBy int32 `json:"submitted_by"`
TotalVisited sql.NullInt32 `json:"total_visited"` TotalVisited sql.NullInt32 `json:"total_visited"`
@ -136,6 +136,8 @@ type Location struct {
IsDeleted sql.NullBool `json:"is_deleted"` IsDeleted sql.NullBool `json:"is_deleted"`
CreatedAt sql.NullTime `json:"created_at"` CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt sql.NullTime `json:"updated_at"`
ApprovedBy sql.NullInt32 `json:"approved_by"`
ApprovedAt sql.NullTime `json:"approved_at"`
} }
type LocationImage struct { type LocationImage struct {
@ -204,6 +206,7 @@ type User struct {
IsAdmin sql.NullBool `json:"is_admin"` IsAdmin sql.NullBool `json:"is_admin"`
IsCritics sql.NullBool `json:"is_critics"` IsCritics sql.NullBool `json:"is_critics"`
IsVerified sql.NullBool `json:"is_verified"` IsVerified sql.NullBool `json:"is_verified"`
IsActive sql.NullBool `json:"is_active"`
SocialMedia pqtype.NullRawMessage `json:"social_media"` SocialMedia pqtype.NullRawMessage `json:"social_media"`
CreatedAt sql.NullTime `json:"created_at"` CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt sql.NullTime `json:"updated_at"`

View File

@ -9,7 +9,10 @@ import (
) )
type Querier interface { type Querier interface {
CreateLocation(ctx context.Context, arg CreateLocationParams) error
CreateUser(ctx context.Context, arg CreateUserParams) (User, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
GetListLocations(ctx context.Context, arg GetListLocationsParams) ([]Location, error)
GetLocation(ctx context.Context, id int32) (Location, error)
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
} }

View File

@ -0,0 +1,41 @@
package db_test
import (
"context"
"database/sql"
"testing"
db "git.nochill.in/nochill/hiling_go/db/sqlc"
"git.nochill.in/nochill/hiling_go/util"
"github.com/stretchr/testify/require"
)
func TestGetLocationsList(t *testing.T) {
arg := db.GetListLocationsParams{
Limit: 10,
Offset: 0,
}
locations, err := testQueries.GetListLocations(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, locations)
}
func TestGetLocation(t *testing.T) {
location, err := testQueries.GetLocation(context.Background(), 1)
require.NoError(t, err)
require.NotEmpty(t, location)
}
func TestCreateLocation(t *testing.T) {
arg := db.CreateLocationParams{
Address: util.RandomString(12),
Name: util.RandomString(10),
SubmittedBy: 1,
RegencyID: 1305,
GoogleMapsLink: sql.NullString{Valid: true, String: util.RandomString(10)},
}
err := testQueries.CreateLocation(context.Background(), arg)
require.NoError(t, err)
}

View File

@ -11,7 +11,7 @@ import (
func TestCreateUser(t *testing.T) { func TestCreateUser(t *testing.T) {
arg := db.CreateUserParams{ arg := db.CreateUserParams{
Username: util.RandomString(10), Username: util.RandomString(7),
Password: util.RandomString(10), Password: util.RandomString(10),
} }

View File

@ -15,7 +15,7 @@ INSERT INTO users (
username, username,
password password
) VALUES ($1, $2) ) 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, 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
` `
type CreateUserParams struct { type CreateUserParams struct {
@ -40,6 +40,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
&i.IsAdmin, &i.IsAdmin,
&i.IsCritics, &i.IsCritics,
&i.IsVerified, &i.IsVerified,
&i.IsActive,
&i.SocialMedia, &i.SocialMedia,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
@ -71,7 +72,7 @@ SET
avatar_picture = COALESCE($3, avatar_picture) avatar_picture = COALESCE($3, avatar_picture)
WHERE WHERE
id = $4 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, 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
` `
type UpdateUserParams struct { type UpdateUserParams struct {
@ -103,6 +104,7 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, e
&i.IsAdmin, &i.IsAdmin,
&i.IsCritics, &i.IsCritics,
&i.IsVerified, &i.IsVerified,
&i.IsActive,
&i.SocialMedia, &i.SocialMedia,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,

View File

@ -5,12 +5,26 @@ sudo -u postgres psql \
-c '\copy regions(id, region_name) FROM '"'/tmp/regions.csv'"' DELIMITER '"','"' CSV HEADER;' \ -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 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 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) FROM '"'/tmp/locations.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;' \
-d hiling_dev && -d hiling_dev &&
sudo -u postgres psql \ sudo -u postgres psql \
-c '\copy users(id,username,password) FROM '"'/tmp/user.csv'"' DELIMITER '"','"' CSV HEADER;' \ -c '\copy users(id,username,password) FROM '"'/tmp/user.csv'"' DELIMITER '"','"' CSV HEADER;' \
-c '\copy regions(id, region_name) FROM '"'/tmp/regions.csv'"' DELIMITER '"','"' CSV HEADER;' \ -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 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 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) FROM '"'/tmp/locations.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;' \
-d hiling_dev_test -d hiling_dev_test
# FIXING SEQUENCES AFTER SEEDING
sudo -u postgres psql \
-c 'SELECT setval('"'locations_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'locations_id_seq'"'))-1 FROM locations))' \
-d hiling_dev_test &&
sudo -u postgres psql \
-c 'SELECT setval('"'users_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'users_id_seq'"'))-1 FROM users))' \
-d hiling_dev_test
sudo -u postgres psql \
-c 'SELECT setval('"'locations_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'locations_id_seq'"'))-1 FROM locations))' \
-d hiling_dev &&
sudo -u postgres psql \
-c 'SELECT setval('"'users_id_seq'"',(SELECT GREATEST(MAX(id)+1,nextval('"'users_id_seq'"'))-1 FROM users))' \
-d hiling_dev

15
notes
View File

@ -43,4 +43,19 @@ https://github.com/gin-gonic/gin/issues/430 (using middleware)
tbh i'd raher use like a wrapper instead of middleware but we'll see tbh i'd raher use like a wrapper instead of middleware but we'll see
##########################################################################################
##########################################################################################
WITHOUT aUTHORIZATION ->
- AUTH
- GET LOCATIONS
- POST,PATCH,GET COMMENTS
- GET REVIEWS
- POST, REVIEWS ?
THE REST SHOUDL HAVE AUTHORIVAOZOIANITON
########################################################################################## ##########################################################################################

View File

@ -1,3 +1,6 @@
when user open hiling site, for the firstime frontend or web send an user ip address to server(why ? so they don't have multiple accounts on 1 device, ofc we can limit the thing but i think having 1 account per device is good practice so user not abusing) after that they gonna the page, if they navigate to the homepage they gonna see the index page when user open hiling site, for the firstime frontend or web send an user ip address to server(why ? so they don't have multiple accounts on 1 device, ofc we can limit the thing but i think having 1 account per device is good practice so user not abusing) after that they gonna the page, if they navigate to the homepage they gonna see the index page
from there user can click the locations, login, see another user reviews or any other acitivites from there user can click the locations, login, see another user reviews or any other acitivites
user can submit user review/rating without login since the system already save user ip address but the user cant see about their reviews, likes, or saved location, user have to login first to see about user reviews, likes, saved location, following etc etc user can submit user review/rating without login since the system already save user ip address but the user cant see about their reviews, likes, or saved location, user have to login first to see about user reviews, likes, saved location, following etc etc
OK, so if user want to submit a location it has go through process where moderator approved the submition then it's live, and it gotta mention people who contributes, mf are neanderthals