Compare commits
6 Commits
ed85ece58a
...
dfe00f8359
Author | SHA1 | Date | |
---|---|---|---|
dfe00f8359 | |||
dcacc9288d | |||
d1ba0251c0 | |||
4f962bb104 | |||
96dfa4ae29 | |||
ed608f170a |
7
Makefile
7
Makefile
@ -2,12 +2,17 @@ include dev.env
|
|||||||
|
|
||||||
migrateup:
|
migrateup:
|
||||||
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" -verbose up
|
migrate -path db/migrations -database "${DB_TYPE}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?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
|
||||||
|
|
||||||
|
seed:
|
||||||
|
./import_csv.sh
|
||||||
|
|
||||||
mock-generate:
|
mock-generate:
|
||||||
mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/naice_pos/db/sqlc Store
|
mockgen -package mockdb -destination db/mock/store.go git.nochill.in/nochill/hiling_go/db/sqlc Store
|
||||||
|
|
||||||
sqlc:
|
sqlc:
|
||||||
sqlc generate && make mock-generate
|
sqlc generate && make mock-generate
|
||||||
|
@ -2,14 +2,14 @@ package api
|
|||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
func errorResponse(err error, msg string) gin.H {
|
func ErrorResponse(err error, msg string) gin.H {
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"message": msg,
|
"message": msg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validResponse(data interface{}, msg string) gin.H {
|
func ValidResponse(data interface{}, msg string) gin.H {
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"message": msg,
|
"message": msg,
|
||||||
"data": data,
|
"data": data,
|
||||||
|
@ -13,7 +13,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config util.Config, store db.Store) (*Server, error) {
|
func NewServer(config util.Config, store db.Store) (*Server, error) {
|
||||||
@ -35,11 +35,11 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
|
|||||||
func (server *Server) getRoutes() {
|
func (server *Server) getRoutes() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.POST("/user/create", server.createUser)
|
router.POST("/user/signup", server.createUser)
|
||||||
|
|
||||||
server.router = router
|
server.Router = router
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Start(address string) error {
|
func (server *Server) Start(address string) error {
|
||||||
return server.router.Run(address)
|
return server.Router.Run(address)
|
||||||
}
|
}
|
||||||
|
32
api/test/main_test.go
Normal file
32
api/test/main_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package api_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
api "git.nochill.in/nochill/hiling_go/api"
|
||||||
|
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"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestServer(t *testing.T, store db.Store) *api.Server {
|
||||||
|
config := util.Config{
|
||||||
|
TokenSymmetricKey: util.RandomString(32),
|
||||||
|
TokenDuration: time.Minute,
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := api.NewServer(config, store)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
60
api/test/user_test.go
Normal file
60
api/test/user_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package api_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
mockdb "git.nochill.in/nochill/hiling_go/db/mock"
|
||||||
|
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||||
|
"git.nochill.in/nochill/hiling_go/util"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSignupAPI(t *testing.T) {
|
||||||
|
user, pass := createUser(t)
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
store := mockdb.NewMockStore(ctrl)
|
||||||
|
store.EXPECT().
|
||||||
|
CreateUser(gomock.Any(), gomock.Any()).
|
||||||
|
Times(1).
|
||||||
|
Return(user, nil)
|
||||||
|
|
||||||
|
server := newTestServer(t, store)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
data, err := json.Marshal(gin.H{
|
||||||
|
"username": user.Username,
|
||||||
|
"password": pass,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
url := "/user/signup"
|
||||||
|
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
server.Router.ServeHTTP(recorder, request)
|
||||||
|
require.Equal(t, http.StatusOK, recorder.Code)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUser(t *testing.T) (user db.User, password string) {
|
||||||
|
passw := util.RandomString(10)
|
||||||
|
hashedPassword, err := util.HashPassword(passw)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
user = db.User{
|
||||||
|
Username: util.RandomString(10),
|
||||||
|
Password: hashedPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
95
api/user.go
95
api/user.go
@ -1,27 +1,106 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateUserRequest struct {
|
type createUserRequest struct {
|
||||||
ClientIpV4 string `json:"client_ip" binding:"required"`
|
Username string `json:"username" binding:"required,alphanum"`
|
||||||
|
Password string `json:"password" binding:"required,min=7"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type createUserResponse struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
AvatarPicture string `json:"avatar_picture"` // avatar_url
|
||||||
|
BannedAt sql.NullTime `json:"banned_at"`
|
||||||
|
BannedUntil sql.NullTime `json:"banned_until"`
|
||||||
|
BanReason string `json:"ban_reason"`
|
||||||
|
IsPermaban sql.NullBool `json:"is_permaban"`
|
||||||
|
IsAdmin sql.NullBool `json:"is_admin"`
|
||||||
|
IsCritics sql.NullBool `json:"is_critics"`
|
||||||
|
IsVerified sql.NullBool `json:"is_verified"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `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 {
|
||||||
ctx.JSON(http.StatusBadRequest, errorResponse(err, ""))
|
if err != nil {
|
||||||
return
|
var ve validator.ValidationErrors
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := server.store.CreateUser(ctx, req.ClientIpV4)
|
hashedPassword, err := util.HashPassword(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, errorResponse(err, ""))
|
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Writer.WriteHeader(http.StatusOK)
|
arg := db.CreateUserParams{
|
||||||
|
Username: req.Username,
|
||||||
|
Password: hashedPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := server.store.CreateUser(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
if pqErr, ok := err.(*pq.Error); ok {
|
||||||
|
switch pqErr.Code.Name() {
|
||||||
|
case "foreign_key_violation", "unique_violation":
|
||||||
|
ctx.JSON(http.StatusConflict, ErrorResponse(err, "Something went wrong while try to save"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := createUserResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
AvatarPicture: user.AvatarPicture.String,
|
||||||
|
BannedAt: sql.NullTime{Valid: user.BannedAt.Valid, Time: user.BannedAt.Time},
|
||||||
|
BannedUntil: sql.NullTime{Valid: user.BannedUntil.Valid, Time: user.BannedUntil.Time},
|
||||||
|
BanReason: user.BanReason.String,
|
||||||
|
IsPermaban: user.IsPermaban,
|
||||||
|
IsAdmin: user.IsAdmin,
|
||||||
|
IsCritics: user.IsCritics,
|
||||||
|
IsVerified: user.IsVerified,
|
||||||
|
CreatedAt: sql.NullTime{Valid: true, Time: user.CreatedAt.Time},
|
||||||
|
UpdatedAt: sql.NullTime{Valid: true, Time: user.UpdatedAt.Time},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
10
db/csv_seeder/locations.csv
Normal file
10
db/csv_seeder/locations.csv
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
id#address#name#submitted_by#thumbnail#regency_id#google_maps_link
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
|
2
db/csv_seeder/user.csv
Normal file
2
db/csv_seeder/user.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
id,username,password
|
||||||
|
1,user123,password
|
|
@ -1,4 +1,7 @@
|
|||||||
DROP TABLE IF EXISTS location_images;
|
DROP TABLE IF EXISTS location_images;
|
||||||
|
DROP TABLE IF EXISTS comments;
|
||||||
|
DROP TABLE IF EXISTS client_ips;
|
||||||
|
DROP TABLE IF EXISTS user_reports;
|
||||||
DROP TABLE IF EXISTS reviews;
|
DROP TABLE IF EXISTS reviews;
|
||||||
DROP TABLE IF EXISTS locations;
|
DROP TABLE IF EXISTS locations;
|
||||||
DROP TABLE IF EXISTS regencies;
|
DROP TABLE IF EXISTS regencies;
|
||||||
@ -6,3 +9,7 @@ DROP TABLE IF EXISTS provinces;
|
|||||||
DROP TABLE IF EXISTS regions;
|
DROP TABLE IF EXISTS regions;
|
||||||
DROP TABLE IF EXISTS tags;
|
DROP TABLE IF EXISTS tags;
|
||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
||||||
|
|
||||||
|
|
||||||
|
DROP TYPE IF EXISTS user_reports_type;
|
||||||
|
DROP TYPE IF EXISTS comment_type;
|
@ -1,20 +1,54 @@
|
|||||||
CREATE TABLE users(
|
CREATE TABLE users(
|
||||||
"id" serial primary key not null,
|
"id" serial primary key not null,
|
||||||
"email" varchar unique,
|
"email" varchar unique,
|
||||||
"username" varchar unique,
|
"username" varchar unique not null,
|
||||||
"password" varchar,
|
"password" varchar not null,
|
||||||
"avatar_picture" varchar,
|
"avatar_picture" varchar,
|
||||||
"google_sign_in_payload" varchar,
|
"google_sign_in_payload" varchar,
|
||||||
"banned_at" timestamp,
|
"banned_at" timestamp,
|
||||||
"banned_until" timestamp,
|
"banned_until" timestamp,
|
||||||
"is_admin" boolean,
|
"ban_reason" varchar,
|
||||||
"is_critics" boolean,
|
"is_permaban" boolean,
|
||||||
"is_verified" boolean,
|
"is_admin" boolean,
|
||||||
"ipv4_address" varchar(15) not null,
|
"is_critics" boolean,
|
||||||
"social_media" jsonb,
|
"is_verified" boolean,
|
||||||
"created_at" timestamp default(now()),
|
"social_media" jsonb,
|
||||||
"updated_at" timestamp default(now())
|
"created_at" timestamp default(now()),
|
||||||
);
|
"updated_at" timestamp default(now())
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE client_ips(
|
||||||
|
"id" serial primary key not null,
|
||||||
|
"ipv4" varchar(15) not null,
|
||||||
|
"ipv6" varchar(40),
|
||||||
|
"banned_at" timestamp,
|
||||||
|
"banned_until" timestamp,
|
||||||
|
"reason" varchar,
|
||||||
|
"is_permaban" boolean,
|
||||||
|
"created_at" timestamp default(now()),
|
||||||
|
"updated_at" timestamp default(now())
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TYPE user_reports_type as ENUM(
|
||||||
|
'comments',
|
||||||
|
'reviews',
|
||||||
|
'locations',
|
||||||
|
'users',
|
||||||
|
'stories'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_reports(
|
||||||
|
"id" serial primary key not null,
|
||||||
|
"message" text not null,
|
||||||
|
"date" timestamp not null,
|
||||||
|
"report_target" integer not null,
|
||||||
|
"report_type" user_reports_type not null,
|
||||||
|
"submitted_by" integer references "users"("id") not null,
|
||||||
|
"created_at" timestamp default(now()),
|
||||||
|
"updated_at" timestamp default(now())
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE regions(
|
CREATE TABLE regions(
|
||||||
"id" serial primary key not null,
|
"id" serial primary key not null,
|
||||||
@ -41,12 +75,12 @@ CREATE TABLE regencies(
|
|||||||
|
|
||||||
CREATE TABLE locations(
|
CREATE TABLE locations(
|
||||||
"id" serial primary key not null,
|
"id" serial primary key not null,
|
||||||
"address" varchar,
|
"address" varchar not null,
|
||||||
"name" varchar,
|
"name" varchar not null,
|
||||||
"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,
|
"thumbnail" varchar not null,
|
||||||
"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()),
|
||||||
@ -80,5 +114,21 @@ CREATE TABLE reviews (
|
|||||||
"location_id" integer references "locations"("id") not null,
|
"location_id" integer references "locations"("id") not null,
|
||||||
"created_at" timestamp default(now()),
|
"created_at" timestamp default(now()),
|
||||||
"updated_at" timestamp default(now())
|
"updated_at" timestamp default(now())
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TYPE comment_type AS ENUM(
|
||||||
|
'stories',
|
||||||
|
'news',
|
||||||
|
'reviews'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE comments(
|
||||||
|
"id" serial primary key not null,
|
||||||
|
"submitted_by" integer not null,
|
||||||
|
"comment_on" integer not null,
|
||||||
|
"comment_type" comment_type not null,
|
||||||
|
"reply_to" integer,
|
||||||
|
"is_hide" boolean,
|
||||||
|
"created_at" timestamp default(now()),
|
||||||
|
"updated_at" timestamp default(now())
|
||||||
);
|
);
|
80
db/mock/store.go
Normal file
80
db/mock/store.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: git.nochill.in/nochill/hiling_go/db/sqlc (interfaces: Store)
|
||||||
|
|
||||||
|
// Package mockdb is a generated GoMock package.
|
||||||
|
package mockdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockStore is a mock of Store interface.
|
||||||
|
type MockStore struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockStoreMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockStoreMockRecorder is the mock recorder for MockStore.
|
||||||
|
type MockStoreMockRecorder struct {
|
||||||
|
mock *MockStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockStore creates a new mock instance.
|
||||||
|
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||||
|
mock := &MockStore{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockStoreMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser mocks base method.
|
||||||
|
func (m *MockStore) CreateUser(arg0 context.Context, arg1 db.CreateUserParams) (db.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateUser", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(db.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser indicates an expected call of CreateUser.
|
||||||
|
func (mr *MockStoreMockRecorder) CreateUser(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePassword mocks base method.
|
||||||
|
func (m *MockStore) UpdatePassword(arg0 context.Context, arg1 db.UpdatePasswordParams) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePassword indicates an expected call of UpdatePassword.
|
||||||
|
func (mr *MockStoreMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*MockStore)(nil).UpdatePassword), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser mocks base method.
|
||||||
|
func (m *MockStore) UpdateUser(arg0 context.Context, arg1 db.UpdateUserParams) (db.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateUser", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(db.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser indicates an expected call of UpdateUser.
|
||||||
|
func (mr *MockStoreMockRecorder) UpdateUser(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockStore)(nil).UpdateUser), arg0, arg1)
|
||||||
|
}
|
@ -1,6 +1,22 @@
|
|||||||
-- name: CreateUser :exec
|
-- name: CreateUser :one
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
ipv4_address
|
username,
|
||||||
) VALUES (
|
password
|
||||||
$1
|
) 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;
|
||||||
|
@ -6,10 +6,124 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sqlc-dev/pqtype"
|
"github.com/sqlc-dev/pqtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CommentType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommentTypeStories CommentType = "stories"
|
||||||
|
CommentTypeNews CommentType = "news"
|
||||||
|
CommentTypeReviews CommentType = "reviews"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *CommentType) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = CommentType(s)
|
||||||
|
case string:
|
||||||
|
*e = CommentType(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for CommentType: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullCommentType struct {
|
||||||
|
CommentType CommentType `json:"comment_type"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if CommentType is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullCommentType) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.CommentType, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.CommentType.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullCommentType) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.CommentType), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserReportsType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserReportsTypeComments UserReportsType = "comments"
|
||||||
|
UserReportsTypeReviews UserReportsType = "reviews"
|
||||||
|
UserReportsTypeLocations UserReportsType = "locations"
|
||||||
|
UserReportsTypeUsers UserReportsType = "users"
|
||||||
|
UserReportsTypeStories UserReportsType = "stories"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *UserReportsType) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = UserReportsType(s)
|
||||||
|
case string:
|
||||||
|
*e = UserReportsType(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for UserReportsType: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullUserReportsType struct {
|
||||||
|
UserReportsType UserReportsType `json:"user_reports_type"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if UserReportsType is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullUserReportsType) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.UserReportsType, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.UserReportsType.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullUserReportsType) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.UserReportsType), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientIp struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Ipv4 string `json:"ipv4"`
|
||||||
|
Ipv6 sql.NullString `json:"ipv6"`
|
||||||
|
BannedAt sql.NullTime `json:"banned_at"`
|
||||||
|
BannedUntil sql.NullTime `json:"banned_until"`
|
||||||
|
Reason sql.NullString `json:"reason"`
|
||||||
|
IsPermaban sql.NullBool `json:"is_permaban"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
SubmittedBy int32 `json:"submitted_by"`
|
||||||
|
CommentOn int32 `json:"comment_on"`
|
||||||
|
CommentType CommentType `json:"comment_type"`
|
||||||
|
ReplyTo sql.NullInt32 `json:"reply_to"`
|
||||||
|
IsHide sql.NullBool `json:"is_hide"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
Address sql.NullString `json:"address"`
|
Address sql.NullString `json:"address"`
|
||||||
@ -79,17 +193,29 @@ type Tag struct {
|
|||||||
type User struct {
|
type User struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
Email sql.NullString `json:"email"`
|
Email sql.NullString `json:"email"`
|
||||||
Username sql.NullString `json:"username"`
|
Username string `json:"username"`
|
||||||
Password sql.NullString `json:"password"`
|
Password string `json:"password"`
|
||||||
AvatarPicture sql.NullString `json:"avatar_picture"`
|
AvatarPicture sql.NullString `json:"avatar_picture"`
|
||||||
GoogleSignInPayload sql.NullString `json:"google_sign_in_payload"`
|
GoogleSignInPayload sql.NullString `json:"google_sign_in_payload"`
|
||||||
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 sql.NullString `json:"ban_reason"`
|
||||||
|
IsPermaban sql.NullBool `json:"is_permaban"`
|
||||||
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"`
|
||||||
Ipv4Address string `json:"ipv4_address"`
|
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserReport struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
ReportTarget int32 `json:"report_target"`
|
||||||
|
ReportType UserReportsType `json:"report_type"`
|
||||||
|
SubmittedBy int32 `json:"submitted_by"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
}
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
CreateUser(ctx context.Context, ipv4Address string) error
|
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||||
|
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
||||||
|
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Querier = (*Queries)(nil)
|
var _ Querier = (*Queries)(nil)
|
||||||
|
32
db/sqlc/test/main_test.go
Normal file
32
db/sqlc/test/main_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||||
|
"git.nochill.in/nochill/hiling_go/util"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testQueries *db.Queries
|
||||||
|
var testDB *sql.DB
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
config, err := util.LoadConfig("../../..")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("cannot load config: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testDB, err = sql.Open(config.DBDriver, config.DBSourceTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("cannot connect db: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testQueries = db.New(testDB)
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
23
db/sqlc/test/users_test.go
Normal file
23
db/sqlc/test/users_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||||
|
"git.nochill.in/nochill/hiling_go/util"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
arg := db.CreateUserParams{
|
||||||
|
Username: util.RandomString(10),
|
||||||
|
Password: util.RandomString(10),
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := testQueries.CreateUser(context.Background(), arg)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, user.Username, arg.Username)
|
||||||
|
require.Equal(t, user.Password, arg.Password)
|
||||||
|
}
|
@ -7,17 +7,105 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
const createUser = `-- name: CreateUser :exec
|
const createUser = `-- name: CreateUser :one
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
ipv4_address
|
username,
|
||||||
) VALUES (
|
password
|
||||||
$1
|
) 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
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) CreateUser(ctx context.Context, ipv4Address string) error {
|
type CreateUserParams struct {
|
||||||
_, err := q.db.ExecContext(ctx, createUser, ipv4Address)
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createUser, arg.Username, arg.Password)
|
||||||
|
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.SocialMedia,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePassword = `-- name: UpdatePassword :exec
|
||||||
|
UPDATE users
|
||||||
|
SET password = $1
|
||||||
|
WHERE id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePasswordParams struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updatePassword, arg.Password, arg.ID)
|
||||||
return err
|
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, 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.SocialMedia,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -45,6 +45,7 @@ require (
|
|||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
go.uber.org/mock v0.2.0 // indirect
|
||||||
golang.org/x/arch v0.5.0 // indirect
|
golang.org/x/arch v0.5.0 // indirect
|
||||||
golang.org/x/crypto v0.13.0 // indirect
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
golang.org/x/net v0.15.0 // indirect
|
golang.org/x/net v0.15.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -241,6 +241,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
|
||||||
|
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
16
import_csv.sh
Executable file
16
import_csv.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
cp ./db/csv_seeder/* /tmp/
|
||||||
|
sudo -u postgres psql \
|
||||||
|
-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 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) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||||
|
-d hiling_dev &&
|
||||||
|
sudo -u postgres psql \
|
||||||
|
-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 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) FROM '"'/tmp/locations.csv'"' DELIMITER '"'#'"' CSV HEADER;' \
|
||||||
|
-d hiling_dev_test
|
11
notes
11
notes
@ -33,3 +33,14 @@ https://en.wikipedia.org/wiki/Provinces_of_Indonesia
|
|||||||
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
######################### CUSTOM GIN VALIDATION ERR MESSAGE ##############################
|
||||||
|
|
||||||
|
make custom err message, and get all the type, the err message sucks now
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
##########################################################################################
|
@ -9,6 +9,7 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
DBDriver string `mapstructure:"DB_TYPE"`
|
DBDriver string `mapstructure:"DB_TYPE"`
|
||||||
DBSource string `mapstructure:"DB_SOURCE"`
|
DBSource string `mapstructure:"DB_SOURCE"`
|
||||||
|
DBSourceTest string `mapstructure:"DB_SOURCE_TEST"`
|
||||||
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
|
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
|
||||||
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
|
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
|
||||||
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
|
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Failed to hash password: %w", err)
|
return "", fmt.Errorf("failed to hash password: %w", err)
|
||||||
}
|
}
|
||||||
return string(hashedPassword), nil
|
return string(hashedPassword), nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user