Merge pull request #5 from pahmiudahgede/api_v2

Api v2
This commit is contained in:
Fahmi Kurniawan 2025-02-22 12:54:39 +07:00 committed by GitHub
commit fb6d930ae6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
149 changed files with 98022 additions and 95124 deletions

View File

@ -1,13 +1,22 @@
# SERVER SETTINGS
SERVER_HOST=localhost
SERVER_PORT= # isi listen port anda (bebas)
SERVER_HOST=
SERVER_PORT=
# DATABASE SETTINGS
DB_HOST=localhost
DB_PORT=5432 # port default postgres
DB_NAME= # nama_database di postgres
DB_USER= # username yang digunakan di postgres
DB_PASSWORD= # password yang digunakan di postgres
DB_HOST=
DB_PORT=
DB_NAME=
DB_USER=
DB_PASSWORD=
# api keyauth
# REDIS SETTINGS
REDIS_HOST=
REDIS_PORT=
REDIS_PASSWORD=
REDIS_DB=
# Keyauth
API_KEY=
#SECRET_KEY
SECRET_KEY=

9
.gitignore vendored
View File

@ -22,4 +22,11 @@ go.work
go.work.sum
# env file
.env
.env
.env.prod
.env.dev
# Ignore avatar images
/public/uploads/avatars/
/public/uploads/articles/
/public/uploads/banners/

17
cmd/main.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/router"
)
func main() {
config.SetupConfig()
app := fiber.New()
router.SetupRoutes(app)
config.StartServer(app)
}

View File

@ -3,54 +3,55 @@ package config
import (
"fmt"
"log"
"os"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var DB *gorm.DB
func InitDatabase() {
InitConfig()
func ConnectDatabase() {
dsn := fmt.Sprintf(
"host=%s port=%s user=%s dbname=%s password=%s sslmode=disable",
DBHost, DBPort, DBUser, DBName, DBPassword,
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
os.Getenv("DB_PORT"),
)
var err error
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("Error: Gagal terhubung ke database: %v", err)
log.Fatalf("Error connecting to database: %v", err)
}
log.Println("Database connected successfully!")
err = DB.AutoMigrate(
&domain.Point{},
&domain.User{},
&domain.UserRole{},
&domain.UserPin{},
&domain.MenuAccess{},
&domain.PlatformHandle{},
&domain.Address{},
&domain.Article{},
&domain.TrashCategory{},
&domain.TrashDetail{},
&domain.Banner{},
&domain.CoverageArea{},
&domain.CoverageDistric{},
&domain.CoverageSubdistrict{},
&domain.RequestPickup{},
&domain.RequestItem{},
&domain.Product{},
&domain.ProductImage{},
&domain.Store{},
// ==wilayah indonesia==
&model.Province{},
&model.Regency{},
&model.District{},
&model.Village{},
// ==wilayah indonesia==
// ==main feature==
&model.User{},
&model.Role{},
&model.UserPin{},
&model.Address{},
&model.Article{},
&model.Banner{},
&model.InitialCoint{},
&model.TrashCategory{},
&model.TrashDetail{},
// ==main feature==
)
if err != nil {
log.Fatalf("Error: Gagal melakukan migrasi schema: %v", err)
log.Fatalf("Error performing auto-migration: %v", err)
}
log.Println("Koneksi ke database berhasil dan migrasi schema juga berhasil")
log.Println("Database migrated successfully!")
}

View File

@ -1,44 +0,0 @@
package config
import (
"log"
"os"
)
var (
DBHost string
DBPort string
DBName string
DBUser string
DBPassword string
RedisHost string
RedisPort string
RedisPassword string
RedisDB int
ServerHost string
ServerPort string
APIKey string
)
func InitConfig() {
ServerHost = os.Getenv("SERVER_HOST")
ServerPort = os.Getenv("SERVER_PORT")
DBHost = os.Getenv("DB_HOST")
DBPort = os.Getenv("DB_PORT")
DBName = os.Getenv("DB_NAME")
DBUser = os.Getenv("DB_USER")
DBPassword = os.Getenv("DB_PASSWORD")
APIKey = os.Getenv("API_KEY")
RedisHost = os.Getenv("REDIS_HOST")
RedisPort = os.Getenv("REDIS_PORT")
RedisPassword = os.Getenv("REDIS_PASSWORD")
RedisDB = 0
if ServerHost == "" || ServerPort == "" || DBHost == "" || DBPort == "" || DBName == "" || DBUser == "" || DBPassword == "" || APIKey == "" {
log.Fatal("Error: Beberapa environment variables tidak ditemukan.")
}
}

View File

@ -4,58 +4,24 @@ import (
"context"
"fmt"
"log"
"time"
"os"
"github.com/go-redis/redis/v8"
)
var RedisClient *redis.Client
var Ctx = context.Background()
func Context() context.Context {
return context.Background()
}
func InitRedis() {
InitConfig()
func ConnectRedis() {
RedisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", RedisHost, RedisPort),
Password: RedisPassword,
DB: RedisDB,
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"),
DB: 0,
})
_, err := RedisClient.Ping(context.Background()).Result()
_, err := RedisClient.Ping(Ctx).Result()
if err != nil {
log.Fatalf("Error: Gagal terhubung ke Redis: %v", err)
log.Fatalf("Error connecting to Redis: %v", err)
}
log.Println("Koneksi ke Redis berhasil.")
}
func GetFromCache(key string) (string, error) {
val, err := RedisClient.Get(Context(), key).Result()
if err == redis.Nil {
return "", nil
} else if err != nil {
return "", err
}
return val, nil
}
func SetToCache(key string, value string, ttl time.Duration) error {
err := RedisClient.Set(Context(), key, value, ttl).Err()
if err != nil {
return err
}
return nil
}
func DeleteFromCache(key string) error {
err := RedisClient.Del(Context(), key).Err()
if err != nil {
return err
}
return nil
log.Println("Redis connected successfully!")
}

21
config/server.go Normal file
View File

@ -0,0 +1,21 @@
package config
import (
"fmt"
"log"
"os"
"github.com/gofiber/fiber/v2"
)
func StartServer(app *fiber.App) {
host := os.Getenv("SERVER_HOST")
port := os.Getenv("SERVER_PORT")
address := fmt.Sprintf("%s:%s", host, port)
log.Printf("Server is running on http://%s", address)
if err := app.Listen(address); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}

17
config/setup_config.go Normal file
View File

@ -0,0 +1,17 @@
package config
import (
"log"
"github.com/joho/godotenv"
)
func SetupConfig() {
err := godotenv.Load(".env.dev")
if err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
ConnectDatabase()
ConnectRedis()
}

View File

@ -1,30 +0,0 @@
package domain
type Province struct {
ID string `json:"id"`
Name string `json:"name"`
ListRegency []Regency `json:"list_regency,omitempty"`
}
type Regency struct {
ID string `json:"id"`
ProvinceID string `json:"province_id"`
Name string `json:"name"`
Province *Province `json:"province,omitempty"`
ListDistrict []District `json:"list_district,omitempty"`
}
type District struct {
ID string `json:"id"`
RegencyID string `json:"regency_id"`
Name string `json:"name"`
Regency *Regency `json:"regency,omitempty"`
ListVillage []Village `json:"list_village,omitempty"`
}
type Village struct {
ID string `json:"id"`
DistrictID string `json:"district_id"`
Name string `json:"name"`
District *District `json:"district,omitempty"`
}

View File

@ -1,18 +0,0 @@
package domain
import "time"
type Address struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
UserID string `gorm:"not null" json:"userId"`
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
Province string `gorm:"not null" json:"province"`
District string `gorm:"not null" json:"district"`
Subdistrict string `gorm:"not null" json:"subdistrict"`
PostalCode int `gorm:"not null" json:"postalCode"`
Village string `gorm:"not null" json:"village"`
Detail string `gorm:"not null" json:"detail"`
Geography string `gorm:"not null" json:"geography"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -1,14 +0,0 @@
package domain
import "time"
type Article struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Title string `gorm:"not null" json:"title"`
CoverImage string `gorm:"not null" json:"coverImage"`
Author string `gorm:"not null" json:"author"`
Heading string `gorm:"not null" json:"heading"`
Content string `gorm:"not null" json:"content"`
PublishedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -1,29 +0,0 @@
package domain
import "time"
type CoverageArea struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
Province string `gorm:"not null" json:"province"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
CoverageDistrics []CoverageDistric `gorm:"foreignKey:CoverageAreaID" json:"coverage_districs"`
}
type CoverageDistric struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
CoverageAreaID string `gorm:"not null;constraint:OnDelete:CASCADE;OnUpdate:CASCADE;" json:"coverage_area_id"`
District string `gorm:"not null" json:"district"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
CoverageSubdistricts []CoverageSubdistrict `gorm:"foreignKey:CoverageDistrictId" json:"subdistricts"`
}
type CoverageSubdistrict struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
CoverageAreaID string `gorm:"not null" json:"coverage_area_id"`
CoverageDistrictId string `gorm:"not null;constraint:OnDelete:CASCADE;OnUpdate:CASCADE;" json:"coverage_district_id"`
Subdistrict string `gorm:"not null" json:"subdistrict"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -1,14 +0,0 @@
package domain
import "time"
type MenuAccess struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
RoleID string `gorm:"not null" json:"roleId"`
Role UserRole `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"role"`
MenuName string `gorm:"not null" json:"menuName"`
Path string `gorm:"not null" json:"path"`
IconURL string `gorm:"not null" json:"iconUrl"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -1,7 +0,0 @@
package domain
type PlatformHandle struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Platform string `gorm:"not null" json:"platform"`
Description string `gorm:"not null" json:"description"`
}

View File

@ -1,28 +0,0 @@
package domain
import "time"
type Product struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
UserID string `gorm:"type:uuid;not null" json:"user_id"`
StoreID string `gorm:"type:uuid;not null" json:"store_id"`
Store Store `gorm:"foreignKey:StoreID" json:"store"`
ProductTitle string `gorm:"not null" json:"product_title"`
ProductImages []ProductImage `gorm:"foreignKey:ProductID;constraint:OnDelete:CASCADE;" json:"product_images"`
TrashDetailID string `gorm:"type:uuid;not null" json:"trash_detail_id"`
TrashDetail TrashDetail `gorm:"foreignKey:TrashDetailID" json:"trash_detail"`
SalePrice int64 `gorm:"not null" json:"sale_price"`
Quantity int `gorm:"not null" json:"quantity"`
ProductDescribe string `gorm:"type:text" json:"product_describe"`
Sold int `gorm:"default:0" json:"sold"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
}
type ProductImage struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
ProductID string `gorm:"type:uuid;not null" json:"product_id"`
ImageURL string `gorm:"not null" json:"image_url"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
}

View File

@ -1,24 +0,0 @@
package domain
import "time"
type RequestPickup struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
UserID string `gorm:"type:uuid;not null" json:"userId"`
Request []RequestItem `gorm:"foreignKey:RequestPickupID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"request"`
RequestTime string `gorm:"type:text;not null" json:"requestTime"`
UserAddressID string `gorm:"type:uuid;not null" json:"userAddressId"`
UserAddress Address `gorm:"foreignKey:UserAddressID" json:"userAddress"`
StatusRequest string `gorm:"type:text;not null" json:"statusRequest"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}
type RequestItem struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
RequestPickupID string `gorm:"type:uuid;not null;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"requestPickupId"`
TrashCategoryID string `gorm:"type:uuid;not null" json:"trashCategoryId"`
TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryID" json:"trashCategory"`
EstimatedAmount string `gorm:"type:text;not null" json:"estimatedAmount"`
}

View File

@ -1,7 +0,0 @@
package domain
type UserRole struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
RoleName string `gorm:"unique;not null" json:"roleName"`
// Users []User `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"users"`
}

View File

@ -1,80 +0,0 @@
package domain
import "time"
type Store struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
UserID string `gorm:"type:uuid;not null" json:"user_id"`
User User `gorm:"foreignKey:UserID" json:"user"`
StoreName string `gorm:"not null;unique" json:"store_name"`
StoreLogo string `json:"store_logo"`
StoreBanner string `json:"store_banner"`
StoreDesc string `gorm:"type:text" json:"store_desc"`
Follower int `gorm:"default:0" json:"follower"`
StoreRating float64 `gorm:"default:0" json:"store_rating"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
}
// type StoreFinance struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// StoreID string `gorm:"type:uuid;not null" json:"store_id"`
// TotalRevenue int64 `gorm:"default:0" json:"total_revenue"`
// TotalExpenses int64 `gorm:"default:0" json:"total_expenses"`
// NetProfit int64 `gorm:"default:0" json:"net_profit"`
// OrdersCount int `gorm:"default:0" json:"orders_count"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }
// type Order struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// StoreID string `gorm:"type:uuid;not null" json:"store_id"`
// UserID string `gorm:"type:uuid;not null" json:"user_id"`
// TotalPrice int64 `gorm:"not null" json:"total_price"`
// OrderStatus string `gorm:"not null" json:"order_status"`
// ShippingStatus string `gorm:"not null" json:"shipping_status"`
// PaymentStatus string `gorm:"not null" json:"payment_status"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }
// type OrderDetail struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// OrderID string `gorm:"type:uuid;not null" json:"order_id"`
// ProductID string `gorm:"type:uuid;not null" json:"product_id"`
// Quantity int `gorm:"not null" json:"quantity"`
// UnitPrice int64 `gorm:"not null" json:"unit_price"`
// TotalPrice int64 `gorm:"not null" json:"total_price"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }
// type Shipping struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// OrderID string `gorm:"type:uuid;not null" json:"order_id"`
// ShippingDate time.Time `gorm:"default:current_timestamp" json:"shipping_date"`
// ShippingCost int64 `gorm:"not null" json:"shipping_cost"`
// TrackingNo string `gorm:"not null" json:"tracking_no"`
// ShippingStatus string `gorm:"not null" json:"shipping_status"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }
// type Cancellation struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// OrderID string `gorm:"type:uuid;not null" json:"order_id"`
// Reason string `gorm:"type:text" json:"reason"`
// CancelledAt time.Time `gorm:"default:current_timestamp" json:"cancelled_at"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }
// type Return struct {
// ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
// OrderID string `gorm:"type:uuid;not null" json:"order_id"`
// Reason string `gorm:"type:text" json:"reason"`
// ReturnedAt time.Time `gorm:"default:current_timestamp" json:"returned_at"`
// CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
// UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
// }

View File

@ -1,84 +0,0 @@
package dto
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type AddressInput struct {
Province string `json:"province" validate:"required"`
District string `json:"district" validate:"required"`
Subdistrict string `json:"subdistrict" validate:"required"`
PostalCode int `json:"postalCode" validate:"required,numeric"`
Village string `json:"village" validate:"required"`
Detail string `json:"detail" validate:"required"`
Geography string `json:"geography" validate:"required"`
}
type AddressResponse struct {
ID string `json:"id"`
Province string `json:"province"`
District string `json:"district"`
Subdistrict string `json:"subdistrict"`
PostalCode int `json:"postalCode"`
Village string `json:"village"`
Detail string `json:"detail"`
Geography string `json:"geography"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func (c *AddressInput) ValidatePost() error {
err := validate.Struct(c)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "Province":
return fmt.Errorf("provinsi harus diisisi")
case "District":
return fmt.Errorf("kabupaten harus diisi")
case "Subdistrict":
return fmt.Errorf("kecamatan harus diisi")
case "PostalCode":
return fmt.Errorf("postal code harus diisi dan berupa angka")
case "Village":
return fmt.Errorf("desa harus diisi")
case "Detail":
return fmt.Errorf("detail wajib diisi")
case "Geography":
return fmt.Errorf("lokasi kordinat harus diisi")
}
}
}
return nil
}
func (c *AddressInput) ValidateUpdate() error {
err := validate.Struct(c)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "Province":
return fmt.Errorf("provinsi harus diisisi")
case "District":
return fmt.Errorf("kabupaten harus diisi")
case "Subdistrict":
return fmt.Errorf("kecamatan harus diisi")
case "PostalCode":
return fmt.Errorf("postal code harus diisi dan berupa angka")
case "Village":
return fmt.Errorf("desa harus diisi")
case "Detail":
return fmt.Errorf("detail wajib diisi")
case "Geography":
return fmt.Errorf("lokasi kordinat harus diisi")
}
}
}
return nil
}

61
dto/address_dto.go Normal file
View File

@ -0,0 +1,61 @@
package dto
import "strings"
type AddressResponseDTO struct {
UserID string `json:"user_id"`
ID string `json:"address_id"`
Province string `json:"province"`
Regency string `json:"regency"`
District string `json:"district"`
Village string `json:"village"`
PostalCode string `json:"postalCode"`
Detail string `json:"detail"`
Geography string `json:"geography"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CreateAddressDTO struct {
Province string `json:"province_id"`
Regency string `json:"regency_id"`
District string `json:"district_id"`
Village string `json:"village_id"`
PostalCode string `json:"postalCode"`
Detail string `json:"detail"`
Geography string `json:"geography"`
}
func (r *CreateAddressDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Province) == "" {
errors["province_id"] = append(errors["province_id"], "Province ID is required")
}
if strings.TrimSpace(r.Regency) == "" {
errors["regency_id"] = append(errors["regency_id"], "Regency ID is required")
}
if strings.TrimSpace(r.District) == "" {
errors["district_id"] = append(errors["district_id"], "District ID is required")
}
if strings.TrimSpace(r.Village) == "" {
errors["village_id"] = append(errors["village_id"], "Village ID is required")
}
if strings.TrimSpace(r.PostalCode) == "" {
errors["postalCode"] = append(errors["village_id"], "PostalCode ID is required")
} else if len(r.PostalCode) < 5 {
errors["postalCode"] = append(errors["postalCode"], "kode pos belum sesuai")
}
if strings.TrimSpace(r.Detail) == "" {
errors["detail"] = append(errors["detail"], "Detail address is required")
}
if strings.TrimSpace(r.Geography) == "" {
errors["geography"] = append(errors["geography"], "Geographic coordinates are required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,38 +0,0 @@
package dto
type ArticleResponse struct {
ID string `json:"id"`
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Author string `json:"author"`
Heading string `json:"heading"`
Content string `json:"content"`
PublishedAt string `json:"publishedAt"`
UpdatedAt string `json:"updatedAt"`
}
type ArticleCreateRequest struct {
Title string `json:"title" validate:"required"`
CoverImage string `json:"coverImage" validate:"required"`
Author string `json:"author" validate:"required"`
Heading string `json:"heading" validate:"required"`
Content string `json:"content" validate:"required"`
}
type ArticleUpdateRequest struct {
Title string `json:"title" validate:"required"`
CoverImage string `json:"coverImage" validate:"required"`
Author string `json:"author" validate:"required"`
Heading string `json:"heading" validate:"required"`
Content string `json:"content" validate:"required"`
}
func (p *ArticleCreateRequest) Validate() error {
validate := GetValidator()
return validate.Struct(p)
}
func (p *ArticleUpdateRequest) Validate() error {
validate := GetValidator()
return validate.Struct(p)
}

48
dto/article_dto.go Normal file
View File

@ -0,0 +1,48 @@
package dto
import (
"strings"
)
type ArticleResponseDTO struct {
ID string `json:"article_id"`
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Author string `json:"author"`
Heading string `json:"heading"`
Content string `json:"content"`
PublishedAt string `json:"publishedAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestArticleDTO struct {
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Author string `json:"author"`
Heading string `json:"heading"`
Content string `json:"content"`
}
func (r *RequestArticleDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Title) == "" {
errors["title"] = append(errors["title"], "Title is required")
}
if strings.TrimSpace(r.Author) == "" {
errors["author"] = append(errors["author"], "Author is required")
}
if strings.TrimSpace(r.Heading) == "" {
errors["heading"] = append(errors["heading"], "Heading is required")
}
if strings.TrimSpace(r.Content) == "" {
errors["content"] = append(errors["content"], "Content is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

121
dto/auth_dto.go Normal file
View File

@ -0,0 +1,121 @@
package dto
import (
"regexp"
"strings"
)
type LoginDTO struct {
RoleID string `json:"roleid"`
Identifier string `json:"identifier"`
Password string `json:"password"`
}
type UserResponseWithToken struct {
UserID string `json:"user_id"`
RoleName string `json:"role_name"`
Token string `json:"token"`
}
type RegisterDTO struct {
Username string `json:"username"`
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"`
RoleID string `json:"roleId,omitempty"`
}
func (l *LoginDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(l.RoleID) == "" {
errors["roleid"] = append(errors["roleid"], "Role ID is required")
}
if strings.TrimSpace(l.Identifier) == "" {
errors["identifier"] = append(errors["identifier"], "Identifier (username, email, or phone) is required")
}
if strings.TrimSpace(l.Password) == "" {
errors["password"] = append(errors["password"], "Password is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RegisterDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
r.validateRequiredFields(errors)
if r.Phone != "" && !IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
}
if r.Email != "" && !IsValidEmail(r.Email) {
errors["email"] = append(errors["email"], "Invalid email format")
}
if r.Password != "" && !IsValidPassword(r.Password) {
errors["password"] = append(errors["password"], "Password must be at least 8 characters long and contain at least one number")
}
if r.ConfirmPassword != "" && r.Password != r.ConfirmPassword {
errors["confirm_password"] = append(errors["confirm_password"], "Password and confirm password do not match")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RegisterDTO) validateRequiredFields(errors map[string][]string) {
if strings.TrimSpace(r.Username) == "" {
errors["username"] = append(errors["username"], "Username is required")
}
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "Name is required")
}
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
}
if strings.TrimSpace(r.Email) == "" {
errors["email"] = append(errors["email"], "Email is required")
}
if strings.TrimSpace(r.Password) == "" {
errors["password"] = append(errors["password"], "Password is required")
}
if strings.TrimSpace(r.ConfirmPassword) == "" {
errors["confirm_password"] = append(errors["confirm_password"], "Confirm password is required")
}
if strings.TrimSpace(r.RoleID) == "" {
errors["roleId"] = append(errors["roleId"], "RoleID is required")
}
}
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^\+62\d{9,13}$`)
return re.MatchString(phone)
}
func IsValidEmail(email string) bool {
re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
return re.MatchString(email)
}
func IsValidPassword(password string) bool {
if len(password) < 8 {
return false
}
re := regexp.MustCompile(`\d`)
return re.MatchString(password)
}

View File

@ -1,19 +0,0 @@
package dto
type BannerResponse struct {
ID string `json:"id"`
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type BannerCreateRequest struct {
BannerName string `json:"bannername" validate:"required"`
BannerImage string `json:"bannerimage" validate:"required"`
}
type BannerUpdateRequest struct {
BannerName *string `json:"bannername,omitempty"`
BannerImage *string `json:"bannerimage,omitempty"`
}

29
dto/banner_dto.go Normal file
View File

@ -0,0 +1,29 @@
package dto
import "strings"
type ResponseBannerDTO struct {
ID string `json:"id"`
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestBannerDTO struct {
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
}
func (r *RequestBannerDTO) ValidateBannerInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.BannerName) == "" {
errors["bannername"] = append(errors["bannername"], "nama banner harus diisi")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,119 +0,0 @@
package dto
type CoverageAreaResponse struct {
ID string `json:"id"`
Province string `json:"province"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CoverageAreaWithDistrictsResponse struct {
ID string `json:"id"`
Province string `json:"province"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
CoverageArea []CoverageAreaResponse `json:"coverage_area"`
}
type CoverageAreaDetailWithLocation struct {
ID string `json:"id"`
Province string `json:"province"`
District string `json:"district"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Subdistrict []SubdistrictResponse `json:"subdistrict"`
}
type SubdistrictResponse struct {
ID string `json:"id"`
Subdistrict string `json:"subdistrict"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func NewCoverageAreaResponse(id, province, createdAt, updatedAt string) CoverageAreaResponse {
return CoverageAreaResponse{
ID: id,
Province: province,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
func NewCoverageAreaWithDistrictsResponse(id, province, createdAt, updatedAt string, coverageArea []CoverageAreaResponse) CoverageAreaWithDistrictsResponse {
return CoverageAreaWithDistrictsResponse{
ID: id,
Province: province,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
CoverageArea: coverageArea,
}
}
func NewCoverageAreaDetailWithLocation(id, province, district, createdAt, updatedAt string, subdistricts []SubdistrictResponse) CoverageAreaDetailWithLocation {
return CoverageAreaDetailWithLocation{
ID: id,
Province: province,
District: district,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
Subdistrict: subdistricts,
}
}
func NewSubdistrictResponse(id, subdistrict, createdAt, updatedAt string) SubdistrictResponse {
return SubdistrictResponse{
ID: id,
Subdistrict: subdistrict,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
type CoverageAreaCreateRequest struct {
Province string `json:"province" validate:"required"`
}
func NewCoverageAreaCreateRequest(province string) CoverageAreaCreateRequest {
return CoverageAreaCreateRequest{
Province: province,
}
}
type CoverageDistrictCreateRequest struct {
CoverageAreaID string `json:"coverage_area_id" validate:"required"`
District string `json:"district" validate:"required"`
}
func NewCoverageDistrictCreateRequest(coverageAreaID, district string) CoverageDistrictCreateRequest {
return CoverageDistrictCreateRequest{
CoverageAreaID: coverageAreaID,
District: district,
}
}
type CoverageSubdistrictCreateRequest struct {
CoverageAreaID string `json:"coverage_area_id" validate:"required"`
CoverageDistrictId string `json:"coverage_district_id" validate:"required"`
Subdistrict string `json:"subdistrict" validate:"required"`
}
func NewCoverageSubdistrictCreateRequest(coverageAreaID, coverageDistrictId, subdistrict string) CoverageSubdistrictCreateRequest {
return CoverageSubdistrictCreateRequest{
CoverageAreaID: coverageAreaID,
CoverageDistrictId: coverageDistrictId,
Subdistrict: subdistrict,
}
}
type CoverageAreaUpdateRequest struct {
Province string `json:"province" validate:"required"`
}
type CoverageDistrictUpdateRequest struct {
District string `json:"district" validate:"required"`
}
type CoverageSubdistrictUpdateRequest struct {
Subdistrict string `json:"subdistrict" validate:"required"`
}

View File

@ -1,29 +0,0 @@
package dto
type PointResponse struct {
ID string `json:"id"`
CoinName string `json:"coin_name"`
ValuePerUnit float64 `json:"value_perunit"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type PointCreateRequest struct {
CoinName string `json:"coin_name" validate:"required"`
ValuePerUnit float64 `json:"value_perunit" validate:"required,gt=0"`
}
type PointUpdateRequest struct {
CoinName string `json:"coin_name" validate:"required"`
ValuePerUnit float64 `json:"value_perunit" validate:"required,gt=0"`
}
func (p *PointCreateRequest) Validate() error {
validate := GetValidator()
return validate.Struct(p)
}
func (p *PointUpdateRequest) Validate() error {
validate := GetValidator()
return validate.Struct(p)
}

34
dto/initialcoint_dto.go Normal file
View File

@ -0,0 +1,34 @@
package dto
import "strings"
type ReponseInitialCointDTO struct {
ID string `json:"coin_id"`
CoinName string `json:"coin_name"`
ValuePerUnit float64 `json:"value_perunit"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestInitialCointDTO struct {
CoinName string `json:"coin_name"`
ValuePerUnit float64 `json:"value_perunit"`
}
func (r *RequestInitialCointDTO) ValidateCointInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.CoinName) == "" {
errors["coin_name"] = append(errors["coin_name"], "nama coin harus diisi")
}
if r.ValuePerUnit <= 0 {
errors["value_perunit"] = append(errors["value_perunit"], "value per unit harus lebih besar dari 0")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,93 +0,0 @@
package dto
import "errors"
type ProductResponseWithSoldDTO struct {
ID string `json:"id"`
StoreID string `json:"store_id"`
ProductTitle string `json:"product_title"`
ProductImages []ProductImageDTO `json:"product_images"`
TrashDetail TrashDetailResponseDTO `json:"trash_detail"`
SalePrice int64 `json:"sale_price"`
Quantity int `json:"quantity"`
ProductDescribe string `json:"product_describe"`
Sold int `json:"sold"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ProductResponseWithoutSoldDTO struct {
ID string `json:"id"`
StoreID string `json:"store_id"`
ProductTitle string `json:"product_title"`
ProductImages []ProductImageDTO `json:"product_images"`
TrashDetail TrashDetailResponseDTO `json:"trash_detail"`
SalePrice int64 `json:"sale_price"`
Quantity int `json:"quantity"`
ProductDescribe string `json:"product_describe"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ProductResponseDTO struct {
ID string `json:"id"`
StoreID string `json:"store_id"`
ProductTitle string `json:"product_title"`
ProductImages []ProductImageDTO `json:"product_images"`
TrashDetail TrashDetailResponseDTO `json:"trash_detail"`
SalePrice int64 `json:"sale_price"`
Quantity int `json:"quantity"`
ProductDescribe string `json:"product_describe"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ProductImageDTO struct {
ImageURL string `json:"image_url"`
}
type TrashDetailResponseDTO struct {
ID string `json:"id"`
Description string `json:"description"`
Price int `json:"price"`
}
type CreateProductRequestDTO struct {
StoreID string `json:"storeid" validate:"required,uuid"`
ProductTitle string `json:"product_title" validate:"required,min=3,max=255"`
ProductImages []string `json:"product_images" validate:"required,min=1,dive,url"`
TrashDetailID string `json:"trash_detail_id" validate:"required,uuid"`
SalePrice int64 `json:"sale_price" validate:"required,gt=0"`
Quantity int `json:"quantity" validate:"required,gt=0"`
ProductDescribe string `json:"product_describe,omitempty"`
}
type CreateProductResponseDTO struct {
ID string `json:"id"`
StoreID string `json:"store_id"`
ProductTitle string `json:"product_title"`
ProductImages []string `json:"product_images"`
TrashDetail TrashDetailResponseDTO `json:"trash_detail"`
SalePrice int64 `json:"sale_price"`
Quantity int `json:"quantity"`
ProductDescribe string `json:"product_describe,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type UpdateProductRequestDTO struct {
ProductTitle string `json:"product_title" validate:"required,min=3,max=255"`
ProductImages []string `json:"product_images" validate:"required,min=1,dive,url"`
TrashDetailID string `json:"trash_detail_id" validate:"required,uuid"`
SalePrice int64 `json:"sale_price" validate:"required,gt=0"`
Quantity int `json:"quantity" validate:"required,gt=0"`
ProductDescribe string `json:"product_describe,omitempty"`
}
func ValidateSalePrice(marketPrice, salePrice int64) error {
if salePrice > marketPrice*2 {
return errors.New("sale price cannot be more than twice the market price")
}
return nil
}

View File

@ -1,46 +0,0 @@
package dto
type RequestPickupRequest struct {
RequestItems []RequestItemDTO `json:"request_items"`
RequestTime string `json:"requestTime"`
UserAddressID string `json:"userAddressId"`
}
type RequestPickupResponse struct {
ID string `json:"id"`
UserID string `json:"userId"`
Request []RequestItemDTO `json:"request"`
RequestTime string `json:"requestTimePickup"`
UserAddress UserAddressDTO `json:"userAddress"`
StatusRequest string `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestItemDTO struct {
TrashCategory string `json:"trashCategory"`
EstimatedAmount string `json:"estimatedAmount"`
}
type UserAddressDTO struct {
Province string `json:"province"`
District string `json:"district"`
Subdistrict string `json:"subdistrict"`
PostalCode int `json:"postalCode"`
Village string `json:"village"`
Detail string `json:"detail"`
Geography string `json:"geography"`
}
func NewRequestPickupResponse(id, userID, requestTime, statusRequest string, request []RequestItemDTO, userAddress UserAddressDTO, createdAt, updatedAt string) RequestPickupResponse {
return RequestPickupResponse{
ID: id,
UserID: userID,
Request: request,
RequestTime: requestTime,
UserAddress: userAddress,
StatusRequest: statusRequest,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}

8
dto/role_dto.go Normal file
View File

@ -0,0 +1,8 @@
package dto
type RoleResponseDTO struct {
ID string `json:"role_id"`
RoleName string `json:"role_name"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}

View File

@ -1,14 +0,0 @@
package dto
type StoreResponseDTO struct {
ID string `json:"id"`
UserID string `json:"user_id"`
StoreName string `json:"store_name"`
StoreLogo string `json:"store_logo"`
StoreBanner string `json:"store_banner"`
StoreDesc string `json:"store_desc"`
Follower int `json:"follower"`
StoreRating float64 `json:"store_rating"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

59
dto/trash_dto.go Normal file
View File

@ -0,0 +1,59 @@
package dto
import "strings"
type RequestTrashCategoryDTO struct {
Name string `json:"name"`
}
type ResponseTrashCategoryDTO struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Details []ResponseTrashDetailDTO `json:"details,omitempty"`
}
type ResponseTrashDetailDTO struct {
ID string `json:"id"`
CategoryID string `json:"category_id"`
Description string `json:"description"`
Price float64 `json:"price"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestTrashDetailDTO struct {
CategoryID string `json:"category_id"`
Description string `json:"description"`
Price float64 `json:"price"`
}
func (r *RequestTrashCategoryDTO) ValidateTrashCategoryInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "name is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RequestTrashDetailDTO) ValidateTrashDetailInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Description) == "" {
errors["description"] = append(errors["description"], "description is required")
}
if r.Price <= 0 {
errors["price"] = append(errors["price"], "price must be greater than 0")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,76 +0,0 @@
package dto
import "github.com/go-playground/validator/v10"
type TrashCategoryDTO struct {
Name string `json:"name" validate:"required,min=3,max=100"`
}
func (t *TrashCategoryDTO) Validate() error {
validate := validator.New()
return validate.Struct(t)
}
type TrashDetailDTO struct {
CategoryID string `json:"category_id" validate:"required,uuid4"`
Description string `json:"description" validate:"required,min=3,max=255"`
Price int `json:"price" validate:"required,min=0"`
}
func (t *TrashDetailDTO) Validate() error {
validate := validator.New()
return validate.Struct(t)
}
type TrashCategoryResponse struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func NewTrashCategoryResponse(id, name, createdAt, updatedAt string) TrashCategoryResponse {
return TrashCategoryResponse{
ID: id,
Name: name,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
type TrashDetailResponse struct {
ID string `json:"id"`
Description string `json:"description"`
Price int `json:"price"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func NewTrashDetailResponse(id, description string, price int, createdAt, updatedAt string) TrashDetailResponse {
return TrashDetailResponse{
ID: id,
Description: description,
Price: price,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
type UpdateTrashCategoryDTO struct {
Name string `json:"name" validate:"required,min=3,max=100"`
}
func (t *UpdateTrashCategoryDTO) Validate() error {
validate := validator.New()
return validate.Struct(t)
}
type UpdateTrashDetailDTO struct {
Description string `json:"description" validate:"required,min=3,max=255"`
Price int `json:"price" validate:"required,min=0"`
}
func (t *UpdateTrashDetailDTO) Validate() error {
validate := validator.New()
return validate.Struct(t)
}

View File

@ -1,152 +0,0 @@
package dto
import (
"errors"
"regexp"
)
type UserResponseDTO struct {
ID string `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
RoleId string `json:"roleId"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func ValidateEmail(email string) error {
if email == "" {
return errors.New("email harus diisi")
}
emailRegex := `^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`
re := regexp.MustCompile(emailRegex)
if !re.MatchString(email) {
return errors.New("format email belum sesuai")
}
return nil
}
func ValidatePhone(phone string) error {
if phone == "" {
return errors.New("nomor telepon harus diisi")
}
phoneRegex := `^\+?[0-9]{10,15}$`
re := regexp.MustCompile(phoneRegex)
if !re.MatchString(phone) {
return errors.New("nomor telepon tidak valid")
}
return nil
}
func ValidatePassword(password string) error {
if password == "" {
return errors.New("password harus diisi")
}
if len(password) < 8 {
return errors.New("password minimal 8 karakter")
}
return nil
}
type RegisterUserInput struct {
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"`
RoleId string `json:"roleId"`
}
func (input *RegisterUserInput) Validate() error {
if input.Username == "" {
return errors.New("username harus diisi")
}
if input.Name == "" {
return errors.New("nama harus diisi")
}
if err := ValidateEmail(input.Email); err != nil {
return err
}
if err := ValidatePhone(input.Phone); err != nil {
return err
}
if err := ValidatePassword(input.Password); err != nil {
return err
}
if input.Password != input.ConfirmPassword {
return errors.New("password dan confirm password tidak cocok")
}
if input.RoleId == "" {
return errors.New("roleId harus diisi")
}
return nil
}
type UpdatePasswordInput struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
func (input *UpdatePasswordInput) Validate() error {
if input.OldPassword == "" {
return errors.New("old password must be provided")
}
if input.NewPassword == "" {
return errors.New("new password must be provided")
}
if len(input.NewPassword) < 8 {
return errors.New("new password must be at least 8 characters long")
}
return nil
}
type UpdateUserInput struct {
Email string `json:"email"`
Username string `json:"username"`
Name string `json:"name"`
Phone string `json:"phone"`
}
func (input *UpdateUserInput) Validate() error {
if input.Email != "" {
if err := ValidateEmail(input.Email); err != nil {
return err
}
}
if input.Username == "" {
return errors.New("username harus diisi")
}
if input.Name == "" {
return errors.New("name harus diisi")
}
if input.Phone != "" {
if err := ValidatePhone(input.Phone); err != nil {
return err
}
}
return nil
}

95
dto/user_dto.go Normal file
View File

@ -0,0 +1,95 @@
package dto
import (
"regexp"
"strings"
)
type UserResponseDTO struct {
ID string `json:"id"`
Username string `json:"username"`
Avatar *string `json:"photoprofile,omitempty"`
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
EmailVerified bool `json:"emailVerified"`
RoleName string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type UpdateUserDTO struct {
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
}
func (r *UpdateUserDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "Name is required")
}
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
}
if strings.TrimSpace(r.Email) == "" {
errors["email"] = append(errors["email"], "Email is required")
} else if !IsValidEmail(r.Email) {
errors["email"] = append(errors["email"], "Invalid email format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func IsUpdateValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^\+62\d{9,13}$`)
return re.MatchString(phone)
}
func IsUPdateValidEmail(email string) bool {
re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
return re.MatchString(email)
}
type UpdatePasswordDTO struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
ConfirmNewPassword string `json:"confirm_new_password"`
}
func (u *UpdatePasswordDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if u.OldPassword == "" {
errors["old_password"] = append(errors["old_password"], "Old password is required")
}
if u.NewPassword == "" {
errors["new_password"] = append(errors["new_password"], "New password is required")
} else if len(u.NewPassword) < 8 {
errors["new_password"] = append(errors["new_password"], "Password must be at least 8 characters long")
}
if u.ConfirmNewPassword == "" {
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Confirm new password is required")
} else if u.NewPassword != u.ConfirmNewPassword {
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Passwords do not match")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,47 +0,0 @@
package dto
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type PinResponse struct {
CreatedAt string `json:"createdAt"`
}
type PinInput struct {
Pin string `json:"pin" validate:"required,len=6,numeric"`
}
func (p *PinInput) ValidateCreate() error {
err := validate.Struct(p)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "Pin":
return fmt.Errorf("PIN harus terdiri dari 6 digit angka")
}
}
}
return nil
}
type PinUpdateInput struct {
OldPin string `json:"old_pin" validate:"required,len=6,numeric"`
NewPin string `json:"new_pin" validate:"required,len=6,numeric"`
}
func (p *PinUpdateInput) ValidateUpdate() error {
err := validate.Struct(p)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "OldPin":
return fmt.Errorf("PIN lama harus terdiri dari 6 digit angka")
case "NewPin":
return fmt.Errorf("PIN baru harus terdiri dari 6 digit angka")
}
}
}
return nil
}

66
dto/userpin_dto.go Normal file
View File

@ -0,0 +1,66 @@
package dto
import (
"fmt"
"regexp"
"strings"
)
type RequestUserPinDTO struct {
Pin string `json:"userpin"`
}
func (r *RequestUserPinDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Pin) == "" {
errors["pin"] = append(errors["pin"], "Pin is required")
}
if err := validatePin(r.Pin); err != nil {
errors["pin"] = append(errors["pin"], err.Error())
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type UpdateUserPinDTO struct {
OldPin string `json:"old_pin"`
NewPin string `json:"new_pin"`
}
func (u *UpdateUserPinDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(u.OldPin) == "" {
errors["old_pin"] = append(errors["old_pin"], "Old pin is required")
}
if err := validatePin(u.NewPin); err != nil {
errors["new_pin"] = append(errors["new_pin"], err.Error())
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func isNumeric(s string) bool {
re := regexp.MustCompile(`^[0-9]+$`)
return re.MatchString(s)
}
func validatePin(pin string) error {
if len(pin) != 6 {
return fmt.Errorf("pin harus terdiri dari 6 digit")
} else if !isNumeric(pin) {
return fmt.Errorf("pin harus berupa angka")
}
return nil
}

View File

@ -1,9 +0,0 @@
package dto
import "github.com/go-playground/validator/v10"
var validate = validator.New()
func GetValidator() *validator.Validate {
return validate
}

View File

@ -0,0 +1,27 @@
package dto
type ProvinceResponseDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Regencies []RegencyResponseDTO `json:"regencies,omitempty"`
}
type RegencyResponseDTO struct {
ID string `json:"id"`
ProvinceID string `json:"province_id"`
Name string `json:"name"`
Districts []DistrictResponseDTO `json:"districts,omitempty"`
}
type DistrictResponseDTO struct {
ID string `json:"id"`
RegencyID string `json:"regency_id"`
Name string `json:"name"`
Villages []VillageResponseDTO `json:"villages,omitempty"`
}
type VillageResponseDTO struct {
ID string `json:"id"`
DistrictID string `json:"district_id"`
Name string `json:"name"`
}

View File

@ -1,137 +0,0 @@
package api
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/controllers"
"github.com/pahmiudahgede/senggoldong/internal/middleware"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func AppRouter(app *fiber.App) {
// # init
pointRepo := repositories.NewPointRepository()
pointService := services.NewPointService(pointRepo)
pointController := controllers.NewPointController(pointService)
bannerRepo := repositories.NewBannerRepository()
bannerService := services.NewBannerService(bannerRepo)
bannerController := controllers.NewBannerController(bannerService)
articleRepo := repositories.NewArticleRepository()
articleService := services.NewArticleService(articleRepo)
articleController := controllers.NewArticleController(articleService)
// # api group domain endpoint #
api := app.Group("/apirijikid")
// # API Secure #
api.Use(middleware.APIKeyMiddleware)
api.Use(middleware.RateLimitMiddleware)
// # user initial coint #
api.Get("/user/initial-coint", pointController.GetAllPoints)
api.Get("/user/initial-coint/:id", pointController.GetPointByID)
api.Post("/user/initial-coint", middleware.RoleRequired(utils.RoleAdministrator), pointController.CreatePoint)
api.Put("/user/initial-coint/:id", middleware.RoleRequired(utils.RoleAdministrator), pointController.UpdatePoint)
api.Delete("/user/initial-coint/:id", middleware.RoleRequired(utils.RoleAdministrator), pointController.DeletePoint)
//# coverage area #
api.Get("/coverage-areas", controllers.GetCoverageAreas)
api.Get("/coverage-areas-district/:id", controllers.GetCoverageAreaByIDProvince)
api.Get("/coverage-areas-subdistrict/:id", controllers.GetCoverageAreaByIDDistrict)
api.Post("/coverage-areas", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateCoverageArea)
api.Post("/coverage-areas-district", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateCoverageDistrict)
api.Post("/coverage-areas-subdistrict", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateCoverageSubdistrict)
api.Put("/coverage-areas/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateCoverageArea)
api.Put("/coverage-areas-district/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateCoverageDistrict)
api.Put("/coverage-areas-subdistrict/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateCoverageSubdistrict)
api.Delete("/coverage-areas/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteCoverageArea)
api.Delete("/coverage-areas-district/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteCoverageDistrict)
api.Delete("/coverage-areas-subdistrict/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteCoverageSubdistrict)
// # role #
api.Get("/roles", middleware.RoleRequired(utils.RoleAdministrator), controllers.GetAllUserRoles)
api.Get("/role/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.GetUserRoleByID)
// # authentication #
api.Post("/register", controllers.Register)
api.Post("/login", controllers.Login)
api.Post("/logout", controllers.Logout)
// # userinfo #
api.Get("/user", middleware.AuthMiddleware, controllers.GetUserInfo)
api.Post("/user/update-password", middleware.AuthMiddleware, controllers.UpdatePassword)
api.Put("/user/update-user", middleware.AuthMiddleware, controllers.UpdateUser)
// # view all user (admin)
api.Get("/user/listallusers", middleware.RoleRequired(utils.RoleAdministrator), controllers.GetListUsers)
api.Get("/user/listalluser/:roleid", middleware.RoleRequired(utils.RoleAdministrator), controllers.GetUsersByRole)
api.Get("/user/listuser/:userid", middleware.RoleRequired(utils.RoleAdministrator), controllers.GetUserByUserID)
// # user set pin #
api.Get("/user/verif-pin", middleware.AuthMiddleware, controllers.GetPin)
api.Get("/user/cek-pin-status", middleware.AuthMiddleware, controllers.GetPinStatus)
api.Post("/user/set-pin", middleware.AuthMiddleware, controllers.CreatePin)
api.Put("/user/update-pin", middleware.AuthMiddleware, controllers.UpdatePin)
api.Put("/user/update-pin", middleware.AuthMiddleware, controllers.UpdatePin)
// # address routing #
api.Get("/addresses", middleware.AuthMiddleware, controllers.GetListAddress)
api.Get("/address/:id", middleware.AuthMiddleware, controllers.GetAddressByID)
api.Post("/address/create-address", middleware.AuthMiddleware, controllers.CreateAddress)
api.Put("/address/update-address/:id", middleware.AuthMiddleware, controllers.UpdateAddress)
api.Delete("/address/delete-address/:id", middleware.AuthMiddleware, controllers.DeleteAddress)
// # article #
api.Get("/articles", articleController.GetAllArticles)
api.Get("/article/:id", articleController.GetArticleByID)
api.Post("/article/create-article", middleware.RoleRequired(utils.RoleAdministrator), articleController.CreateArticle)
api.Put("/article/update-article/:id", middleware.RoleRequired(utils.RoleAdministrator), articleController.UpdateArticle)
api.Delete("/article/delete-article/:id", middleware.RoleRequired(utils.RoleAdministrator), articleController.DeleteArticle)
// # trash type #
api.Get("/trash-categorys", controllers.GetTrashCategories)
api.Get("/trash-category/:id", controllers.GetTrashCategoryDetail)
api.Post("/trash-category/create-trash-category", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateTrashCategory)
api.Post("/trash-category/create-trash-categorydetail", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateTrashDetail)
api.Put("/trash-category/update-trash-category/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateTrashCategory)
api.Put("/trash-category/update-trash-detail/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateTrashDetail)
api.Delete("/trash-category/delete-trash-category/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteTrashCategory)
api.Delete("/trash-category/delete-trash-detail/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteTrashDetail)
// # banner #
api.Get("/banners", bannerController.GetAllBanners)
api.Get("/banner/:id", bannerController.GetBannerByID)
api.Post("/banner/create-banner", middleware.RoleRequired(utils.RoleAdministrator), bannerController.CreateBanner)
api.Put("/banner/update-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), bannerController.UpdateBanner)
api.Delete("/banner/delete-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), bannerController.DeleteBanner)
// # wilayah di indonesia #
api.Get("/wilayah-indonesia/provinces", controllers.GetProvinces)
api.Get("/wilayah-indonesia/regencies", controllers.GetRegencies)
api.Get("/wilayah-indonesia/subdistricts", controllers.GetDistricts)
api.Get("/wilayah-indonesia/villages", controllers.GetVillages)
api.Get("/wilayah-indonesia/provinces/:id", controllers.GetProvinceByID)
api.Get("/wilayah-indonesia/regencies/:id", controllers.GetRegencyByID)
api.Get("/wilayah-indonesia/subdistricts/:id", controllers.GetDistrictByID)
api.Get("/wilayah-indonesia/villages/:id", controllers.GetVillageByID)
// # request pickup by user (masyarakat) #
api.Get("/requestpickup", middleware.RoleRequired(utils.RoleMasyarakat, utils.RolePengepul), controllers.GetRequestPickupsByUser)
api.Post("/addrequestpickup", middleware.RoleRequired(utils.RoleMasyarakat), controllers.CreateRequestPickup)
api.Delete("/deleterequestpickup/:id", middleware.RoleRequired(utils.RoleMasyarakat), controllers.DeleteRequestPickup)
// # product post by pengepul
api.Get("/post/products", middleware.RoleRequired(utils.RolePengepul), controllers.GetAllProducts)
api.Get("/post/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.GetProductByID)
api.Get("/view/product/:storeid", middleware.RoleRequired(utils.RolePengepul), controllers.GetProductsByStore)
api.Post("/post/addproduct", middleware.RoleRequired(utils.RolePengepul), controllers.CreateProduct)
api.Put("/post/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.UpdateProduct)
api.Delete("/delete/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.DeleteProduct)
// # marketplace
api.Get("/store/marketplace", middleware.RoleRequired(utils.RolePengepul), controllers.GetStoresByUserID)
api.Get("/store/marketplace/:storeid", middleware.RoleRequired(utils.RolePengepul), controllers.GetStoreByID)
}

View File

@ -1,147 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetProvinces(c *fiber.Ctx) error {
provinces, err := services.GetProvinces()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve provinces",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Provinces retrieved successfully",
provinces,
))
}
func GetRegencies(c *fiber.Ctx) error {
regencies, err := services.GetRegencies()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve regencies",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Regencies retrieved successfully",
regencies,
))
}
func GetDistricts(c *fiber.Ctx) error {
districts, err := services.GetDistricts()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve districts",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Districts retrieved successfully",
districts,
))
}
func GetVillages(c *fiber.Ctx) error {
villages, err := services.GetVillages()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve villages",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Villages retrieved successfully",
villages,
))
}
func GetProvinceByID(c *fiber.Ctx) error {
id := c.Params("id")
province, err := services.GetProvinceByID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve province",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Province by id retrieved successfully",
province,
))
}
func GetRegencyByID(c *fiber.Ctx) error {
id := c.Params("id")
regency, err := services.GetRegencyByID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve regency",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Regency by id retrieved successfully",
regency,
))
}
func GetDistrictByID(c *fiber.Ctx) error {
id := c.Params("id")
district, err := services.GetDistrictByID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve district",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"District by id retrieved successfully",
district,
))
}
func GetVillageByID(c *fiber.Ctx) error {
id := c.Params("id")
village, err := services.GetVillageByID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to retrieve village",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Village by id retrieved successfully",
village,
))
}

View File

@ -1,205 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func CreateAddress(c *fiber.Ctx) error {
var input dto.AddressInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Mohon masukkan alamat dengan benar",
nil,
))
}
if err := input.ValidatePost(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
address, err := services.CreateAddress(userID, input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create address",
nil,
))
}
createdAtFormatted := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAtFormatted := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponse := dto.AddressResponse{
ID: address.ID,
Province: address.Province,
District: address.District,
Subdistrict: address.Subdistrict,
PostalCode: address.PostalCode,
Village: address.Village,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAtFormatted,
UpdatedAt: updatedAtFormatted,
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Address created successfully",
addressResponse,
))
}
func GetListAddress(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
addresses, err := services.GetAllAddressesByUserID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Addresses not found",
nil,
))
}
var addressResponses []dto.AddressResponse
for _, address := range addresses {
createdAtFormatted := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAtFormatted := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponse := dto.AddressResponse{
ID: address.ID,
Province: address.Province,
District: address.District,
Subdistrict: address.Subdistrict,
PostalCode: address.PostalCode,
Village: address.Village,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAtFormatted,
UpdatedAt: updatedAtFormatted,
}
addressResponses = append(addressResponses, addressResponse)
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Addresses fetched successfully",
addressResponses,
))
}
func GetAddressByID(c *fiber.Ctx) error {
addressID := c.Params("id")
address, err := services.GetAddressByID(addressID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Address not found",
nil,
))
}
createdAtFormatted := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAtFormatted := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponse := dto.AddressResponse{
ID: address.ID,
Province: address.Province,
District: address.District,
Subdistrict: address.Subdistrict,
PostalCode: address.PostalCode,
Village: address.Village,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAtFormatted,
UpdatedAt: updatedAtFormatted,
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Address fetched successfully",
addressResponse,
))
}
func UpdateAddress(c *fiber.Ctx) error {
addressID := c.Params("id")
var input dto.AddressInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := input.ValidateUpdate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
address, err := services.UpdateAddress(addressID, input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update address",
nil,
))
}
createdAtFormatted := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAtFormatted := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponse := dto.AddressResponse{
ID: address.ID,
Province: address.Province,
District: address.District,
Subdistrict: address.Subdistrict,
PostalCode: address.PostalCode,
Village: address.Village,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAtFormatted,
UpdatedAt: updatedAtFormatted,
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Address updated successfully",
addressResponse,
))
}
func DeleteAddress(c *fiber.Ctx) error {
addressID := c.Params("id")
err := services.DeleteAddress(addressID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete address",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Address deleted successfully",
nil,
))
}

View File

@ -1,122 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type ArticleController struct {
service *services.ArticleService
}
func NewArticleController(service *services.ArticleService) *ArticleController {
return &ArticleController{service: service}
}
func (ac *ArticleController) GetAllArticles(c *fiber.Ctx) error {
articles, err := ac.service.GetAllArticles()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
"Failed to fetch articles",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Articles fetched successfully",
articles,
))
}
func (ac *ArticleController) GetArticleByID(c *fiber.Ctx) error {
id := c.Params("id")
article, err := ac.service.GetArticleByID(id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Article not found",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Article fetched successfully",
article,
))
}
func (ac *ArticleController) CreateArticle(c *fiber.Ctx) error {
var request dto.ArticleCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
article, err := ac.service.CreateArticle(&request)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
err.Error(),
))
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Article created successfully",
article,
))
}
func (ac *ArticleController) UpdateArticle(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.ArticleUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
article, err := ac.service.UpdateArticle(id, &request)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Point updated successfully",
article,
))
}
func (ac *ArticleController) DeleteArticle(c *fiber.Ctx) error {
id := c.Params("id")
err := ac.service.DeleteArticle(id)
if err != nil {
if err.Error() == "article not found" {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Article not found",
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Article deleted successfully",
nil,
))
}

View File

@ -1,294 +0,0 @@
package controllers
import (
"context"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func Register(c *fiber.Ctx) error {
var userInput dto.RegisterUserInput
if err := c.BodyParser(&userInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := userInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
err := services.RegisterUser(userInput.Username, userInput.Name, userInput.Email, userInput.Phone, userInput.Password, userInput.ConfirmPassword, userInput.RoleId)
if err != nil {
return c.Status(fiber.StatusConflict).JSON(utils.FormatResponse(
fiber.StatusConflict,
err.Error(),
nil,
))
}
user, err := repositories.GetUserByEmailUsernameOrPhone(userInput.Email, userInput.RoleId)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch user after registration",
nil,
))
}
userResponse := dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
Name: user.Name,
Email: user.Email,
Phone: user.Phone,
RoleId: user.RoleID,
CreatedAt: utils.FormatDateToIndonesianFormat(user.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(user.UpdatedAt),
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"User registered successfully",
userResponse,
))
}
func Login(c *fiber.Ctx) error {
var credentials struct {
Identifier string `json:"identifier"`
Password string `json:"password"`
}
if err := c.BodyParser(&credentials); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
token, err := services.LoginUser(credentials.Identifier, credentials.Password)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
err.Error(),
nil,
))
}
ctx := context.Background()
err = config.RedisClient.Set(ctx, "auth_token:"+token, credentials.Identifier, time.Hour*24).Err()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to store session",
nil,
))
}
user, err := repositories.GetUserByEmailUsernameOrPhone(credentials.Identifier, "")
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
err.Error(),
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Login successful",
map[string]interface{}{
"token": token,
"role": user.RoleID,
},
))
}
func GetUserInfo(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
user, err := services.GetUserByID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"user tidak ditemukan",
nil,
))
}
userResponse := dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
RoleId: user.RoleID,
CreatedAt: utils.FormatDateToIndonesianFormat(user.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(user.UpdatedAt),
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Data user berhasil ditampilkan",
userResponse,
))
}
func UpdateUser(c *fiber.Ctx) error {
var userInput dto.UpdateUserInput
if err := c.BodyParser(&userInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := userInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized access",
nil,
))
}
err := services.UpdateUser(userID, userInput.Email, userInput.Username, userInput.Name, userInput.Phone)
if err != nil {
return c.Status(fiber.StatusConflict).JSON(utils.FormatResponse(
fiber.StatusConflict,
err.Error(),
nil,
))
}
user, err := repositories.GetUserByID(userID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch user after update",
nil,
))
}
userResponse := dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
Name: user.Name,
Email: user.Email,
Phone: user.Phone,
RoleId: user.RoleID,
CreatedAt: utils.FormatDateToIndonesianFormat(user.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(user.UpdatedAt),
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"User updated successfully",
userResponse,
))
}
func UpdatePassword(c *fiber.Ctx) error {
var passwordInput dto.UpdatePasswordInput
if err := c.BodyParser(&passwordInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := passwordInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
err := services.UpdatePassword(userID, passwordInput.OldPassword, passwordInput.NewPassword)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
err.Error(),
nil,
))
}
user, err := repositories.GetUserByID(userID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch user after password update",
nil,
))
}
updatedAtFormatted := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Password updated successfully",
map[string]string{
"updatedAt": updatedAtFormatted,
},
))
}
func Logout(c *fiber.Ctx) error {
tokenString := c.Get("Authorization")
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
if tokenString == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Token is required",
nil,
))
}
ctx := context.Background()
err := config.RedisClient.Del(ctx, "auth_token:"+tokenString).Err()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete session",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Logout successful",
nil,
))
}

View File

@ -1,128 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerController struct {
service *services.BannerService
}
func NewBannerController(service *services.BannerService) *BannerController {
return &BannerController{service: service}
}
func (bc *BannerController) GetAllBanners(c *fiber.Ctx) error {
banners, err := bc.service.GetAllBanners()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
"Failed to fetch banners",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Banners fetched successfully",
banners,
))
}
func (bc *BannerController) GetBannerByID(c *fiber.Ctx) error {
id := c.Params("id")
banner, err := bc.service.GetBannerByID(id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Banner not found",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Banner fetched successfully",
banner,
))
}
func (bc *BannerController) CreateBanner(c *fiber.Ctx) error {
var request dto.BannerCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
banner, err := bc.service.CreateBanner(&request)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
err.Error(),
))
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Banner created successfully",
banner,
))
}
func (bc *BannerController) UpdateBanner(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.BannerUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
banner, err := bc.service.UpdateBanner(id, &request)
if err != nil {
if err.Error() == "banner not found" {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Banner not found",
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Banner updated successfully",
banner,
))
}
func (bc *BannerController) DeleteBanner(c *fiber.Ctx) error {
id := c.Params("id")
err := bc.service.DeleteBanner(id)
if err != nil {
if err.Error() == "banner not found" {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Banner not found",
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Banner deleted successfully",
nil,
))
}

View File

@ -1,409 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetCoverageAreas(c *fiber.Ctx) error {
coverageAreas, err := services.GetCoverageAreas()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch coverage areas",
nil,
))
}
var coverageAreaResponses []dto.CoverageAreaResponse
for _, area := range coverageAreas {
coverageAreaResponses = append(coverageAreaResponses, dto.NewCoverageAreaResponse(
area.ID,
area.Province,
utils.FormatDateToIndonesianFormat(area.CreatedAt),
utils.FormatDateToIndonesianFormat(area.UpdatedAt),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage areas has been fetched",
coverageAreaResponses,
))
}
func GetCoverageAreaByIDProvince(c *fiber.Ctx) error {
id := c.Params("id")
coverageArea, err := services.GetCoverageAreaByID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch coverage area details by province",
nil,
))
}
var coverageAreaResponse dto.CoverageAreaWithDistrictsResponse
coverageAreaResponse.ID = coverageArea.ID
coverageAreaResponse.Province = coverageArea.Province
coverageAreaResponse.CreatedAt = utils.FormatDateToIndonesianFormat(coverageArea.CreatedAt)
coverageAreaResponse.UpdatedAt = utils.FormatDateToIndonesianFormat(coverageArea.UpdatedAt)
districts, err := services.GetCoverageDistricsByCoverageAreaID(coverageArea.ID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch coverage districts",
nil,
))
}
var coverageAreas []dto.CoverageAreaResponse
for _, district := range districts {
coverageAreas = append(coverageAreas, dto.NewCoverageAreaResponse(
district.ID,
district.District,
utils.FormatDateToIndonesianFormat(district.CreatedAt),
utils.FormatDateToIndonesianFormat(district.UpdatedAt),
))
}
coverageAreaResponse.CoverageArea = coverageAreas
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage areas detail by province has been fetched",
coverageAreaResponse,
))
}
func GetCoverageAreaByIDDistrict(c *fiber.Ctx) error {
id := c.Params("id")
coverageDetail, err := services.GetCoverageAreaByDistrictID(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch coverage area details by district",
nil,
))
}
coverageArea, err := services.GetCoverageAreaByID(coverageDetail.CoverageAreaID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch coverage area details by province",
nil,
))
}
subdistricts, err := services.GetSubdistrictsByCoverageDistrictID(coverageDetail.ID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch subdistricts",
nil,
))
}
var subdistrictResponses []dto.SubdistrictResponse
for _, loc := range subdistricts {
subdistrictResponses = append(subdistrictResponses, dto.NewSubdistrictResponse(
loc.ID,
loc.Subdistrict,
utils.FormatDateToIndonesianFormat(loc.CreatedAt),
utils.FormatDateToIndonesianFormat(loc.UpdatedAt),
))
}
coverageAreaResponse := dto.NewCoverageAreaDetailWithLocation(
coverageDetail.ID,
coverageArea.Province,
coverageDetail.District,
utils.FormatDateToIndonesianFormat(coverageDetail.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageDetail.UpdatedAt),
subdistrictResponses,
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage areas detail by district has been fetched",
coverageAreaResponse,
))
}
func CreateCoverageArea(c *fiber.Ctx) error {
var request dto.CoverageAreaCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageArea, err := services.CreateCoverageArea(request.Province)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create coverage area",
nil,
))
}
coverageAreaResponse := dto.NewCoverageAreaResponse(
coverageArea.ID,
coverageArea.Province,
utils.FormatDateToIndonesianFormat(coverageArea.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageArea.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage area has been created",
coverageAreaResponse,
))
}
func CreateCoverageDistrict(c *fiber.Ctx) error {
var request dto.CoverageDistrictCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageDistrict, err := services.CreateCoverageDistrict(request.CoverageAreaID, request.District)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create coverage district",
nil,
))
}
coverageDistrictResponse := dto.NewCoverageAreaResponse(
coverageDistrict.ID,
coverageDistrict.District,
utils.FormatDateToIndonesianFormat(coverageDistrict.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageDistrict.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage district has been created",
coverageDistrictResponse,
))
}
func CreateCoverageSubdistrict(c *fiber.Ctx) error {
var request dto.CoverageSubdistrictCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageSubdistrict, err := services.CreateCoverageSubdistrict(
request.CoverageAreaID,
request.CoverageDistrictId,
request.Subdistrict,
)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create coverage subdistrict",
nil,
))
}
coverageSubdistrictResponse := dto.NewCoverageAreaResponse(
coverageSubdistrict.ID,
coverageSubdistrict.Subdistrict,
utils.FormatDateToIndonesianFormat(coverageSubdistrict.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageSubdistrict.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage subdistrict has been created",
coverageSubdistrictResponse,
))
}
func UpdateCoverageArea(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.CoverageAreaUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageArea, err := services.UpdateCoverageArea(id, domain.CoverageArea{
Province: request.Province,
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update coverage area",
nil,
))
}
coverageAreaResponse := dto.NewCoverageAreaResponse(
coverageArea.ID,
coverageArea.Province,
utils.FormatDateToIndonesianFormat(coverageArea.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageArea.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage area has been updated",
coverageAreaResponse,
))
}
func UpdateCoverageDistrict(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.CoverageDistrictUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageDistrict, err := services.UpdateCoverageDistrict(id, domain.CoverageDistric{
District: request.District,
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update coverage district",
nil,
))
}
coverageDistrictResponse := dto.NewCoverageAreaResponse(
coverageDistrict.ID,
coverageDistrict.District,
utils.FormatDateToIndonesianFormat(coverageDistrict.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageDistrict.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage district has been updated",
coverageDistrictResponse,
))
}
func UpdateCoverageSubdistrict(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.CoverageSubdistrictUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
coverageSubdistrict, err := services.UpdateCoverageSubdistrict(id, domain.CoverageSubdistrict{
Subdistrict: request.Subdistrict,
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update coverage subdistrict",
nil,
))
}
coverageSubdistrictResponse := dto.NewSubdistrictResponse(
coverageSubdistrict.ID,
coverageSubdistrict.Subdistrict,
utils.FormatDateToIndonesianFormat(coverageSubdistrict.CreatedAt),
utils.FormatDateToIndonesianFormat(coverageSubdistrict.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage subdistrict has been updated",
coverageSubdistrictResponse,
))
}
func DeleteCoverageArea(c *fiber.Ctx) error {
id := c.Params("id")
if err := services.DeleteCoverageArea(id); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete coverage area",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage area has been deleted",
nil,
))
}
func DeleteCoverageDistrict(c *fiber.Ctx) error {
id := c.Params("id")
if err := services.DeleteCoverageDistrict(id); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete coverage district",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage district has been deleted",
nil,
))
}
func DeleteCoverageSubdistrict(c *fiber.Ctx) error {
id := c.Params("id")
if err := services.DeleteCoverageSubdistrict(id); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete coverage subdistrict",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Coverage subdistrict has been deleted",
nil,
))
}

View File

@ -1,123 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type PointController struct {
service *services.PointService
}
func NewPointController(service *services.PointService) *PointController {
return &PointController{service: service}
}
func (pc *PointController) GetAllPoints(c *fiber.Ctx) error {
points, err := pc.service.GetAllPoints()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
"Failed to fetch points",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Points fetched successfully",
points,
))
}
func (pc *PointController) GetPointByID(c *fiber.Ctx) error {
id := c.Params("id")
point, err := pc.service.GetPointByID(id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Point not found",
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Point fetched successfully",
point,
))
}
func (pc *PointController) CreatePoint(c *fiber.Ctx) error {
var request dto.PointCreateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
point, err := pc.service.CreatePoint(&request)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
err.Error(),
))
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Point created successfully",
point,
))
}
func (pc *PointController) UpdatePoint(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.PointUpdateRequest
if err := c.BodyParser(&request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
"Invalid request body",
))
}
point, err := pc.service.UpdatePoint(id, &request)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse(
fiber.StatusBadRequest,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Point updated successfully",
point,
))
}
func (pc *PointController) DeletePoint(c *fiber.Ctx) error {
id := c.Params("id")
err := pc.service.DeletePoint(id)
if err != nil {
if err.Error() == "point not found" {
return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse(
fiber.StatusNotFound,
"Point not found",
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse(
fiber.StatusInternalServerError,
err.Error(),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Point deleted successfully",
nil,
))
}

View File

@ -1,262 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetAllProducts(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: user ID is missing",
nil,
))
}
storeID := c.Query("storeID", "")
limit := c.QueryInt("limit", 10)
page := c.QueryInt("page", 1)
if limit <= 0 || page <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid pagination parameters",
nil,
))
}
var products []dto.ProductResponseWithSoldDTO
var err error
if storeID != "" {
products, err = services.GetProductsByStoreID(storeID, limit, page)
} else {
products, err = services.GetProductsByUserID(userID, limit, page)
}
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch products",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Products fetched successfully",
products,
))
}
func GetProductByID(c *fiber.Ctx) error {
storeID, ok := c.Locals("userID").(string)
if !ok || storeID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: store ID is missing",
nil,
))
}
productID := c.Params("productid")
if productID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Product ID is required",
nil,
))
}
product, err := services.GetProductByIDAndStoreID(productID, storeID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Product not found",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Product fetched successfully",
product,
))
}
func GetProductsByStore(c *fiber.Ctx) error {
storeID := c.Params("storeid")
if storeID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Store ID is required",
nil,
))
}
limit := c.QueryInt("limit", 10)
page := c.QueryInt("page", 1)
if limit <= 0 || page <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid pagination parameters",
nil,
))
}
products, err := services.GetProductsByStoreID(storeID, limit, page)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Store not found",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Products fetched successfully",
products,
))
}
func CreateProduct(c *fiber.Ctx) error {
var input dto.CreateProductRequestDTO
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: user ID is missing",
nil,
))
}
if input.StoreID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Store ID is required in the body",
nil,
))
}
product, err := services.CreateProduct(input, userID)
if err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(utils.FormatResponse(
fiber.StatusUnprocessableEntity,
err.Error(),
nil,
))
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Product created successfully",
product,
))
}
func UpdateProduct(c *fiber.Ctx) error {
productID := c.Params("productid")
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: user ID is missing",
nil,
))
}
if productID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Product ID is required",
nil,
))
}
var input dto.UpdateProductRequestDTO
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request payload",
nil,
))
}
if err := dto.GetValidator().Struct(input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid product data",
nil,
))
}
updatedProduct, err := services.UpdateProduct(productID, input)
if err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(utils.FormatResponse(
fiber.StatusUnprocessableEntity,
err.Error(),
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Product updated successfully",
updatedProduct,
))
}
func DeleteProduct(c *fiber.Ctx) error {
productID := c.Params("productid")
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: user ID is missing",
nil,
))
}
if productID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Product ID is required",
nil,
))
}
err := services.DeleteProduct(productID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Product not found or unable to delete",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Product deleted successfully",
nil,
))
}

View File

@ -1,203 +0,0 @@
package controllers
import (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetRequestPickupsByUser(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"User not authenticated",
nil,
))
}
service := services.NewRequestPickupService(repositories.NewRequestPickupRepository())
requestPickups, err := service.GetRequestPickupsByUser(userID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch request pickups",
nil,
))
}
var requestPickupResponses []dto.RequestPickupResponse
for _, requestPickup := range requestPickups {
userAddress := dto.UserAddressDTO{
Province: requestPickup.UserAddress.Province,
District: requestPickup.UserAddress.District,
Subdistrict: requestPickup.UserAddress.Subdistrict,
PostalCode: requestPickup.UserAddress.PostalCode,
Village: requestPickup.UserAddress.Village,
Detail: requestPickup.UserAddress.Detail,
Geography: requestPickup.UserAddress.Geography,
}
var requestItems []dto.RequestItemDTO
for _, item := range requestPickup.Request {
requestItems = append(requestItems, dto.RequestItemDTO{
TrashCategory: item.TrashCategory.Name,
EstimatedAmount: item.EstimatedAmount,
})
}
requestPickupResponses = append(requestPickupResponses, dto.NewRequestPickupResponse(
requestPickup.ID,
requestPickup.UserID,
requestPickup.RequestTime,
requestPickup.StatusRequest,
requestItems,
userAddress,
utils.FormatDateToIndonesianFormat(requestPickup.CreatedAt),
utils.FormatDateToIndonesianFormat(requestPickup.UpdatedAt),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Request pickup by user has been fetched",
requestPickupResponses,
))
}
func CreateRequestPickup(c *fiber.Ctx) error {
var req dto.RequestPickupRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid request body",
nil,
))
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"User not authenticated",
nil,
))
}
if req.UserAddressID == "" || len(req.RequestItems) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Missing required fields",
nil,
))
}
var requestItems []domain.RequestItem
for _, item := range req.RequestItems {
requestItems = append(requestItems, domain.RequestItem{
TrashCategoryID: item.TrashCategory,
EstimatedAmount: item.EstimatedAmount,
})
}
requestPickup := &domain.RequestPickup{
UserID: userID,
Request: requestItems,
RequestTime: req.RequestTime,
UserAddressID: req.UserAddressID,
StatusRequest: "Pending",
}
service := services.NewRequestPickupService(repositories.NewRequestPickupRepository())
if err := service.CreateRequestPickup(requestPickup); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create request pickup",
nil,
))
}
detail, err := service.GetRequestPickupByID(requestPickup.ID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch created request pickup",
nil,
))
}
var requestItemsDTO []dto.RequestItemDTO
for _, item := range detail.Request {
requestItemsDTO = append(requestItemsDTO, dto.RequestItemDTO{
TrashCategory: item.TrashCategory.Name,
EstimatedAmount: item.EstimatedAmount,
})
}
userAddressDTO := dto.UserAddressDTO{
Province: detail.UserAddress.Province,
District: detail.UserAddress.District,
Subdistrict: detail.UserAddress.Subdistrict,
PostalCode: detail.UserAddress.PostalCode,
Village: detail.UserAddress.Village,
Detail: detail.UserAddress.Detail,
Geography: detail.UserAddress.Geography,
}
response := dto.NewRequestPickupResponse(
detail.ID,
detail.UserID,
detail.RequestTime,
detail.StatusRequest,
requestItemsDTO,
userAddressDTO,
utils.FormatDateToIndonesianFormat(detail.CreatedAt),
utils.FormatDateToIndonesianFormat(detail.UpdatedAt),
)
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Request pickup created successfully",
response,
))
}
func DeleteRequestPickup(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Missing required ID",
nil,
))
}
service := services.NewRequestPickupService(repositories.NewRequestPickupRepository())
if err := service.DeleteRequestPickupByID(id); err != nil {
if err.Error() == fmt.Sprintf("request pickup with id %s not found", id) {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Request pickup not found",
nil,
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete request pickup",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Request pickup deleted successfully",
nil,
))
}

View File

@ -1,43 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetUserRoleByID(c *fiber.Ctx) error {
id := c.Params("id")
role, err := services.GetUserRoleByID(id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"UserRole tidak ditemukan",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"UserRole ditemukan",
role,
))
}
func GetAllUserRoles(c *fiber.Ctx) error {
roles, err := services.GetAllUserRoles()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Gagal mengambil data UserRole",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Daftar UserRole",
roles,
))
}

View File

@ -1,70 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetStoreByID(c *fiber.Ctx) error {
storeID := c.Params("storeid")
if storeID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Store ID is required",
nil,
))
}
store, err := services.GetStoreByID(storeID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Store not found",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Store fetched successfully",
store,
))
}
func GetStoresByUserID(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Unauthorized: user ID is missing",
nil,
))
}
limit := c.QueryInt("limit", 0)
page := c.QueryInt("page", 1)
if limit < 0 || page <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid pagination parameters",
nil,
))
}
stores, err := services.GetStoresByUserID(userID, limit, page)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch stores",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Stores fetched successfully",
stores,
))
}

View File

@ -1,293 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetTrashCategories(c *fiber.Ctx) error {
trashCategories, err := services.GetTrashCategories()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch trash categories",
nil,
))
}
var response []dto.TrashCategoryResponse
for _, category := range trashCategories {
response = append(response, dto.NewTrashCategoryResponse(
category.ID,
category.Name,
utils.FormatDateToIndonesianFormat(category.CreatedAt),
utils.FormatDateToIndonesianFormat(category.UpdatedAt),
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash categories fetched successfully",
response,
))
}
func GetTrashCategoryDetail(c *fiber.Ctx) error {
id := c.Params("id")
trashCategoryDetail, err := services.GetTrashCategoryDetail(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch trash category detail",
nil,
))
}
detailResponse := dto.NewTrashCategoryResponse(
trashCategoryDetail.ID,
trashCategoryDetail.Name,
utils.FormatDateToIndonesianFormat(trashCategoryDetail.CreatedAt),
utils.FormatDateToIndonesianFormat(trashCategoryDetail.UpdatedAt),
)
var detailResponseList []dto.TrashDetailResponse
if trashCategoryDetail.Details != nil {
for _, detail := range trashCategoryDetail.Details {
detailResponseList = append(detailResponseList, dto.NewTrashDetailResponse(
detail.ID,
detail.Description,
detail.Price,
utils.FormatDateToIndonesianFormat(detail.CreatedAt),
utils.FormatDateToIndonesianFormat(detail.UpdatedAt),
))
}
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash category detail fetched successfully",
struct {
Category dto.TrashCategoryResponse `json:"category"`
Details []dto.TrashDetailResponse `json:"details,omitempty"`
}{
Category: detailResponse,
Details: detailResponseList,
},
))
}
func CreateTrashCategory(c *fiber.Ctx) error {
var categoryInput dto.TrashCategoryDTO
if err := c.BodyParser(&categoryInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := categoryInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Validation failed: "+err.Error(),
nil,
))
}
newCategory, err := services.CreateTrashCategory(categoryInput.Name)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create trash category",
nil,
))
}
categoryResponse := map[string]interface{}{
"id": newCategory.ID,
"name": newCategory.Name,
"createdAt": newCategory.CreatedAt,
"updatedAt": newCategory.UpdatedAt,
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Trash category created successfully",
categoryResponse,
))
}
func CreateTrashDetail(c *fiber.Ctx) error {
var detailInput dto.TrashDetailDTO
if err := c.BodyParser(&detailInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := detailInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Validation failed: "+err.Error(),
nil,
))
}
newDetail, err := services.CreateTrashDetail(detailInput.CategoryID, detailInput.Description, detailInput.Price)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create trash detail",
nil,
))
}
detailResponse := map[string]interface{}{
"id": newDetail.ID,
"description": newDetail.Description,
"price": newDetail.Price,
"createdAt": newDetail.CreatedAt,
"updatedAt": newDetail.UpdatedAt,
}
return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse(
fiber.StatusCreated,
"Trash detail created successfully",
detailResponse,
))
}
func UpdateTrashCategory(c *fiber.Ctx) error {
id := c.Params("id")
var categoryInput dto.UpdateTrashCategoryDTO
if err := c.BodyParser(&categoryInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := categoryInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Validation failed: "+err.Error(),
nil,
))
}
updatedCategory, err := services.UpdateTrashCategory(id, categoryInput.Name)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update trash category",
nil,
))
}
response := dto.NewTrashCategoryResponse(
updatedCategory.ID,
updatedCategory.Name,
utils.FormatDateToIndonesianFormat(updatedCategory.CreatedAt),
utils.FormatDateToIndonesianFormat(updatedCategory.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash category updated successfully",
response,
))
}
func UpdateTrashDetail(c *fiber.Ctx) error {
id := c.Params("id")
var detailInput dto.UpdateTrashDetailDTO
if err := c.BodyParser(&detailInput); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Invalid input data",
nil,
))
}
if err := detailInput.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Validation failed: "+err.Error(),
nil,
))
}
updatedDetail, err := services.UpdateTrashDetail(id, detailInput.Description, detailInput.Price)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update trash detail",
nil,
))
}
response := dto.NewTrashDetailResponse(
updatedDetail.ID,
updatedDetail.Description,
updatedDetail.Price,
utils.FormatDateToIndonesianFormat(updatedDetail.CreatedAt),
utils.FormatDateToIndonesianFormat(updatedDetail.UpdatedAt),
)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash detail updated successfully",
response,
))
}
func DeleteTrashCategory(c *fiber.Ctx) error {
id := c.Params("id")
err := services.DeleteTrashCategory(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete trash category",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash category deleted successfully",
nil,
))
}
func DeleteTrashDetail(c *fiber.Ctx) error {
id := c.Params("id")
err := services.DeleteTrashDetail(id)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to delete trash detail",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Trash detail deleted successfully",
nil,
))
}

View File

@ -1,75 +0,0 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetListUsers(c *fiber.Ctx) error {
users, err := services.GetUsers()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch users",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Users fetched successfully",
users,
))
}
func GetUsersByRole(c *fiber.Ctx) error {
roleID := c.Params("roleID")
users, err := services.GetUsersByRole(roleID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch users by role",
nil,
))
}
if len(users) == 0 {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"No users found for the specified role",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"Users fetched successfully",
users,
))
}
func GetUserByUserID(c *fiber.Ctx) error {
userID := c.Params("userID")
user, err := services.GetUserByUserID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"User not found",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"User fetched successfully",
struct {
User dto.UserResponseDTO `json:"user"`
}{
User: user,
},
))
}

View File

@ -1,240 +0,0 @@
package controllers
import (
"time"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func CreatePin(c *fiber.Ctx) error {
var input dto.PinInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
if err := input.ValidateCreate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
redisPin, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result()
if err != nil && err != redis.Nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to check PIN from Redis",
nil,
))
}
if redisPin != "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"PIN sudah ada, tidak perlu dibuat lagi",
nil,
))
}
pin, err := services.CreatePin(userID, input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create PIN",
nil,
))
}
err = config.RedisClient.Set(c.Context(), "pin:"+userID, pin.Pin, time.Minute*30).Err()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to save PIN to Redis",
nil,
))
}
formattedCreatedAt := utils.FormatDateToIndonesianFormat(pin.CreatedAt)
pinResponse := dto.PinResponse{
CreatedAt: formattedCreatedAt,
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN created successfully",
pinResponse,
))
}
func GetPinStatus(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
_, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result()
if err == redis.Nil {
pin, err := services.GetPinByUserID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Anda belum membuat PIN",
nil,
))
}
formattedCreatedAt := utils.FormatDateToIndonesianFormat(pin.CreatedAt)
formattedUpdatedAt := utils.FormatDateToIndonesianFormat(pin.UpdatedAt)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN sudah dibuat",
map[string]interface{}{
"createdAt": formattedCreatedAt,
"updatedAt": formattedUpdatedAt,
},
))
} else if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch PIN from Redis",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN sudah dibuat",
map[string]interface{}{
"createdAt": "PIN ditemukan di Redis",
"updatedAt": "PIN ditemukan di Redis",
},
))
}
func GetPin(c *fiber.Ctx) error {
var input dto.PinInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
userID := c.Locals("userID").(string)
redisPin, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result()
if err == redis.Nil {
pin, err := services.GetPinByUserID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"Sepertinya anda belum membuat pin",
nil,
))
}
isPinValid := services.CheckPin(pin.Pin, input.Pin)
if isPinValid {
config.RedisClient.Set(c.Context(), "pin:"+userID, pin.Pin, time.Minute*30)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN benar",
true,
))
}
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"PIN salah",
false,
))
} else if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to fetch PIN from Redis",
nil,
))
}
isPinValid := services.CheckPin(redisPin, input.Pin)
if isPinValid {
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN benar",
true,
))
}
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"PIN salah",
false,
))
}
func UpdatePin(c *fiber.Ctx) error {
var input dto.PinUpdateInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
if err := input.ValidateUpdate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
updatedPin, err := services.UpdatePin(userID, input.OldPin, input.NewPin)
if err != nil {
if err.Error() == "PIN lama salah" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"PIN lama salah",
nil,
))
}
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update PIN",
nil,
))
}
config.RedisClient.Del(c.Context(), "pin:"+userID)
config.RedisClient.Set(c.Context(), "pin:"+userID, updatedPin.Pin, time.Minute*30)
formattedUpdatedAt := utils.FormatDateToIndonesianFormat(updatedPin.UpdatedAt)
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN updated successfully",
map[string]interface{}{
"id": updatedPin.ID,
"updatedAt": formattedUpdatedAt,
},
))
}

View File

@ -0,0 +1,92 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type AddressHandler struct {
AddressService services.AddressService
}
func NewAddressHandler(addressService services.AddressService) *AddressHandler {
return &AddressHandler{AddressService: addressService}
}
func (h *AddressHandler) CreateAddress(c *fiber.Ctx) error {
var requestAddressDTO dto.CreateAddressDTO
if err := c.BodyParser(&requestAddressDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := requestAddressDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
addressResponse, err := h.AddressService.CreateAddress(c.Locals("userID").(string), requestAddressDTO)
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.CreateResponse(c, addressResponse, "user address created successfully")
}
func (h *AddressHandler) GetAddressByUserID(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
addresses, err := h.AddressService.GetAddressByUserID(userID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, addresses, "User addresses fetched successfully")
}
func (h *AddressHandler) GetAddressByID(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
addressID := c.Params("address_id")
address, err := h.AddressService.GetAddressByID(userID, addressID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, address, "Address fetched successfully")
}
func (h *AddressHandler) UpdateAddress(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
addressID := c.Params("address_id")
var addressDTO dto.CreateAddressDTO
if err := c.BodyParser(&addressDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := addressDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
updatedAddress, err := h.AddressService.UpdateAddress(userID, addressID, addressDTO)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, updatedAddress, "User address updated successfully")
}
func (h *AddressHandler) DeleteAddress(c *fiber.Ctx) error {
userID := c.Locals("userID").(string)
addressID := c.Params("address_id")
err := h.AddressService.DeleteAddress(userID, addressID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusForbidden, err.Error())
}
return utils.SuccessResponse(c, nil, "Address deleted successfully")
}

View File

@ -0,0 +1,137 @@
package handler
import (
"fmt"
"mime/multipart"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type ArticleHandler struct {
ArticleService services.ArticleService
}
func NewArticleHandler(articleService services.ArticleService) *ArticleHandler {
return &ArticleHandler{ArticleService: articleService}
}
func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
var request dto.RequestArticleDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
coverImage, err := c.FormFile("coverImage")
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Cover image is required")
}
articleResponse, err := h.ArticleService.CreateArticle(request, coverImage)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.CreateResponse(c, articleResponse, "Article created successfully")
}
func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil || page < 1 {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil || limit < 1 {
limit = 0
}
articles, totalArticles, err := h.ArticleService.GetAllArticles(page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch articles")
}
fmt.Printf("Total Articles: %d\n", totalArticles)
if page == 0 && limit == 0 {
return utils.NonPaginatedResponse(c, articles, totalArticles, "Articles fetched successfully")
}
return utils.PaginatedResponse(c, articles, page, limit, totalArticles, "Articles fetched successfully")
}
func (h *ArticleHandler) GetArticleByID(c *fiber.Ctx) error {
id := c.Params("article_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Article ID is required")
}
article, err := h.ArticleService.GetArticleByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "Article not found")
}
return utils.SuccessResponse(c, article, "Article fetched successfully")
}
func (h *ArticleHandler) UpdateArticle(c *fiber.Ctx) error {
id := c.Params("article_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Article ID is required")
}
var request dto.RequestArticleDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
var coverImage *multipart.FileHeader
coverImage, err := c.FormFile("coverImage")
if err != nil && err.Error() != "no such file" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Cover image is required")
}
articleResponse, err := h.ArticleService.UpdateArticle(id, request, coverImage)
if err != nil {
if err.Error() == fmt.Sprintf("article with ID %s not found", id) {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, articleResponse, "Article updated successfully")
}
func (h *ArticleHandler) DeleteArticle(c *fiber.Ctx) error {
id := c.Params("article_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Article ID is required")
}
err := h.ArticleService.DeleteArticle(id)
if err != nil {
if err.Error() == fmt.Sprintf("article with ID %s not found", id) {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Article deleted successfully")
}

View File

@ -0,0 +1,72 @@
package handler
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type UserHandler struct {
UserService services.UserService
}
func NewUserHandler(userService services.UserService) *UserHandler {
return &UserHandler{UserService: userService}
}
func (h *UserHandler) Login(c *fiber.Ctx) error {
var loginDTO dto.LoginDTO
if err := c.BodyParser(&loginDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
validationErrors, valid := loginDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, validationErrors)
}
user, err := h.UserService.Login(loginDTO)
if err != nil {
return utils.GenericResponse(c, fiber.StatusUnauthorized, err.Error())
}
return utils.SuccessResponse(c, user, "Login successful")
}
func (h *UserHandler) Register(c *fiber.Ctx) error {
var registerDTO dto.RegisterDTO
if err := c.BodyParser(&registerDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}})
}
errors, valid := registerDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
userResponse, err := h.UserService.Register(registerDTO)
if err != nil {
return utils.GenericResponse(c, fiber.StatusConflict, err.Error())
}
return utils.CreateResponse(c, userResponse, "Registration successful")
}
func (h *UserHandler) Logout(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
log.Println("Unauthorized access: User ID not found in session")
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
err := utils.DeleteSessionData(userID)
if err != nil {
return utils.InternalServerErrorResponse(c, "Error logging out")
}
return utils.SuccessResponse(c, nil, "Logout successful")
}

View File

@ -0,0 +1,108 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerHandler struct {
BannerService services.BannerService
}
func NewBannerHandler(bannerService services.BannerService) *BannerHandler {
return &BannerHandler{BannerService: bannerService}
}
func (h *BannerHandler) CreateBanner(c *fiber.Ctx) error {
var request dto.RequestBannerDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateBannerInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
bannerImage, err := c.FormFile("bannerimage")
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner image is required")
}
bannerResponse, err := h.BannerService.CreateBanner(request, bannerImage)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.CreateResponse(c, bannerResponse, "Banner created successfully")
}
func (h *BannerHandler) GetAllBanners(c *fiber.Ctx) error {
banners, err := h.BannerService.GetAllBanners()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch banners")
}
return utils.NonPaginatedResponse(c, banners, len(banners), "Banners fetched successfully")
}
func (h *BannerHandler) GetBannerByID(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
banner, err := h.BannerService.GetBannerByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "invalid banner id")
}
return utils.SuccessResponse(c, banner, "Banner fetched successfully")
}
func (h *BannerHandler) UpdateBanner(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
var request dto.RequestBannerDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateBannerInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
bannerImage, err := c.FormFile("bannerimage")
if err != nil && err.Error() != "no such file" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner image is required")
}
bannerResponse, err := h.BannerService.UpdateBanner(id, request, bannerImage)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, bannerResponse, "Banner updated successfully")
}
func (h *BannerHandler) DeleteBanner(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
err := h.BannerService.DeleteBanner(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Banner deleted successfully")
}

View File

@ -0,0 +1,98 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type InitialCointHandler struct {
InitialCointService services.InitialCointService
}
func NewInitialCointHandler(initialCointService services.InitialCointService) *InitialCointHandler {
return &InitialCointHandler{InitialCointService: initialCointService}
}
func (h *InitialCointHandler) CreateInitialCoint(c *fiber.Ctx) error {
var request dto.RequestInitialCointDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateCointInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
initialCointResponse, err := h.InitialCointService.CreateInitialCoint(request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.CreateResponse(c, initialCointResponse, "Initial coint created successfully")
}
func (h *InitialCointHandler) GetAllInitialCoints(c *fiber.Ctx) error {
initialCoints, err := h.InitialCointService.GetAllInitialCoints()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch initial coints")
}
return utils.NonPaginatedResponse(c, initialCoints, len(initialCoints), "Initial coints fetched successfully")
}
func (h *InitialCointHandler) GetInitialCointByID(c *fiber.Ctx) error {
id := c.Params("coin_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Coin ID is required")
}
initialCoint, err := h.InitialCointService.GetInitialCointByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "Invalid coin ID")
}
return utils.SuccessResponse(c, initialCoint, "Initial coint fetched successfully")
}
func (h *InitialCointHandler) UpdateInitialCoint(c *fiber.Ctx) error {
id := c.Params("coin_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Coin ID is required")
}
var request dto.RequestInitialCointDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateCointInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
initialCointResponse, err := h.InitialCointService.UpdateInitialCoint(id, request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, initialCointResponse, "Initial coint updated successfully")
}
func (h *InitialCointHandler) DeleteInitialCoint(c *fiber.Ctx) error {
id := c.Params("coin_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Coin ID is required")
}
err := h.InitialCointService.DeleteInitialCoint(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Initial coint deleted successfully")
}

View File

@ -0,0 +1,46 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type RoleHandler struct {
RoleService services.RoleService
}
func NewRoleHandler(roleService services.RoleService) *RoleHandler {
return &RoleHandler{RoleService: roleService}
}
func (h *RoleHandler) GetRoles(c *fiber.Ctx) error {
roleID, ok := c.Locals("roleID").(string)
if !ok || roleID != utils.RoleAdministrator {
return utils.GenericResponse(c, fiber.StatusForbidden, "Forbidden: You don't have permission to access this resource")
}
roles, err := h.RoleService.GetRoles()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, roles, "Roles fetched successfully")
}
func (h *RoleHandler) GetRoleByID(c *fiber.Ctx) error {
roleID := c.Params("role_id")
roleIDFromSession, ok := c.Locals("roleID").(string)
if !ok || roleIDFromSession != utils.RoleAdministrator {
return utils.GenericResponse(c, fiber.StatusForbidden, "Forbidden: You don't have permission to access this resource")
}
role, err := h.RoleService.GetRoleByID(roleID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "role id tidak ditemukan")
}
return utils.SuccessResponse(c, role, "Role fetched successfully")
}

View File

@ -0,0 +1,128 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type TrashHandler struct {
TrashService services.TrashService
}
func NewTrashHandler(trashService services.TrashService) *TrashHandler {
return &TrashHandler{TrashService: trashService}
}
func (h *TrashHandler) CreateCategory(c *fiber.Ctx) error {
var request dto.RequestTrashCategoryDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
categoryResponse, err := h.TrashService.CreateCategory(request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to create category: "+err.Error())
}
return utils.CreateResponse(c, categoryResponse, "Category created successfully")
}
func (h *TrashHandler) AddDetailToCategory(c *fiber.Ctx) error {
var request dto.RequestTrashDetailDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
detailResponse, err := h.TrashService.AddDetailToCategory(request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to add detail to category: "+err.Error())
}
return utils.CreateResponse(c, detailResponse, "Trash detail added successfully")
}
func (h *TrashHandler) GetCategories(c *fiber.Ctx) error {
categories, err := h.TrashService.GetCategories()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch categories: "+err.Error())
}
return utils.NonPaginatedResponse(c, categories, len(categories), "Categories retrieved successfully")
}
func (h *TrashHandler) GetCategoryByID(c *fiber.Ctx) error {
id := c.Params("category_id")
category, err := h.TrashService.GetCategoryByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "Category not found: "+err.Error())
}
return utils.SuccessResponse(c, category, "Category retrieved successfully")
}
func (h *TrashHandler) GetTrashDetailByID(c *fiber.Ctx) error {
id := c.Params("detail_id")
detail, err := h.TrashService.GetTrashDetailByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, "Trash detail not found: "+err.Error())
}
return utils.SuccessResponse(c, detail, "Trash detail retrieved successfully")
}
func (h *TrashHandler) UpdateCategory(c *fiber.Ctx) error {
id := c.Params("category_id")
var request dto.RequestTrashCategoryDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}})
}
updatedCategory, err := h.TrashService.UpdateCategory(id, request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error updating category: "+err.Error())
}
return utils.SuccessResponse(c, updatedCategory, "Category updated successfully")
}
func (h *TrashHandler) UpdateDetail(c *fiber.Ctx) error {
id := c.Params("detail_id")
var request dto.RequestTrashDetailDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}})
}
updatedDetail, err := h.TrashService.UpdateDetail(id, request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error updating detail: "+err.Error())
}
return utils.SuccessResponse(c, updatedDetail, "Trash detail updated successfully")
}
func (h *TrashHandler) DeleteCategory(c *fiber.Ctx) error {
id := c.Params("category_id")
if err := h.TrashService.DeleteCategory(id); err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error deleting category: "+err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Category deleted successfully")
}
func (h *TrashHandler) DeleteDetail(c *fiber.Ctx) error {
id := c.Params("detail_id")
if err := h.TrashService.DeleteDetail(id); err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error deleting detail: "+err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Trash detail deleted successfully")
}

View File

@ -0,0 +1,98 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type UserProfileHandler struct {
UserProfileService services.UserProfileService
}
func NewUserProfileHandler(userProfileService services.UserProfileService) *UserProfileHandler {
return &UserProfileHandler{UserProfileService: userProfileService}
}
func (h *UserProfileHandler) GetUserProfile(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
userProfile, err := h.UserProfileService.GetUserProfile(userID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.SuccessResponse(c, userProfile, "User profile retrieved successfully")
}
func (h *UserProfileHandler) UpdateUserProfile(c *fiber.Ctx) error {
var updateData dto.UpdateUserDTO
if err := c.BodyParser(&updateData); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
errors, valid := updateData.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
userResponse, err := h.UserProfileService.UpdateUserProfile(userID, updateData)
if err != nil {
return utils.GenericResponse(c, fiber.StatusConflict, err.Error())
}
return utils.SuccessResponse(c, userResponse, "User profile updated successfully")
}
func (h *UserProfileHandler) UpdateUserPassword(c *fiber.Ctx) error {
var passwordData dto.UpdatePasswordDTO
if err := c.BodyParser(&passwordData); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
errors, valid := passwordData.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
message, err := h.UserProfileService.UpdateUserPassword(userID, passwordData)
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, message)
}
func (h *UserProfileHandler) UpdateUserAvatar(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
file, err := c.FormFile("avatar")
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, "No avatar file uploaded")
}
message, err := h.UserProfileService.UpdateUserAvatar(userID, file)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, message)
}

View File

@ -0,0 +1,101 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type UserPinHandler struct {
UserPinService services.UserPinService
}
func NewUserPinHandler(userPinService services.UserPinService) *UserPinHandler {
return &UserPinHandler{UserPinService: userPinService}
}
func (h *UserPinHandler) VerifyUserPin(c *fiber.Ctx) error {
var requestUserPinDTO dto.RequestUserPinDTO
if err := c.BodyParser(&requestUserPinDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := requestUserPinDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
message, err := h.UserPinService.VerifyUserPin(userID, requestUserPinDTO.Pin)
if err != nil {
return utils.GenericResponse(c, fiber.StatusUnauthorized, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, message)
}
func (h *UserPinHandler) CheckPinStatus(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
status, err := h.UserPinService.CheckPinStatus(userID)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
if status == "Pin not created" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Pin belum dibuat")
}
return utils.GenericResponse(c, fiber.StatusOK, "Pin sudah dibuat")
}
func (h *UserPinHandler) CreateUserPin(c *fiber.Ctx) error {
var requestUserPinDTO dto.RequestUserPinDTO
if err := c.BodyParser(&requestUserPinDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := requestUserPinDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
userID := c.Locals("userID").(string)
message, err := h.UserPinService.CreateUserPin(userID, requestUserPinDTO.Pin)
if err != nil {
return utils.GenericResponse(c, fiber.StatusConflict, err.Error())
}
return utils.GenericResponse(c, fiber.StatusCreated, message)
}
func (h *UserPinHandler) UpdateUserPin(c *fiber.Ctx) error {
var requestUserPinDTO dto.UpdateUserPinDTO
if err := c.BodyParser(&requestUserPinDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := requestUserPinDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
userID := c.Locals("userID").(string)
message, err := h.UserPinService.UpdateUserPin(userID, requestUserPinDTO.OldPin, requestUserPinDTO.NewPin)
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, message)
}

View File

@ -0,0 +1,199 @@
package handler
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type WilayahIndonesiaHandler struct {
WilayahService services.WilayahIndonesiaService
}
func NewWilayahImportHandler(wilayahService services.WilayahIndonesiaService) *WilayahIndonesiaHandler {
return &WilayahIndonesiaHandler{WilayahService: wilayahService}
}
func (h *WilayahIndonesiaHandler) ImportWilayahData(c *fiber.Ctx) error {
err := h.WilayahService.ImportDataFromCSV()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, fiber.StatusCreated, "Data imported successfully")
}
func (h *WilayahIndonesiaHandler) GetProvinces(c *fiber.Ctx) error {
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
provinces, totalProvinces, err := h.WilayahService.GetAllProvinces(page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch provinces")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, provinces, page, limit, totalProvinces, "Provinces fetched successfully")
}
return utils.NonPaginatedResponse(c, provinces, totalProvinces, "Provinces fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetProvinceByID(c *fiber.Ctx) error {
provinceID := c.Params("provinceid")
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
province, totalRegencies, err := h.WilayahService.GetProvinceByID(provinceID, page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch province")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, province, page, limit, totalRegencies, "Province fetched successfully")
}
return utils.NonPaginatedResponse(c, province, totalRegencies, "Province fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetAllRegencies(c *fiber.Ctx) error {
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
regencies, totalRegencies, err := h.WilayahService.GetAllRegencies(page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch regency")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, regencies, page, limit, totalRegencies, "regency fetched successfully")
}
return utils.NonPaginatedResponse(c, regencies, totalRegencies, "Provinces fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetRegencyByID(c *fiber.Ctx) error {
regencyId := c.Params("regencyid")
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
regency, totalDistrict, err := h.WilayahService.GetRegencyByID(regencyId, page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch regency")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, regency, page, limit, totalDistrict, "regency fetched successfully")
}
return utils.NonPaginatedResponse(c, regency, totalDistrict, "regency fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetAllDistricts(c *fiber.Ctx) error {
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
districts, totalDistricts, err := h.WilayahService.GetAllDistricts(page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch districts")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, districts, page, limit, totalDistricts, "districts fetched successfully")
}
return utils.NonPaginatedResponse(c, districts, totalDistricts, "districts fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetDistrictByID(c *fiber.Ctx) error {
districtId := c.Params("districtid")
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
district, totalVillages, err := h.WilayahService.GetDistrictByID(districtId, page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch district")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, district, page, limit, totalVillages, "district fetched successfully")
}
return utils.NonPaginatedResponse(c, district, totalVillages, "district fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetAllVillages(c *fiber.Ctx) error {
page, err := strconv.Atoi(c.Query("page", "0"))
if err != nil {
page = 0
}
limit, err := strconv.Atoi(c.Query("limit", "0"))
if err != nil {
limit = 0
}
villages, totalVillages, err := h.WilayahService.GetAllVillages(page, limit)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch villages")
}
if page > 0 && limit > 0 {
return utils.PaginatedResponse(c, villages, page, limit, totalVillages, "villages fetched successfully")
}
return utils.NonPaginatedResponse(c, villages, totalVillages, "villages fetched successfully")
}
func (h *WilayahIndonesiaHandler) GetVillageByID(c *fiber.Ctx) error {
id := c.Params("villageid")
village, err := h.WilayahService.GetVillageByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, village, "Village fetched successfully")
}

View File

@ -1,54 +0,0 @@
package middleware
import (
"context"
"fmt"
"os"
"time"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/utils"
)
func APIKeyMiddleware(c *fiber.Ctx) error {
apiKey := c.Get("x-api-key")
expectedAPIKey := os.Getenv("API_KEY")
if apiKey != expectedAPIKey {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid API Key",
nil,
))
}
return c.Next()
}
func RateLimitMiddleware(c *fiber.Ctx) error {
apiKey := c.Get("x-api-key")
if apiKey == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"API Key is missing",
nil,
))
}
ctx := context.Background()
rateLimitKey := fmt.Sprintf("rate_limit:%s", apiKey)
count, _ := config.RedisClient.Incr(ctx, rateLimitKey).Result()
if count > 100 {
return c.Status(fiber.StatusTooManyRequests).JSON(utils.FormatResponse(
fiber.StatusTooManyRequests,
"Rate limit exceeded",
nil,
))
}
config.RedisClient.Expire(ctx, rateLimitKey, time.Minute)
return c.Next()
}

View File

@ -1,148 +0,0 @@
package middleware
import (
"context"
"errors"
"os"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/utils"
)
func containsRole(roles []string, role string) bool {
for _, r := range roles {
if r == role {
return true
}
}
return false
}
func RoleRequired(roles ...string) fiber.Handler {
return func(c *fiber.Ctx) error {
tokenString := strings.TrimPrefix(c.Get("Authorization"), "Bearer ")
if tokenString == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Token is required",
nil,
))
}
ctx := context.Background()
cachedToken, err := config.RedisClient.Get(ctx, "auth_token:"+tokenString).Result()
if err != nil || cachedToken == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid or expired token",
nil,
))
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(os.Getenv("API_KEY")), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid or expired token",
nil,
))
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid token claims",
nil,
))
}
userID, ok := claims["sub"].(string)
if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Missing or invalid user ID in token",
nil,
))
}
role, ok := claims["role"].(string)
if !ok || role == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Missing or invalid role in token",
nil,
))
}
c.Locals("userID", userID)
c.Locals("role", role)
if !containsRole(roles, role) {
return c.Status(fiber.StatusForbidden).JSON(utils.FormatResponse(
fiber.StatusForbidden,
"You do not have permission to access this resource",
nil,
))
}
return c.Next()
}
}
func AuthMiddleware(c *fiber.Ctx) error {
tokenString := strings.TrimPrefix(c.Get("Authorization"), "Bearer ")
if tokenString == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Missing or invalid token",
nil,
))
}
ctx := context.Background()
cachedToken, err := config.RedisClient.Get(ctx, "auth_token:"+tokenString).Result()
if err != nil || cachedToken == "" {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid or expired token",
nil,
))
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("API_KEY")), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid or expired token",
nil,
))
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"Invalid token claims",
nil,
))
}
userID := claims["sub"].(string)
c.Locals("userID", userID)
config.RedisClient.Expire(ctx, "auth_token:"+tokenString, time.Hour*24)
return c.Next()
}

View File

@ -1,205 +0,0 @@
package repositories
import (
"errors"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/utils"
)
func GetProvinces() ([]domain.Province, error) {
records, err := utils.ReadCSV("public/document/provinces.csv")
if err != nil {
return nil, err
}
var provinces []domain.Province
for _, record := range records {
province := domain.Province{
ID: record[0],
Name: record[1],
}
provinces = append(provinces, province)
}
return provinces, nil
}
func GetRegencies() ([]domain.Regency, error) {
records, err := utils.ReadCSV("public/document/regencies.csv")
if err != nil {
return nil, err
}
var regencies []domain.Regency
for _, record := range records {
regency := domain.Regency{
ID: record[0],
ProvinceID: record[1],
Name: record[2],
}
regencies = append(regencies, regency)
}
return regencies, nil
}
func GetDistricts() ([]domain.District, error) {
records, err := utils.ReadCSV("public/document/districts.csv")
if err != nil {
return nil, err
}
var districts []domain.District
for _, record := range records {
district := domain.District{
ID: record[0],
RegencyID: record[1],
Name: record[2],
}
districts = append(districts, district)
}
return districts, nil
}
func GetVillages() ([]domain.Village, error) {
records, err := utils.ReadCSV("public/document/villages.csv")
if err != nil {
return nil, err
}
var villages []domain.Village
for _, record := range records {
village := domain.Village{
ID: record[0],
DistrictID: record[1],
Name: record[2],
}
villages = append(villages, village)
}
return villages, nil
}
func GetProvinceByID(id string) (domain.Province, error) {
provinces, err := GetProvinces()
if err != nil {
return domain.Province{}, err
}
for _, province := range provinces {
if province.ID == id {
regencies, err := GetRegenciesByProvinceID(id)
if err != nil {
return domain.Province{}, err
}
province.ListRegency = regencies
return province, nil
}
}
return domain.Province{}, errors.New("province not found")
}
func GetRegencyByID(id string) (domain.Regency, error) {
regencies, err := GetRegencies()
if err != nil {
return domain.Regency{}, err
}
for _, regency := range regencies {
if regency.ID == id {
districts, err := GetDistrictsByRegencyID(id)
if err != nil {
return domain.Regency{}, err
}
regency.ListDistrict = districts
return regency, nil
}
}
return domain.Regency{}, errors.New("regency not found")
}
func GetDistrictByID(id string) (domain.District, error) {
districts, err := GetDistricts()
if err != nil {
return domain.District{}, err
}
for _, district := range districts {
if district.ID == id {
villages, err := GetVillagesByDistrictID(id)
if err != nil {
return domain.District{}, err
}
district.ListVillage = villages
return district, nil
}
}
return domain.District{}, errors.New("district not found")
}
func GetVillageByID(id string) (domain.Village, error) {
villages, err := GetVillages()
if err != nil {
return domain.Village{}, err
}
for _, village := range villages {
if village.ID == id {
return village, nil
}
}
return domain.Village{}, errors.New("village not found")
}
func GetRegenciesByProvinceID(provinceID string) ([]domain.Regency, error) {
regencies, err := GetRegencies()
if err != nil {
return nil, err
}
var result []domain.Regency
for _, regency := range regencies {
if regency.ProvinceID == provinceID {
result = append(result, regency)
}
}
return result, nil
}
func GetDistrictsByRegencyID(regencyID string) ([]domain.District, error) {
districts, err := GetDistricts()
if err != nil {
return nil, err
}
var result []domain.District
for _, district := range districts {
if district.RegencyID == regencyID {
result = append(result, district)
}
}
return result, nil
}
func GetVillagesByDistrictID(districtID string) ([]domain.Village, error) {
villages, err := GetVillages()
if err != nil {
return nil, err
}
var result []domain.Village
for _, village := range villages {
if village.DistrictID == districtID {
result = append(result, village)
}
}
return result, nil
}

View File

@ -1,83 +0,0 @@
package repositories
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func CreateAddress(address *domain.Address) error {
result := config.DB.Create(address)
if result.Error != nil {
return result.Error
}
cacheKey := fmt.Sprintf("address:user:%s", address.UserID)
config.RedisClient.Del(context.Background(), cacheKey)
return nil
}
func GetAddressesByUserID(userID string) ([]domain.Address, error) {
ctx := context.Background()
cacheKey := fmt.Sprintf("address:user:%s", userID)
cachedAddresses, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil {
var addresses []domain.Address
if json.Unmarshal([]byte(cachedAddresses), &addresses) == nil {
return addresses, nil
}
}
var addresses []domain.Address
err = config.DB.Where("user_id = ?", userID).Find(&addresses).Error
if err != nil {
return nil, err
}
addressesJSON, _ := json.Marshal(addresses)
config.RedisClient.Set(ctx, cacheKey, addressesJSON, time.Hour).Err()
return addresses, nil
}
func GetAddressByID(addressID string) (domain.Address, error) {
var address domain.Address
if err := config.DB.Where("id = ?", addressID).First(&address).Error; err != nil {
return address, errors.New("address not found")
}
return address, nil
}
func UpdateAddress(address domain.Address) (domain.Address, error) {
if err := config.DB.Save(&address).Error; err != nil {
return address, err
}
cacheKey := fmt.Sprintf("address:user:%s", address.UserID)
config.RedisClient.Del(context.Background(), cacheKey)
return address, nil
}
func DeleteAddress(addressID string) error {
var address domain.Address
if err := config.DB.Where("id = ?", addressID).First(&address).Error; err != nil {
return err
}
if err := config.DB.Delete(&address).Error; err != nil {
return err
}
cacheKey := fmt.Sprintf("address:user:%s", address.UserID)
config.RedisClient.Del(context.Background(), cacheKey)
return nil
}

View File

@ -0,0 +1,61 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type AddressRepository interface {
CreateAddress(address *model.Address) error
FindAddressByUserID(userID string) ([]model.Address, error)
FindAddressByID(id string) (*model.Address, error)
UpdateAddress(address *model.Address) error
DeleteAddress(id string) error
}
type addressRepository struct {
DB *gorm.DB
}
func NewAddressRepository(db *gorm.DB) AddressRepository {
return &addressRepository{DB: db}
}
func (r *addressRepository) CreateAddress(address *model.Address) error {
return r.DB.Create(address).Error
}
func (r *addressRepository) FindAddressByUserID(userID string) ([]model.Address, error) {
var addresses []model.Address
err := r.DB.Where("user_id = ?", userID).Find(&addresses).Error
if err != nil {
return nil, err
}
return addresses, nil
}
func (r *addressRepository) FindAddressByID(id string) (*model.Address, error) {
var address model.Address
err := r.DB.Where("id = ?", id).First(&address).Error
if err != nil {
return nil, err
}
return &address, nil
}
func (r *addressRepository) UpdateAddress(address *model.Address) error {
err := r.DB.Save(address).Error
if err != nil {
return err
}
return nil
}
func (r *addressRepository) DeleteAddress(id string) error {
err := r.DB.Where("id = ?", id).Delete(&model.Address{}).Error
if err != nil {
return err
}
return nil
}

View File

@ -1,44 +0,0 @@
package repositories
import (
"errors"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
type ArticleRepository struct{}
func NewArticleRepository() *ArticleRepository {
return &ArticleRepository{}
}
func (r *ArticleRepository) GetAll() ([]domain.Article, error) {
var articles []domain.Article
err := config.DB.Find(&articles).Error
if err != nil {
return nil, errors.New("failed to fetch articles from database")
}
return articles, nil
}
func (r *ArticleRepository) GetByID(id string) (*domain.Article, error) {
var article domain.Article
err := config.DB.First(&article, "id = ?", id).Error
if err != nil {
return nil, errors.New("article not found")
}
return &article, nil
}
func (r *ArticleRepository) Create(article *domain.Article) error {
return config.DB.Create(article).Error
}
func (r *ArticleRepository) Update(article *domain.Article) error {
return config.DB.Save(article).Error
}
func (r *ArticleRepository) Delete(article *domain.Article) error {
return config.DB.Delete(article).Error
}

View File

@ -0,0 +1,74 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type ArticleRepository interface {
CreateArticle(article *model.Article) error
FindArticleByID(id string) (*model.Article, error)
FindAllArticles(page, limit int) ([]model.Article, int, error)
UpdateArticle(id string, article *model.Article) error
DeleteArticle(id string) error
}
type articleRepository struct {
DB *gorm.DB
}
func NewArticleRepository(db *gorm.DB) ArticleRepository {
return &articleRepository{DB: db}
}
func (r *articleRepository) CreateArticle(article *model.Article) error {
return r.DB.Create(article).Error
}
func (r *articleRepository) FindArticleByID(id string) (*model.Article, error) {
var article model.Article
err := r.DB.Where("id = ?", id).First(&article).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("article with ID %s not found", id)
}
return nil, fmt.Errorf("failed to fetch article: %v", err)
}
return &article, nil
}
func (r *articleRepository) FindAllArticles(page, limit int) ([]model.Article, int, error) {
var articles []model.Article
var total int64
if err := r.DB.Model(&model.Article{}).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to count articles: %v", err)
}
fmt.Printf("Total Articles Count: %d\n", total)
if page > 0 && limit > 0 {
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&articles).Error
if err != nil {
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
}
} else {
err := r.DB.Find(&articles).Error
if err != nil {
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
}
}
return articles, int(total), nil
}
func (r *articleRepository) UpdateArticle(id string, article *model.Article) error {
return r.DB.Model(&model.Article{}).Where("id = ?", id).Updates(article).Error
}
func (r *articleRepository) DeleteArticle(id string) error {
result := r.DB.Delete(&model.Article{}, "id = ?", id)
return result.Error
}

View File

@ -1,176 +0,0 @@
package repositories
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func IsEmailExist(email, roleId string) bool {
ctx := context.Background()
cacheKey := fmt.Sprintf("email:%s", email)
cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedRole == roleId {
return true
}
var user domain.User
if err := config.DB.Where("email = ?", email).First(&user).Error; err == nil {
if user.RoleID == roleId {
if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil {
log.Printf("Redis Set error: %v", err)
}
return true
}
}
return false
}
func IsUsernameExist(username, roleId string) bool {
ctx := context.Background()
cacheKey := fmt.Sprintf("username:%s", username)
cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedRole == roleId {
return true
}
var user domain.User
if err := config.DB.Where("username = ?", username).First(&user).Error; err == nil {
if user.RoleID == roleId {
if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil {
log.Printf("Redis Set error: %v", err)
}
return true
}
}
return false
}
func IsPhoneExist(phone, roleId string) bool {
ctx := context.Background()
cacheKey := fmt.Sprintf("phone:%s", phone)
cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedRole == roleId {
return true
}
var user domain.User
if err := config.DB.Where("phone = ?", phone).First(&user).Error; err == nil {
if user.RoleID == roleId {
if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil {
log.Printf("Redis Set error: %v", err)
}
return true
}
}
return false
}
func CreateUser(username, name, email, phone, password, roleId string) error {
user := domain.User{
Username: username,
Name: name,
Email: email,
Phone: phone,
Password: password,
RoleID: roleId,
}
result := config.DB.Create(&user)
if result.Error != nil {
return errors.New("failed to create user")
}
return nil
}
func GetUserByEmailUsernameOrPhone(identifier, roleId string) (domain.User, error) {
ctx := context.Background()
cacheKey := fmt.Sprintf("user:%s", identifier)
var user domain.User
cachedUser, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil {
if err := json.Unmarshal([]byte(cachedUser), &user); err == nil {
if roleId == "" || user.RoleID == roleId {
return user, nil
}
}
}
err = config.DB.Where("email = ? OR username = ? OR phone = ?", identifier, identifier, identifier).First(&user).Error
if err != nil {
return user, errors.New("user not found")
}
if roleId != "" && user.RoleID != roleId {
return user, errors.New("identifier found but role does not match")
}
userJSON, _ := json.Marshal(user)
if err := config.RedisClient.Set(ctx, cacheKey, userJSON, 1*time.Hour).Err(); err != nil {
log.Printf("Redis Set error: %v", err)
}
return user, nil
}
func GetUserByID(userID string) (domain.User, error) {
ctx := context.Background()
cacheKey := fmt.Sprintf("user:%s", userID)
var user domain.User
cachedUser, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil {
if err := json.Unmarshal([]byte(cachedUser), &user); err == nil {
return user, nil
}
}
if err := config.DB.Preload("Role").Where("id = ?", userID).First(&user).Error; err != nil {
return user, errors.New("user not found")
}
userJSON, _ := json.Marshal(user)
if err := config.RedisClient.Set(ctx, cacheKey, userJSON, 1*time.Hour).Err(); err != nil {
log.Printf("Redis Set error: %v", err)
}
return user, nil
}
func UpdateUser(user *domain.User) error {
if err := config.DB.Save(user).Error; err != nil {
return errors.New("failed to update user")
}
cacheKey := fmt.Sprintf("user:%s", user.ID)
if err := config.RedisClient.Del(context.Background(), cacheKey).Err(); err != nil {
log.Printf("Redis Del error: %v", err)
}
return nil
}
func UpdateUserPassword(userID, newPassword string) error {
var user domain.User
if err := config.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return errors.New("user not found")
}
user.Password = newPassword
if err := config.DB.Save(&user).Error; err != nil {
return errors.New("failed to update password")
}
cacheKey := fmt.Sprintf("user:%s", userID)
if err := config.RedisClient.Del(context.Background(), cacheKey).Err(); err != nil {
log.Printf("Redis Del error: %v", err)
}
return nil
}

View File

@ -0,0 +1,82 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type UserRepository interface {
FindByIdentifierAndRole(identifier, roleID string) (*model.User, error)
FindByEmailOrUsernameOrPhone(identifier string) (*model.User, error)
FindByUsername(username string) (*model.User, error)
FindByPhoneAndRole(phone, roleID string) (*model.User, error)
FindByEmailAndRole(email, roleID string) (*model.User, error)
Create(user *model.User) error
}
type userRepository struct {
DB *gorm.DB
}
func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{DB: db}
}
func (r *userRepository) FindByIdentifierAndRole(identifier, roleID string) (*model.User, error) {
var user model.User
err := r.DB.Preload("Role").Where("(email = ? OR username = ? OR phone = ?) AND role_id = ?", identifier, identifier, identifier, roleID).First(&user).Error
if err != nil {
return nil, err
}
if user.Role == nil {
return nil, fmt.Errorf("role not found for this user")
}
return &user, nil
}
func (r *userRepository) FindByUsername(username string) (*model.User, error) {
var user model.User
err := r.DB.Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *userRepository) FindByPhoneAndRole(phone, roleID string) (*model.User, error) {
var user model.User
err := r.DB.Where("phone = ? AND role_id = ?", phone, roleID).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *userRepository) FindByEmailAndRole(email, roleID string) (*model.User, error) {
var user model.User
err := r.DB.Where("email = ? AND role_id = ?", email, roleID).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *userRepository) FindByEmailOrUsernameOrPhone(identifier string) (*model.User, error) {
var user model.User
err := r.DB.Where("email = ? OR username = ? OR phone = ?", identifier, identifier, identifier).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *userRepository) Create(user *model.User) error {
err := r.DB.Create(user).Error
if err != nil {
return err
}
return nil
}

View File

@ -1,44 +0,0 @@
package repositories
import (
"errors"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
type BannerRepository struct{}
func NewBannerRepository() *BannerRepository {
return &BannerRepository{}
}
func (r *BannerRepository) GetAll() ([]domain.Banner, error) {
var banners []domain.Banner
err := config.DB.Find(&banners).Error
if err != nil {
return nil, errors.New("failed to fetch banners from database")
}
return banners, nil
}
func (r *BannerRepository) GetByID(id string) (*domain.Banner, error) {
var banner domain.Banner
err := config.DB.First(&banner, "id = ?", id).Error
if err != nil {
return nil, errors.New("banner not found")
}
return &banner, nil
}
func (r *BannerRepository) Create(banner *domain.Banner) error {
return config.DB.Create(banner).Error
}
func (r *BannerRepository) Update(banner *domain.Banner) error {
return config.DB.Save(banner).Error
}
func (r *BannerRepository) Delete(banner *domain.Banner) error {
return config.DB.Delete(banner).Error
}

View File

@ -0,0 +1,69 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type BannerRepository interface {
CreateBanner(banner *model.Banner) error
FindBannerByID(id string) (*model.Banner, error)
FindAllBanners() ([]model.Banner, error)
UpdateBanner(id string, banner *model.Banner) error
DeleteBanner(id string) error
}
type bannerRepository struct {
DB *gorm.DB
}
func NewBannerRepository(db *gorm.DB) BannerRepository {
return &bannerRepository{DB: db}
}
func (r *bannerRepository) CreateBanner(banner *model.Banner) error {
if err := r.DB.Create(banner).Error; err != nil {
return fmt.Errorf("failed to create banner: %v", err)
}
return nil
}
func (r *bannerRepository) FindBannerByID(id string) (*model.Banner, error) {
var banner model.Banner
err := r.DB.Where("id = ?", id).First(&banner).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("banner with ID %s not found", id)
}
return nil, fmt.Errorf("failed to fetch banner by ID: %v", err)
}
return &banner, nil
}
func (r *bannerRepository) FindAllBanners() ([]model.Banner, error) {
var banners []model.Banner
err := r.DB.Find(&banners).Error
if err != nil {
return nil, fmt.Errorf("failed to fetch banners: %v", err)
}
return banners, nil
}
func (r *bannerRepository) UpdateBanner(id string, banner *model.Banner) error {
err := r.DB.Model(&model.Banner{}).Where("id = ?", id).Updates(banner).Error
if err != nil {
return fmt.Errorf("failed to update banner: %v", err)
}
return nil
}
func (r *bannerRepository) DeleteBanner(id string) error {
result := r.DB.Delete(&model.Banner{}, "id = ?", id)
if result.Error != nil {
return fmt.Errorf("failed to delete banner: %v", result.Error)
}
return nil
}

View File

@ -1,148 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func GetCoverageAreas() ([]domain.CoverageArea, error) {
var coverageAreas []domain.CoverageArea
if err := config.DB.Find(&coverageAreas).Error; err != nil {
return nil, err
}
return coverageAreas, nil
}
func GetCoverageAreaByID(id string) (domain.CoverageArea, error) {
var coverageArea domain.CoverageArea
if err := config.DB.Where("id = ?", id).First(&coverageArea).Error; err != nil {
return coverageArea, err
}
return coverageArea, nil
}
func GetCoverageAreaByDistrictID(id string) (domain.CoverageDistric, error) {
var coverageDistric domain.CoverageDistric
if err := config.DB.Where("id = ?", id).First(&coverageDistric).Error; err != nil {
return coverageDistric, err
}
return coverageDistric, nil
}
func GetCoverageDistricsByCoverageAreaID(areaID string) ([]domain.CoverageDistric, error) {
var districts []domain.CoverageDistric
if err := config.DB.Where("coverage_area_id = ?", areaID).Find(&districts).Error; err != nil {
return nil, err
}
return districts, nil
}
func GetSubdistrictsByCoverageDistrictID(districtID string) ([]domain.CoverageSubdistrict, error) {
var subdistricts []domain.CoverageSubdistrict
if err := config.DB.Where("coverage_district_id = ?", districtID).Find(&subdistricts).Error; err != nil {
return nil, err
}
return subdistricts, nil
}
func CreateCoverageArea(coverageArea *domain.CoverageArea) error {
if err := config.DB.Create(&coverageArea).Error; err != nil {
return err
}
return nil
}
func CreateCoverageDistrict(coverageDistrict *domain.CoverageDistric) error {
if err := config.DB.Create(&coverageDistrict).Error; err != nil {
return err
}
return nil
}
func CreateCoverageSubdistrict(coverageSubdistrict *domain.CoverageSubdistrict) error {
if err := config.DB.Create(&coverageSubdistrict).Error; err != nil {
return err
}
return nil
}
func UpdateCoverageArea(id string, coverageArea domain.CoverageArea) (domain.CoverageArea, error) {
var existingCoverageArea domain.CoverageArea
if err := config.DB.Where("id = ?", id).First(&existingCoverageArea).Error; err != nil {
return existingCoverageArea, err
}
existingCoverageArea.Province = coverageArea.Province
if err := config.DB.Save(&existingCoverageArea).Error; err != nil {
return existingCoverageArea, err
}
return existingCoverageArea, nil
}
func UpdateCoverageDistrict(id string, coverageDistrict domain.CoverageDistric) (domain.CoverageDistric, error) {
var existingCoverageDistrict domain.CoverageDistric
if err := config.DB.Where("id = ?", id).First(&existingCoverageDistrict).Error; err != nil {
return existingCoverageDistrict, err
}
existingCoverageDistrict.District = coverageDistrict.District
if err := config.DB.Save(&existingCoverageDistrict).Error; err != nil {
return existingCoverageDistrict, err
}
return existingCoverageDistrict, nil
}
func UpdateCoverageSubdistrict(id string, coverageSubdistrict domain.CoverageSubdistrict) (domain.CoverageSubdistrict, error) {
var existingCoverageSubdistrict domain.CoverageSubdistrict
if err := config.DB.Where("id = ?", id).First(&existingCoverageSubdistrict).Error; err != nil {
return existingCoverageSubdistrict, err
}
existingCoverageSubdistrict.Subdistrict = coverageSubdistrict.Subdistrict
if err := config.DB.Save(&existingCoverageSubdistrict).Error; err != nil {
return existingCoverageSubdistrict, err
}
return existingCoverageSubdistrict, nil
}
func DeleteCoverageArea(id string) error {
var coverageArea domain.CoverageArea
if err := config.DB.Where("id = ?", id).First(&coverageArea).Error; err != nil {
return err
}
if err := config.DB.Delete(&coverageArea).Error; err != nil {
return err
}
return nil
}
func DeleteCoverageDistrict(id string) error {
var coverageDistrict domain.CoverageDistric
if err := config.DB.Where("id = ?", id).First(&coverageDistrict).Error; err != nil {
return err
}
if err := config.DB.Delete(&coverageDistrict).Error; err != nil {
return err
}
return nil
}
func DeleteCoverageSubdistrict(id string) error {
var coverageSubdistrict domain.CoverageSubdistrict
if err := config.DB.Where("id = ?", id).First(&coverageSubdistrict).Error; err != nil {
return err
}
if err := config.DB.Delete(&coverageSubdistrict).Error; err != nil {
return err
}
return nil
}

View File

@ -1,44 +0,0 @@
package repositories
import (
"errors"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
type PointRepository struct{}
func NewPointRepository() *PointRepository {
return &PointRepository{}
}
func (r *PointRepository) GetAll() ([]domain.Point, error) {
var points []domain.Point
err := config.DB.Find(&points).Error
if err != nil {
return nil, errors.New("failed to fetch points from database")
}
return points, nil
}
func (r *PointRepository) GetByID(id string) (*domain.Point, error) {
var point domain.Point
err := config.DB.First(&point, "id = ?", id).Error
if err != nil {
return nil, errors.New("point not found")
}
return &point, nil
}
func (r *PointRepository) Create(point *domain.Point) error {
return config.DB.Create(point).Error
}
func (r *PointRepository) Update(point *domain.Point) error {
return config.DB.Save(point).Error
}
func (r *PointRepository) Delete(point *domain.Point) error {
return config.DB.Delete(point).Error
}

View File

@ -0,0 +1,67 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type InitialCointRepository interface {
CreateInitialCoint(coint *model.InitialCoint) error
FindInitialCointByID(id string) (*model.InitialCoint, error)
FindAllInitialCoints() ([]model.InitialCoint, error)
UpdateInitialCoint(id string, coint *model.InitialCoint) error
DeleteInitialCoint(id string) error
}
type initialCointRepository struct {
DB *gorm.DB
}
func NewInitialCointRepository(db *gorm.DB) InitialCointRepository {
return &initialCointRepository{DB: db}
}
func (r *initialCointRepository) CreateInitialCoint(coint *model.InitialCoint) error {
if err := r.DB.Create(coint).Error; err != nil {
return fmt.Errorf("failed to create initial coint: %v", err)
}
return nil
}
func (r *initialCointRepository) FindInitialCointByID(id string) (*model.InitialCoint, error) {
var coint model.InitialCoint
err := r.DB.Where("id = ?", id).First(&coint).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("initial coint with ID %s not found", id)
}
return nil, fmt.Errorf("failed to fetch initial coint by ID: %v", err)
}
return &coint, nil
}
func (r *initialCointRepository) FindAllInitialCoints() ([]model.InitialCoint, error) {
var coints []model.InitialCoint
err := r.DB.Find(&coints).Error
if err != nil {
return nil, fmt.Errorf("failed to fetch initial coints: %v", err)
}
return coints, nil
}
func (r *initialCointRepository) UpdateInitialCoint(id string, coint *model.InitialCoint) error {
err := r.DB.Model(&model.InitialCoint{}).Where("id = ?", id).Updates(coint).Error
if err != nil {
return fmt.Errorf("failed to update initial coint: %v", err)
}
return nil
}
func (r *initialCointRepository) DeleteInitialCoint(id string) error {
result := r.DB.Delete(&model.InitialCoint{}, "id = ?", id)
if result.Error != nil {
return fmt.Errorf("failed to delete initial coint: %v", result.Error)
}
return nil
}

View File

@ -1,108 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"gorm.io/gorm"
)
func GetProductsByStoreID(storeID string, limit, offset int) ([]domain.Product, error) {
var products []domain.Product
query := config.DB.Preload("ProductImages").Preload("TrashDetail").Where("store_id = ?", storeID)
if limit > 0 {
query = query.Limit(limit).Offset(offset)
}
err := query.Find(&products).Error
return products, err
}
func GetProductsByUserID(userID string, limit, offset int) ([]domain.Product, error) {
var products []domain.Product
query := config.DB.Preload("ProductImages").Preload("TrashDetail").Where("user_id = ?", userID)
if limit > 0 {
query = query.Limit(limit).Offset(offset)
}
err := query.Find(&products).Error
return products, err
}
func GetProductByIDAndStoreID(productID, storeID string) (domain.Product, error) {
var product domain.Product
err := config.DB.Preload("ProductImages").Preload("TrashDetail").
Where("id = ? AND store_id = ?", productID, storeID).
First(&product).Error
return product, err
}
func GetProductByID(productID string) (domain.Product, error) {
var product domain.Product
err := config.DB.Preload("ProductImages").Preload("TrashDetail").
Where("id = ?", productID).First(&product).Error
return product, err
}
func IsValidStoreID(storeID string) bool {
var count int64
err := config.DB.Model(&domain.Store{}).Where("id = ?", storeID).Count(&count).Error
if err != nil || count == 0 {
return false
}
return true
}
func CreateProduct(product *domain.Product, images []domain.ProductImage) error {
return config.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(product).Error; err != nil {
return err
}
if len(images) > 0 {
for i := range images {
images[i].ProductID = product.ID
}
if err := tx.Create(&images).Error; err != nil {
return err
}
}
return nil
})
}
func UpdateProduct(product *domain.Product, images []domain.ProductImage) error {
return config.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(product).Error; err != nil {
return err
}
if len(images) > 0 {
for i := range images {
images[i].ProductID = product.ID
}
if err := tx.Where("product_id = ?", product.ID).Delete(&domain.ProductImage{}).Error; err != nil {
return err
}
if err := tx.Create(&images).Error; err != nil {
return err
}
}
return nil
})
}
func DeleteProduct(productID string) error {
return config.DB.Where("id = ?", productID).Delete(&domain.Product{}).Error
}

View File

@ -1,64 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
type RequestPickupRepository interface {
Create(request *domain.RequestPickup) error
GetByID(id string) (*domain.RequestPickup, error)
GetByUserID(userID string) ([]domain.RequestPickup, error)
DeleteByID(id string) error
ExistsByID(id string) (bool, error)
}
type requestPickupRepository struct{}
func NewRequestPickupRepository() RequestPickupRepository {
return &requestPickupRepository{}
}
func (r *requestPickupRepository) Create(request *domain.RequestPickup) error {
return config.DB.Create(request).Error
}
func (r *requestPickupRepository) GetByID(id string) (*domain.RequestPickup, error) {
var requestPickup domain.RequestPickup
if err := config.DB.Preload("Request").
Preload("Request.TrashCategory").
Preload("UserAddress").
Where("id = ?", id).
First(&requestPickup).Error; err != nil {
return nil, err
}
return &requestPickup, nil
}
func (r *requestPickupRepository) GetByUserID(userID string) ([]domain.RequestPickup, error) {
var requestPickups []domain.RequestPickup
err := config.DB.Preload("Request").
Preload("Request.TrashCategory").
Preload("UserAddress").
Where("user_id = ?", userID).
Find(&requestPickups).Error
if err != nil {
return nil, err
}
return requestPickups, nil
}
func (r *requestPickupRepository) ExistsByID(id string) (bool, error) {
var count int64
if err := config.DB.Model(&domain.RequestPickup{}).Where("id = ?", id).Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
func (r *requestPickupRepository) DeleteByID(id string) error {
return config.DB.Where("id = ?", id).Delete(&domain.RequestPickup{}).Error
}

View File

@ -1,76 +0,0 @@
package repositories
import (
"encoding/json"
"errors"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func GetUserRoleByID(id string) (domain.UserRole, error) {
var role domain.UserRole
ctx := config.Context()
cachedRole, err := config.RedisClient.Get(ctx, "userRole:"+id).Result()
if err == nil {
err := json.Unmarshal([]byte(cachedRole), &role)
if err != nil {
return role, errors.New("gagal mendekode data cache Redis")
}
return role, nil
}
err = config.DB.Where("id = ?", id).First(&role).Error
if err != nil {
return role, errors.New("userRole tidak ditemukan")
}
roleJSON, err := json.Marshal(role)
if err != nil {
return role, errors.New("gagal mendekode data untuk Redis")
}
err = config.RedisClient.Set(ctx, "userRole:"+id, roleJSON, time.Hour*24).Err()
if err != nil {
return role, errors.New("gagal menyimpan data di Redis")
}
return role, nil
}
func GetAllUserRoles() ([]domain.UserRole, error) {
var roles []domain.UserRole
ctx := config.Context()
cachedRoles, err := config.RedisClient.Get(ctx, "allUserRoles").Result()
if err == nil {
err := json.Unmarshal([]byte(cachedRoles), &roles)
if err != nil {
return roles, errors.New("gagal mendekode data cache Redis")
}
return roles, nil
}
err = config.DB.Find(&roles).Error
if err != nil {
return nil, errors.New("gagal mengambil data UserRole")
}
rolesJSON, err := json.Marshal(roles)
if err != nil {
return roles, errors.New("gagal mendekode data untuk Redis")
}
err = config.RedisClient.Set(ctx, "allUserRoles", rolesJSON, time.Hour*24).Err()
if err != nil {
return roles, errors.New("gagal menyimpan data di Redis")
}
return roles, nil
}

View File

@ -0,0 +1,37 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type RoleRepository interface {
FindByID(id string) (*model.Role, error)
FindAll() ([]model.Role, error)
}
type roleRepository struct {
DB *gorm.DB
}
func NewRoleRepository(db *gorm.DB) RoleRepository {
return &roleRepository{DB: db}
}
func (r *roleRepository) FindByID(id string) (*model.Role, error) {
var role model.Role
err := r.DB.Where("id = ?", id).First(&role).Error
if err != nil {
return nil, err
}
return &role, nil
}
func (r *roleRepository) FindAll() ([]model.Role, error) {
var roles []model.Role
err := r.DB.Find(&roles).Error
if err != nil {
return nil, err
}
return roles, nil
}

View File

@ -1,24 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func GetStoreByID(storeID string) (domain.Store, error) {
var store domain.Store
err := config.DB.Where("id = ?", storeID).First(&store).Error
return store, err
}
func GetStoresByUserID(userID string, limit, offset int) ([]domain.Store, error) {
var stores []domain.Store
query := config.DB.Where("user_id = ?", userID)
if limit > 0 {
query = query.Limit(limit).Offset(offset)
}
err := query.Find(&stores).Error
return stores, err
}

View File

@ -0,0 +1,105 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type TrashRepository interface {
CreateCategory(category *model.TrashCategory) error
AddDetailToCategory(detail *model.TrashDetail) error
GetCategories() ([]model.TrashCategory, error)
GetCategoryByID(id string) (*model.TrashCategory, error)
GetTrashDetailByID(id string) (*model.TrashDetail, error)
GetDetailsByCategoryID(categoryID string) ([]model.TrashDetail, error)
UpdateCategoryName(id string, newName string) error
UpdateTrashDetail(id string, description string, price float64) error
DeleteCategory(id string) error
DeleteTrashDetail(id string) error
}
type trashRepository struct {
DB *gorm.DB
}
func NewTrashRepository(db *gorm.DB) TrashRepository {
return &trashRepository{DB: db}
}
func (r *trashRepository) CreateCategory(category *model.TrashCategory) error {
if err := r.DB.Create(category).Error; err != nil {
return fmt.Errorf("failed to create category: %v", err)
}
return nil
}
func (r *trashRepository) AddDetailToCategory(detail *model.TrashDetail) error {
if err := r.DB.Create(detail).Error; err != nil {
return fmt.Errorf("failed to add detail to category: %v", err)
}
return nil
}
func (r *trashRepository) GetCategories() ([]model.TrashCategory, error) {
var categories []model.TrashCategory
if err := r.DB.Preload("Details").Find(&categories).Error; err != nil {
return nil, fmt.Errorf("failed to fetch categories: %v", err)
}
return categories, nil
}
func (r *trashRepository) GetCategoryByID(id string) (*model.TrashCategory, error) {
var category model.TrashCategory
if err := r.DB.Preload("Details").First(&category, "id = ?", id).Error; err != nil {
return nil, fmt.Errorf("category not found: %v", err)
}
return &category, nil
}
func (r *trashRepository) GetTrashDetailByID(id string) (*model.TrashDetail, error) {
var detail model.TrashDetail
if err := r.DB.First(&detail, "id = ?", id).Error; err != nil {
return nil, fmt.Errorf("trash detail not found: %v", err)
}
return &detail, nil
}
func (r *trashRepository) GetDetailsByCategoryID(categoryID string) ([]model.TrashDetail, error) {
var details []model.TrashDetail
if err := r.DB.Where("category_id = ?", categoryID).Find(&details).Error; err != nil {
return nil, fmt.Errorf("failed to fetch details for category %s: %v", categoryID, err)
}
return details, nil
}
func (r *trashRepository) UpdateCategoryName(id string, newName string) error {
if err := r.DB.Model(&model.TrashCategory{}).Where("id = ?", id).Update("name", newName).Error; err != nil {
return fmt.Errorf("failed to update category name: %v", err)
}
return nil
}
func (r *trashRepository) UpdateTrashDetail(id string, description string, price float64) error {
if err := r.DB.Model(&model.TrashDetail{}).Where("id = ?", id).Updates(model.TrashDetail{Description: description, Price: price}).Error; err != nil {
return fmt.Errorf("failed to update trash detail: %v", err)
}
return nil
}
func (r *trashRepository) DeleteCategory(id string) error {
if err := r.DB.Delete(&model.TrashCategory{}, "id = ?", id).Error; err != nil {
return fmt.Errorf("failed to delete category: %v", err)
}
return nil
}
func (r *trashRepository) DeleteTrashDetail(id string) error {
if err := r.DB.Delete(&model.TrashDetail{}, "id = ?", id).Error; err != nil {
return fmt.Errorf("failed to delete trash detail: %v", err)
}
return nil
}

View File

@ -1,81 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
)
func GetTrashCategories() ([]domain.TrashCategory, error) {
var categories []domain.TrashCategory
if err := config.DB.Find(&categories).Error; err != nil {
return nil, err
}
return categories, nil
}
func GetTrashCategoryDetail(id string) (domain.TrashCategory, error) {
var category domain.TrashCategory
if err := config.DB.Preload("Details").Where("id = ?", id).First(&category).Error; err != nil {
return category, err
}
return category, nil
}
func GetTrashDetailByID(id string) (domain.TrashDetail, error) {
var detail domain.TrashDetail
if err := config.DB.Where("id = ?", id).First(&detail).Error; err != nil {
return detail, err
}
return detail, nil
}
func CreateTrashCategory(category *domain.TrashCategory) error {
if err := config.DB.Create(category).Error; err != nil {
return err
}
return nil
}
func CreateTrashDetail(detail *domain.TrashDetail) error {
if err := config.DB.Create(detail).Error; err != nil {
return err
}
return nil
}
func UpdateTrashCategory(category *domain.TrashCategory) error {
if err := config.DB.Save(category).Error; err != nil {
return err
}
return nil
}
func UpdateTrashDetail(detail *domain.TrashDetail) error {
if err := config.DB.Save(detail).Error; err != nil {
return err
}
return nil
}
func DeleteTrashCategory(id string) error {
if err := config.DB.Where("category_id = ?", id).Delete(&domain.TrashDetail{}).Error; err != nil {
return err
}
if err := config.DB.Where("id = ?", id).Delete(&domain.TrashCategory{}).Error; err != nil {
return err
}
return nil
}
func DeleteTrashDetail(id string) error {
if err := config.DB.Where("id = ?", id).Delete(&domain.TrashDetail{}).Error; err != nil {
return err
}
return nil
}

View File

@ -1,33 +0,0 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/config"
)
func GetUsers() ([]domain.User, error) {
var users []domain.User
if err := config.DB.Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
func GetUsersByRole(roleID string) ([]domain.User, error) {
var users []domain.User
if err := config.DB.Where("role_id = ?", roleID).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
func GetUserByUserrId(userID string) (domain.User, error) {
var user domain.User
if err := config.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return domain.User{}, err
}
return user, nil
}

View File

@ -0,0 +1,56 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type UserProfileRepository interface {
FindByID(userID string) (*model.User, error)
Update(user *model.User) error
UpdateAvatar(userID, avatarURL string) error
}
type userProfileRepository struct {
DB *gorm.DB
}
func NewUserProfileRepository(db *gorm.DB) UserProfileRepository {
return &userProfileRepository{DB: db}
}
func (r *userProfileRepository) FindByID(userID string) (*model.User, error) {
var user model.User
err := r.DB.Preload("Role").Where("id = ?", userID).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("user with ID %s not found", userID)
}
return nil, err
}
if user.Role == nil {
return nil, fmt.Errorf("role not found for this user")
}
return &user, nil
}
func (r *userProfileRepository) Update(user *model.User) error {
err := r.DB.Save(user).Error
if err != nil {
return err
}
return nil
}
func (r *userProfileRepository) UpdateAvatar(userID, avatarURL string) error {
var user model.User
err := r.DB.Model(&user).Where("id = ?", userID).Update("avatar", avatarURL).Error
if err != nil {
return err
}
return nil
}

View File

@ -1,72 +0,0 @@
package repositories
import (
"context"
"errors"
"fmt"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"golang.org/x/crypto/bcrypt"
)
func CreatePin(pin *domain.UserPin) error {
result := config.DB.Create(pin)
if result.Error != nil {
return result.Error
}
return nil
}
func GetPinByUserID(userID string) (domain.UserPin, error) {
ctx := context.Background()
redisClient := config.RedisClient
redisKey := fmt.Sprintf("user_pin:%s", userID)
pin, err := redisClient.Get(ctx, redisKey).Result()
if err == nil {
return domain.UserPin{
UserID: userID,
Pin: pin,
}, nil
}
var dbPin domain.UserPin
err = config.DB.Where("user_id = ?", userID).First(&dbPin).Error
if err != nil {
return dbPin, errors.New("PIN tidak ditemukan")
}
redisClient.Set(ctx, redisKey, dbPin.Pin, 5*time.Minute)
return dbPin, nil
}
func UpdatePin(userID string, newPin string) (domain.UserPin, error) {
var pin domain.UserPin
err := config.DB.Where("user_id = ?", userID).First(&pin).Error
if err != nil {
return pin, errors.New("PIN tidak ditemukan")
}
hashedPin, err := bcrypt.GenerateFromPassword([]byte(newPin), bcrypt.DefaultCost)
if err != nil {
return pin, err
}
pin.Pin = string(hashedPin)
if err := config.DB.Save(&pin).Error; err != nil {
return pin, err
}
redisClient := config.RedisClient
redisClient.Del(context.Background(), fmt.Sprintf("user_pin:%s", userID))
return pin, nil
}

View File

@ -0,0 +1,59 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type UserPinRepository interface {
FindByUserID(userID string) (*model.UserPin, error)
FindByPin(userPin string) (*model.UserPin, error)
Create(userPin *model.UserPin) error
Update(userPin *model.UserPin) error
}
type userPinRepository struct {
DB *gorm.DB
}
func NewUserPinRepository(db *gorm.DB) UserPinRepository {
return &userPinRepository{DB: db}
}
func (r *userPinRepository) FindByUserID(userID string) (*model.UserPin, error) {
var userPin model.UserPin
err := r.DB.Where("user_id = ?", userID).First(&userPin).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &userPin, nil
}
func (r *userPinRepository) FindByPin(pin string) (*model.UserPin, error) {
var userPin model.UserPin
err := r.DB.Where("pin = ?", pin).First(&userPin).Error
if err != nil {
return nil, err
}
return &userPin, nil
}
func (r *userPinRepository) Create(userPin *model.UserPin) error {
err := r.DB.Create(userPin).Error
if err != nil {
return err
}
return nil
}
func (r *userPinRepository) Update(userPin *model.UserPin) error {
err := r.DB.Save(userPin).Error
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,243 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type WilayahIndonesiaRepository interface {
ImportProvinces(provinces []model.Province) error
ImportRegencies(regencies []model.Regency) error
ImportDistricts(districts []model.District) error
ImportVillages(villages []model.Village) error
FindAllProvinces(page, limit int) ([]model.Province, int, error)
FindProvinceByID(id string, page, limit int) (*model.Province, int, error)
FindAllRegencies(page, limit int) ([]model.Regency, int, error)
FindRegencyByID(id string, page, limit int) (*model.Regency, int, error)
FindAllDistricts(page, limit int) ([]model.District, int, error)
FindDistrictByID(id string, page, limit int) (*model.District, int, error)
FindAllVillages(page, limit int) ([]model.Village, int, error)
FindVillageByID(id string) (*model.Village, error)
}
type wilayahIndonesiaRepository struct {
DB *gorm.DB
}
func NewWilayahIndonesiaRepository(db *gorm.DB) WilayahIndonesiaRepository {
return &wilayahIndonesiaRepository{DB: db}
}
func (r *wilayahIndonesiaRepository) ImportProvinces(provinces []model.Province) error {
for _, province := range provinces {
if err := r.DB.Create(&province).Error; err != nil {
return err
}
}
return nil
}
func (r *wilayahIndonesiaRepository) ImportRegencies(regencies []model.Regency) error {
for _, regency := range regencies {
if err := r.DB.Create(&regency).Error; err != nil {
return err
}
}
return nil
}
func (r *wilayahIndonesiaRepository) ImportDistricts(districts []model.District) error {
for _, district := range districts {
if err := r.DB.Create(&district).Error; err != nil {
return err
}
}
return nil
}
func (r *wilayahIndonesiaRepository) ImportVillages(villages []model.Village) error {
for _, village := range villages {
if err := r.DB.Create(&village).Error; err != nil {
return err
}
}
return nil
}
func (r *wilayahIndonesiaRepository) FindAllProvinces(page, limit int) ([]model.Province, int, error) {
var provinces []model.Province
var total int64
err := r.DB.Model(&model.Province{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
if page > 0 && limit > 0 {
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&provinces).Error
if err != nil {
return nil, 0, err
}
} else {
err := r.DB.Find(&provinces).Error
if err != nil {
return nil, 0, err
}
}
return provinces, int(total), nil
}
func (r *wilayahIndonesiaRepository) FindProvinceByID(id string, page, limit int) (*model.Province, int, error) {
var province model.Province
err := r.DB.Preload("Regencies", func(db *gorm.DB) *gorm.DB {
if page > 0 && limit > 0 {
return db.Offset((page - 1) * limit).Limit(limit)
}
return db
}).Where("id = ?", id).First(&province).Error
if err != nil {
return nil, 0, err
}
var totalRegencies int64
r.DB.Model(&model.Regency{}).Where("province_id = ?", id).Count(&totalRegencies)
return &province, int(totalRegencies), nil
}
func (r *wilayahIndonesiaRepository) FindAllRegencies(page, limit int) ([]model.Regency, int, error) {
var regencies []model.Regency
var total int64
err := r.DB.Model(&model.Regency{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
if page > 0 && limit > 0 {
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&regencies).Error
if err != nil {
return nil, 0, err
}
} else {
err := r.DB.Find(&regencies).Error
if err != nil {
return nil, 0, err
}
}
return regencies, int(total), nil
}
func (r *wilayahIndonesiaRepository) FindRegencyByID(id string, page, limit int) (*model.Regency, int, error) {
var regency model.Regency
err := r.DB.Preload("Districts", func(db *gorm.DB) *gorm.DB {
if page > 0 && limit > 0 {
return db.Offset((page - 1) * limit).Limit(limit)
}
return db
}).Where("id = ?", id).First(&regency).Error
if err != nil {
return nil, 0, err
}
var totalDistricts int64
err = r.DB.Model(&model.District{}).Where("regency_id = ?", id).Count(&totalDistricts).Error
if err != nil {
return nil, 0, err
}
return &regency, int(totalDistricts), nil
}
func (r *wilayahIndonesiaRepository) FindAllDistricts(page, limit int) ([]model.District, int, error) {
var district []model.District
var total int64
err := r.DB.Model(&model.District{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
if page > 0 && limit > 0 {
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&district).Error
if err != nil {
return nil, 0, err
}
} else {
err := r.DB.Find(&district).Error
if err != nil {
return nil, 0, err
}
}
return district, int(total), nil
}
func (r *wilayahIndonesiaRepository) FindDistrictByID(id string, page, limit int) (*model.District, int, error) {
var district model.District
err := r.DB.Preload("Villages", func(db *gorm.DB) *gorm.DB {
if page > 0 && limit > 0 {
return db.Offset((page - 1) * limit).Limit(limit)
}
return db
}).Where("id = ?", id).First(&district).Error
if err != nil {
return nil, 0, err
}
var totalVillage int64
r.DB.Model(&model.Village{}).Where("district_id = ?", id).Count(&totalVillage)
return &district, int(totalVillage), nil
}
func (r *wilayahIndonesiaRepository) FindAllVillages(page, limit int) ([]model.Village, int, error) {
var villages []model.Village
var total int64
err := r.DB.Model(&model.Village{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
if page > 0 && limit > 0 {
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&villages).Error
if err != nil {
return nil, 0, err
}
} else {
err := r.DB.Find(&villages).Error
if err != nil {
return nil, 0, err
}
}
return villages, int(total), nil
}
func (r *wilayahIndonesiaRepository) FindVillageByID(id string) (*model.Village, error) {
var village model.Village
err := r.DB.Where("id = ?", id).First(&village).Error
if err != nil {
return nil, err
}
return &village, nil
}

View File

@ -1,70 +0,0 @@
package services
import (
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
)
func GetProvinces() ([]domain.Province, error) {
provinces, err := repositories.GetProvinces()
if err != nil {
return nil, err
}
return provinces, nil
}
func GetRegencies() ([]domain.Regency, error) {
regencies, err := repositories.GetRegencies()
if err != nil {
return nil, err
}
return regencies, nil
}
func GetDistricts() ([]domain.District, error) {
districts, err := repositories.GetDistricts()
if err != nil {
return nil, err
}
return districts, nil
}
func GetVillages() ([]domain.Village, error) {
villages, err := repositories.GetVillages()
if err != nil {
return nil, err
}
return villages, nil
}
func GetProvinceByID(id string) (domain.Province, error) {
province, err := repositories.GetProvinceByID(id)
if err != nil {
return domain.Province{}, err
}
return province, nil
}
func GetRegencyByID(id string) (domain.Regency, error) {
regency, err := repositories.GetRegencyByID(id)
if err != nil {
return domain.Regency{}, err
}
return regency, nil
}
func GetDistrictByID(id string) (domain.District, error) {
district, err := repositories.GetDistrictByID(id)
if err != nil {
return domain.District{}, err
}
return district, nil
}
func GetVillageByID(id string) (domain.Village, error) {
village, err := repositories.GetVillageByID(id)
if err != nil {
return domain.Village{}, err
}
return village, nil
}

View File

@ -1,110 +0,0 @@
package services
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
)
func CreateAddress(userID string, input dto.AddressInput) (domain.Address, error) {
address := domain.Address{
UserID: userID,
Province: input.Province,
District: input.District,
Subdistrict: input.Subdistrict,
PostalCode: input.PostalCode,
Village: input.Village,
Detail: input.Detail,
Geography: input.Geography,
}
err := repositories.CreateAddress(&address)
if err != nil {
return domain.Address{}, err
}
cacheKey := fmt.Sprintf("address:user:%s", userID)
config.RedisClient.Del(context.Background(), cacheKey)
return address, nil
}
func GetAllAddressesByUserID(userID string) ([]domain.Address, error) {
ctx := context.Background()
cacheKey := fmt.Sprintf("address:user:%s", userID)
cachedAddresses, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil {
var addresses []domain.Address
if json.Unmarshal([]byte(cachedAddresses), &addresses) == nil {
return addresses, nil
}
}
addresses, err := repositories.GetAddressesByUserID(userID)
if err != nil {
return nil, err
}
addressesJSON, _ := json.Marshal(addresses)
config.RedisClient.Set(ctx, cacheKey, addressesJSON, time.Hour).Err()
return addresses, nil
}
func GetAddressByID(addressID string) (domain.Address, error) {
address, err := repositories.GetAddressByID(addressID)
if err != nil {
return address, errors.New("address not found")
}
return address, nil
}
func UpdateAddress(addressID string, input dto.AddressInput) (domain.Address, error) {
address, err := repositories.GetAddressByID(addressID)
if err != nil {
return address, errors.New("address not found")
}
address.Province = input.Province
address.District = input.District
address.Subdistrict = input.Subdistrict
address.PostalCode = input.PostalCode
address.Village = input.Village
address.Detail = input.Detail
address.Geography = input.Geography
updatedAddress, err := repositories.UpdateAddress(address)
if err != nil {
return updatedAddress, errors.New("failed to update address")
}
cacheKey := fmt.Sprintf("address:user:%s", address.UserID)
config.RedisClient.Del(context.Background(), cacheKey)
return updatedAddress, nil
}
func DeleteAddress(addressID string) error {
address, err := repositories.GetAddressByID(addressID)
if err != nil {
return errors.New("address not found")
}
err = repositories.DeleteAddress(addressID)
if err != nil {
return errors.New("failed to delete address")
}
cacheKey := fmt.Sprintf("address:user:%s", address.UserID)
config.RedisClient.Del(context.Background(), cacheKey)
return nil
}

View File

@ -0,0 +1,402 @@
package services
import (
"fmt"
"time"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model"
"github.com/pahmiudahgede/senggoldong/utils"
)
type AddressService interface {
CreateAddress(userID string, request dto.CreateAddressDTO) (*dto.AddressResponseDTO, error)
GetAddressByUserID(userID string) ([]dto.AddressResponseDTO, error)
GetAddressByID(userID, id string) (*dto.AddressResponseDTO, error)
UpdateAddress(userID, id string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error)
DeleteAddress(userID, id string) error
}
type addressService struct {
AddressRepo repositories.AddressRepository
WilayahRepo repositories.WilayahIndonesiaRepository
}
func NewAddressService(addressRepo repositories.AddressRepository, wilayahRepo repositories.WilayahIndonesiaRepository) AddressService {
return &addressService{
AddressRepo: addressRepo,
WilayahRepo: wilayahRepo,
}
}
func (s *addressService) CreateAddress(userID string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error) {
province, _, err := s.WilayahRepo.FindProvinceByID(addressDTO.Province, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid province_id")
}
regency, _, err := s.WilayahRepo.FindRegencyByID(addressDTO.Regency, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid regency_id")
}
district, _, err := s.WilayahRepo.FindDistrictByID(addressDTO.District, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid district_id")
}
village, err := s.WilayahRepo.FindVillageByID(addressDTO.Village)
if err != nil {
return nil, fmt.Errorf("invalid village_id")
}
address := model.Address{
UserID: userID,
Province: province.Name,
Regency: regency.Name,
District: district.Name,
Village: village.Name,
PostalCode: addressDTO.PostalCode,
Detail: addressDTO.Detail,
Geography: addressDTO.Geography,
}
err = s.AddressRepo.CreateAddress(&address)
if err != nil {
return nil, fmt.Errorf("failed to create address: %v", err)
}
userCacheKey := fmt.Sprintf("user:%s:addresses", userID)
utils.DeleteData(userCacheKey)
createdAt, _ := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponseDTO := &dto.AddressResponseDTO{
UserID: address.UserID,
ID: address.ID,
Province: address.Province,
Regency: address.Regency,
District: address.District,
Village: address.Village,
PostalCode: address.PostalCode,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheKey := fmt.Sprintf("address:%s", address.ID)
cacheData := map[string]interface{}{
"data": addressResponseDTO,
}
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching new address to Redis: %v\n", err)
}
addresses, err := s.AddressRepo.FindAddressByUserID(userID)
if err != nil {
return nil, fmt.Errorf("failed to fetch updated addresses for user: %v", err)
}
var addressDTOs []dto.AddressResponseDTO
for _, addr := range addresses {
createdAt, _ := utils.FormatDateToIndonesianFormat(addr.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(addr.UpdatedAt)
addressDTOs = append(addressDTOs, dto.AddressResponseDTO{
UserID: addr.UserID,
ID: addr.ID,
Province: addr.Province,
Regency: addr.Regency,
District: addr.District,
Village: addr.Village,
PostalCode: addr.PostalCode,
Detail: addr.Detail,
Geography: addr.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": addressDTOs,
}
err = utils.SetJSONData(userCacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching updated user addresses to Redis: %v\n", err)
}
return addressResponseDTO, nil
}
func (s *addressService) GetAddressByUserID(userID string) ([]dto.AddressResponseDTO, error) {
cacheKey := fmt.Sprintf("user:%s:addresses", userID)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
var addresses []dto.AddressResponseDTO
if data, ok := cachedData["data"].([]interface{}); ok {
for _, item := range data {
addressData, ok := item.(map[string]interface{})
if ok {
addresses = append(addresses, dto.AddressResponseDTO{
UserID: addressData["user_id"].(string),
ID: addressData["address_id"].(string),
Province: addressData["province"].(string),
Regency: addressData["regency"].(string),
District: addressData["district"].(string),
Village: addressData["village"].(string),
PostalCode: addressData["postalCode"].(string),
Detail: addressData["detail"].(string),
Geography: addressData["geography"].(string),
CreatedAt: addressData["createdAt"].(string),
UpdatedAt: addressData["updatedAt"].(string),
})
}
}
return addresses, nil
}
}
addresses, err := s.AddressRepo.FindAddressByUserID(userID)
if err != nil {
return nil, fmt.Errorf("failed to fetch addresses: %v", err)
}
var addressDTOs []dto.AddressResponseDTO
for _, address := range addresses {
createdAt, _ := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressDTOs = append(addressDTOs, dto.AddressResponseDTO{
UserID: address.UserID,
ID: address.ID,
Province: address.Province,
Regency: address.Regency,
District: address.District,
Village: address.Village,
PostalCode: address.PostalCode,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData := map[string]interface{}{
"data": addressDTOs,
}
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching addresses to Redis: %v\n", err)
}
return addressDTOs, nil
}
func (s *addressService) GetAddressByID(userID, id string) (*dto.AddressResponseDTO, error) {
address, err := s.AddressRepo.FindAddressByID(id)
if err != nil {
return nil, fmt.Errorf("address not found: %v", err)
}
if address.UserID != userID {
return nil, fmt.Errorf("you are not authorized to update this address")
}
cacheKey := fmt.Sprintf("address:%s", id)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
addressData, ok := cachedData["data"].(map[string]interface{})
if ok {
address := dto.AddressResponseDTO{
UserID: addressData["user_id"].(string),
ID: addressData["address_id"].(string),
Province: addressData["province"].(string),
Regency: addressData["regency"].(string),
District: addressData["district"].(string),
Village: addressData["village"].(string),
PostalCode: addressData["postalCode"].(string),
Detail: addressData["detail"].(string),
Geography: addressData["geography"].(string),
CreatedAt: addressData["createdAt"].(string),
UpdatedAt: addressData["updatedAt"].(string),
}
return &address, nil
}
}
createdAt, _ := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressDTO := &dto.AddressResponseDTO{
UserID: address.UserID,
ID: address.ID,
Province: address.Province,
Regency: address.Regency,
District: address.District,
Village: address.Village,
PostalCode: address.PostalCode,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheData := map[string]interface{}{
"data": addressDTO,
}
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching address to Redis: %v\n", err)
}
return addressDTO, nil
}
func (s *addressService) UpdateAddress(userID, id string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error) {
address, err := s.AddressRepo.FindAddressByID(id)
if err != nil {
return nil, fmt.Errorf("address not found: %v", err)
}
if address.UserID != userID {
return nil, fmt.Errorf("you are not authorized to update this address")
}
province, _, err := s.WilayahRepo.FindProvinceByID(addressDTO.Province, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid province_id")
}
regency, _, err := s.WilayahRepo.FindRegencyByID(addressDTO.Regency, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid regency_id")
}
district, _, err := s.WilayahRepo.FindDistrictByID(addressDTO.District, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid district_id")
}
village, err := s.WilayahRepo.FindVillageByID(addressDTO.Village)
if err != nil {
return nil, fmt.Errorf("invalid village_id")
}
address.Province = province.Name
address.Regency = regency.Name
address.District = district.Name
address.Village = village.Name
address.PostalCode = addressDTO.PostalCode
address.Detail = addressDTO.Detail
address.Geography = addressDTO.Geography
address.UpdatedAt = time.Now()
err = s.AddressRepo.UpdateAddress(address)
if err != nil {
return nil, fmt.Errorf("failed to update address: %v", err)
}
addressCacheKey := fmt.Sprintf("address:%s", id)
utils.DeleteData(addressCacheKey)
userAddressesCacheKey := fmt.Sprintf("user:%s:addresses", userID)
utils.DeleteData(userAddressesCacheKey)
createdAt, _ := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
addressResponseDTO := &dto.AddressResponseDTO{
UserID: address.UserID,
ID: address.ID,
Province: address.Province,
Regency: address.Regency,
District: address.District,
Village: address.Village,
PostalCode: address.PostalCode,
Detail: address.Detail,
Geography: address.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheData := map[string]interface{}{
"data": addressResponseDTO,
}
err = utils.SetJSONData(addressCacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching updated address to Redis: %v\n", err)
}
addresses, err := s.AddressRepo.FindAddressByUserID(address.UserID)
if err != nil {
return nil, fmt.Errorf("failed to fetch updated addresses for user: %v", err)
}
var addressDTOs []dto.AddressResponseDTO
for _, addr := range addresses {
createdAt, _ := utils.FormatDateToIndonesianFormat(addr.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(addr.UpdatedAt)
addressDTOs = append(addressDTOs, dto.AddressResponseDTO{
UserID: addr.UserID,
ID: addr.ID,
Province: addr.Province,
Regency: addr.Regency,
District: addr.District,
Village: addr.Village,
PostalCode: addr.PostalCode,
Detail: addr.Detail,
Geography: addr.Geography,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": addressDTOs,
}
err = utils.SetJSONData(userAddressesCacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching updated user addresses to Redis: %v\n", err)
}
return addressResponseDTO, nil
}
func (s *addressService) DeleteAddress(userID, addressID string) error {
address, err := s.AddressRepo.FindAddressByID(addressID)
if err != nil {
return fmt.Errorf("address not found: %v", err)
}
if address.UserID != userID {
return fmt.Errorf("you are not authorized to delete this address")
}
err = s.AddressRepo.DeleteAddress(addressID)
if err != nil {
return fmt.Errorf("failed to delete address: %v", err)
}
addressCacheKey := fmt.Sprintf("address:%s", addressID)
err = utils.DeleteData(addressCacheKey)
if err != nil {
fmt.Printf("Error deleting address cache: %v\n", err)
}
userAddressesCacheKey := fmt.Sprintf("user:%s:addresses", address.UserID)
err = utils.DeleteData(userAddressesCacheKey)
if err != nil {
fmt.Printf("Error deleting user addresses cache: %v\n", err)
}
return nil
}

View File

@ -1,192 +0,0 @@
package services
import (
"encoding/json"
"errors"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/utils"
)
type ArticleService struct {
repo *repositories.ArticleRepository
}
func NewArticleService(repo *repositories.ArticleRepository) *ArticleService {
return &ArticleService{repo: repo}
}
func (s *ArticleService) GetAllArticles() ([]dto.ArticleResponse, error) {
ctx := config.Context()
cacheKey := "articles:all"
cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedData != "" {
var cachedArticles []dto.ArticleResponse
if err := json.Unmarshal([]byte(cachedData), &cachedArticles); err == nil {
return cachedArticles, nil
}
}
articles, err := s.repo.GetAll()
if err != nil {
return nil, err
}
var result []dto.ArticleResponse
for _, article := range articles {
result = append(result, dto.ArticleResponse{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: utils.FormatDateToIndonesianFormat(article.PublishedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(article.UpdatedAt),
})
}
cacheData, _ := json.Marshal(result)
config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5)
return result, nil
}
func (s *ArticleService) GetArticleByID(id string) (*dto.ArticleResponse, error) {
ctx := config.Context()
cacheKey := "articles:" + id
cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedData != "" {
var cachedArticle dto.ArticleResponse
if err := json.Unmarshal([]byte(cachedData), &cachedArticle); err == nil {
return &cachedArticle, nil
}
}
article, err := s.repo.GetByID(id)
if err != nil {
return nil, err
}
result := &dto.ArticleResponse{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: utils.FormatDateToIndonesianFormat(article.PublishedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(article.UpdatedAt),
}
cacheData, _ := json.Marshal(result)
config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5)
return result, nil
}
func (s *ArticleService) CreateArticle(request *dto.ArticleCreateRequest) (*dto.ArticleResponse, error) {
if err := request.Validate(); err != nil {
return nil, err
}
newArticle := &domain.Article{
Title: request.Title,
CoverImage: request.CoverImage,
Author: request.Author,
Heading: request.Heading,
Content: request.Content,
}
err := s.repo.Create(newArticle)
if err != nil {
return nil, errors.New("failed to create article")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "articles:all")
response := &dto.ArticleResponse{
ID: newArticle.ID,
Title: newArticle.Title,
CoverImage: newArticle.CoverImage,
Author: newArticle.Author,
Heading: newArticle.Heading,
Content: newArticle.Content,
PublishedAt: utils.FormatDateToIndonesianFormat(newArticle.PublishedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(newArticle.UpdatedAt),
}
return response, nil
}
func (s *ArticleService) UpdateArticle(id string, request *dto.ArticleUpdateRequest) (*dto.ArticleResponse, error) {
// if err := dto.GetValidator().Struct(request); err != nil {
// return nil, errors.New("invalid input data")
// }
if err := request.Validate(); err != nil {
return nil, err
}
article, err := s.repo.GetByID(id)
if err != nil {
return nil, errors.New("article not found")
}
article.Title = request.Title
article.CoverImage = request.CoverImage
article.Author = request.Author
article.Heading = request.Heading
article.Content = request.Content
article.UpdatedAt = time.Now()
err = s.repo.Update(article)
if err != nil {
return nil, errors.New("failed to update article")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "articles:all")
config.RedisClient.Del(ctx, "articles:"+id)
response := &dto.ArticleResponse{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: utils.FormatDateToIndonesianFormat(article.PublishedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(article.UpdatedAt),
}
return response, nil
}
func (s *ArticleService) DeleteArticle(id string) error {
article, err := s.repo.GetByID(id)
if err != nil {
return errors.New("article not found")
}
err = s.repo.Delete(article)
if err != nil {
return errors.New("failed to delete article")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "articles:all")
config.RedisClient.Del(ctx, "articles:"+id)
return nil
}

View File

@ -0,0 +1,418 @@
package services
import (
"encoding/json"
"fmt"
"mime/multipart"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model"
"github.com/pahmiudahgede/senggoldong/utils"
)
type ArticleService interface {
CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error)
GetArticleByID(id string) (*dto.ArticleResponseDTO, error)
UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
DeleteArticle(id string) error
}
type articleService struct {
ArticleRepo repositories.ArticleRepository
}
func NewArticleService(articleRepo repositories.ArticleRepository) ArticleService {
return &articleService{ArticleRepo: articleRepo}
}
func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
coverImageDir := "./public/uploads/articles"
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
}
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
extension := filepath.Ext(coverImage.Filename)
if !allowedExtensions[extension] {
return nil, fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
}
coverImageFileName := fmt.Sprintf("%s_cover%s", uuid.New().String(), extension)
coverImagePath := filepath.Join(coverImageDir, coverImageFileName)
src, err := coverImage.Open()
if err != nil {
return nil, fmt.Errorf("failed to open uploaded file: %v", err)
}
defer src.Close()
dst, err := os.Create(coverImagePath)
if err != nil {
return nil, fmt.Errorf("failed to create cover image file: %v", err)
}
defer dst.Close()
if _, err := dst.ReadFrom(src); err != nil {
return nil, fmt.Errorf("failed to save cover image: %v", err)
}
article := model.Article{
Title: request.Title,
CoverImage: coverImagePath,
Author: request.Author,
Heading: request.Heading,
Content: request.Content,
}
if err := s.ArticleRepo.CreateArticle(&article); err != nil {
return nil, fmt.Errorf("failed to create article: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
articleResponseDTO := &dto.ArticleResponseDTO{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheKey := fmt.Sprintf("article:%s", article.ID)
cacheData := map[string]interface{}{
"data": articleResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching article to Redis: %v\n", err)
}
articles, total, err := s.ArticleRepo.FindAllArticles(0, 0)
if err != nil {
fmt.Printf("Error fetching all articles: %v\n", err)
}
var articleDTOs []dto.ArticleResponseDTO
for _, a := range articles {
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
ID: a.ID,
Title: a.Title,
CoverImage: a.CoverImage,
Author: a.Author,
Heading: a.Heading,
Content: a.Content,
PublishedAt: createdAt,
UpdatedAt: updatedAt,
})
}
articlesCacheKey := "articles:all"
cacheData = map[string]interface{}{
"data": articleDTOs,
"total": total,
}
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching all articles to Redis: %v\n", err)
}
return articleResponseDTO, nil
}
func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error) {
var cacheKey string
if page == 0 && limit == 0 {
cacheKey = "articles:all"
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
if data, ok := cachedData["data"].([]interface{}); ok {
var articles []dto.ArticleResponseDTO
for _, item := range data {
articleData, ok := item.(map[string]interface{})
if ok {
articles = append(articles, dto.ArticleResponseDTO{
ID: articleData["article_id"].(string),
Title: articleData["title"].(string),
CoverImage: articleData["coverImage"].(string),
Author: articleData["author"].(string),
Heading: articleData["heading"].(string),
Content: articleData["content"].(string),
PublishedAt: articleData["publishedAt"].(string),
UpdatedAt: articleData["updatedAt"].(string),
})
}
}
if total, ok := cachedData["total"].(float64); ok {
fmt.Printf("Cached Total Articles: %f\n", total)
return articles, int(total), nil
} else {
fmt.Println("Total articles not found in cache, using 0 as fallback.")
return articles, 0, nil
}
}
}
}
articles, total, err := s.ArticleRepo.FindAllArticles(page, limit)
if err != nil {
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
}
fmt.Printf("Total Articles from Database: %d\n", total)
var articleDTOs []dto.ArticleResponseDTO
for _, article := range articles {
publishedAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: publishedAt,
UpdatedAt: updatedAt,
})
}
cacheKey = fmt.Sprintf("articles_page:%d_limit:%d", page, limit)
cacheData := map[string]interface{}{
"data": articleDTOs,
"total": total,
}
fmt.Printf("Setting cache with total: %d\n", total)
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching articles to Redis: %v\n", err)
}
return articleDTOs, total, nil
}
func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, error) {
cacheKey := fmt.Sprintf("article:%s", id)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
articleResponse := &dto.ArticleResponseDTO{}
if data, ok := cachedData["data"].(string); ok {
if err := json.Unmarshal([]byte(data), articleResponse); err == nil {
return articleResponse, nil
}
}
}
article, err := s.ArticleRepo.FindArticleByID(id)
if err != nil {
return nil, fmt.Errorf("failed to fetch article by ID: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
articleResponseDTO := &dto.ArticleResponseDTO{
ID: article.ID,
Title: article.Title,
CoverImage: article.CoverImage,
Author: article.Author,
Heading: article.Heading,
Content: article.Content,
PublishedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheData := map[string]interface{}{
"data": articleResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching article to Redis: %v\n", err)
}
return articleResponseDTO, nil
}
func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
article, err := s.ArticleRepo.FindArticleByID(id)
if err != nil {
return nil, fmt.Errorf("article not found: %v", id)
}
article.Title = request.Title
article.Heading = request.Heading
article.Content = request.Content
article.Author = request.Author
var coverImagePath string
if coverImage != nil {
coverImagePath, err = s.saveCoverImage(coverImage, article.CoverImage)
if err != nil {
return nil, fmt.Errorf("failed to save cover image: %v", err)
}
article.CoverImage = coverImagePath
}
err = s.ArticleRepo.UpdateArticle(id, article)
if err != nil {
return nil, fmt.Errorf("failed to update article: %v", err)
}
updatedArticle, err := s.ArticleRepo.FindArticleByID(id)
if err != nil {
return nil, fmt.Errorf("failed to fetch updated article: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.UpdatedAt)
articleResponseDTO := &dto.ArticleResponseDTO{
ID: updatedArticle.ID,
Title: updatedArticle.Title,
CoverImage: updatedArticle.CoverImage,
Author: updatedArticle.Author,
Heading: updatedArticle.Heading,
Content: updatedArticle.Content,
PublishedAt: createdAt,
UpdatedAt: updatedAt,
}
articleCacheKey := fmt.Sprintf("article:%s", updatedArticle.ID)
err = utils.SetJSONData(articleCacheKey, map[string]interface{}{"data": articleResponseDTO}, time.Hour*24)
if err != nil {
fmt.Printf("Error caching updated article to Redis: %v\n", err)
}
articlesCacheKey := "articles:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting articles cache: %v\n", err)
}
articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
if err != nil {
fmt.Printf("Error fetching all articles: %v\n", err)
} else {
var articleDTOs []dto.ArticleResponseDTO
for _, a := range articles {
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
ID: a.ID,
Title: a.Title,
CoverImage: a.CoverImage,
Author: a.Author,
Heading: a.Heading,
Content: a.Content,
PublishedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData := map[string]interface{}{
"data": articleDTOs,
}
err = utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching updated articles to Redis: %v\n", err)
}
}
return articleResponseDTO, nil
}
func (s *articleService) saveCoverImage(coverImage *multipart.FileHeader, oldImagePath string) (string, error) {
coverImageDir := "./public/uploads/articles"
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to create directory for cover image: %v", err)
}
}
extension := filepath.Ext(coverImage.Filename)
if extension != ".jpg" && extension != ".jpeg" && extension != ".png" {
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
}
coverImageFileName := fmt.Sprintf("%s_cover%s", uuid.New().String(), extension)
coverImagePath := filepath.Join(coverImageDir, coverImageFileName)
if oldImagePath != "" {
err := os.Remove(oldImagePath)
if err != nil {
fmt.Printf("Failed to delete old cover image: %v\n", err)
} else {
fmt.Printf("Successfully deleted old cover image: %s\n", oldImagePath)
}
}
src, err := coverImage.Open()
if err != nil {
return "", fmt.Errorf("failed to open uploaded file: %v", err)
}
defer src.Close()
dst, err := os.Create(coverImagePath)
if err != nil {
return "", fmt.Errorf("failed to create cover image file: %v", err)
}
defer dst.Close()
_, err = dst.ReadFrom(src)
if err != nil {
return "", fmt.Errorf("failed to save cover image: %v", err)
}
return coverImagePath, nil
}
func (s *articleService) DeleteArticle(id string) error {
article, err := s.ArticleRepo.FindArticleByID(id)
if err != nil {
return fmt.Errorf("failed to find article: %v", id)
}
if article.CoverImage != "" {
err := os.Remove(article.CoverImage)
if err != nil {
fmt.Printf("Failed to delete cover image: %v\n", err)
} else {
fmt.Printf("Successfully deleted cover image: %s\n", article.CoverImage)
}
}
err = s.ArticleRepo.DeleteArticle(id)
if err != nil {
return fmt.Errorf("failed to delete article: %v", err)
}
articleCacheKey := fmt.Sprintf("article:%s", id)
err = utils.DeleteData(articleCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for article: %v\n", err)
}
articlesCacheKey := "articles:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all articles: %v\n", err)
}
return nil
}

View File

@ -1,154 +0,0 @@
package services
import (
"errors"
"os"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"golang.org/x/crypto/bcrypt"
)
func RegisterUser(username, name, email, phone, password, confirmPassword, roleId string) error {
if password != confirmPassword {
return errors.New("password dan confirm password tidak cocok")
}
if repositories.IsEmailExist(email, roleId) {
return errors.New("email is already registered with the same role")
}
if repositories.IsUsernameExist(username, roleId) {
return errors.New("username is already registered with the same role")
}
if repositories.IsPhoneExist(phone, roleId) {
return errors.New("phone number is already registered with the same role")
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return errors.New("failed to hash password")
}
err = repositories.CreateUser(username, name, email, phone, string(hashedPassword), roleId)
if err != nil {
return err
}
return nil
}
func LoginUser(identifier, password string) (string, error) {
if identifier == "" || password == "" {
return "", errors.New("email/username/phone and password must be provided")
}
const roleId = ""
user, err := repositories.GetUserByEmailUsernameOrPhone(identifier, roleId)
if err != nil {
return "", errors.New("invalid email/username/phone or password")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
return "", errors.New("invalid email/username/phone or password")
}
token := generateJWT(user.ID, user.RoleID)
return token, nil
}
func generateJWT(userID, role string) string {
claims := jwt.MapClaims{
"sub": userID,
"role": role,
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := token.SignedString([]byte(os.Getenv("API_KEY")))
if err != nil {
return ""
}
return t
}
func GetUserByID(userID string) (domain.User, error) {
user, err := repositories.GetUserByID(userID)
if err != nil {
return user, errors.New("user not found")
}
return user, nil
}
func UpdateUser(userID, email, username, name, phone string) error {
user, err := repositories.GetUserByID(userID)
if err != nil {
return errors.New("user not found")
}
if email != "" && email != user.Email {
if repositories.IsEmailExist(email, user.RoleID) {
return errors.New("email is already registered with the same role")
}
user.Email = email
}
if username != "" && username != user.Username {
if repositories.IsUsernameExist(username, user.RoleID) {
return errors.New("username is already registered with the same role")
}
user.Username = username
}
if phone != "" && phone != user.Phone {
if repositories.IsPhoneExist(phone, user.RoleID) {
return errors.New("phone number is already registered with the same role")
}
user.Phone = phone
}
if name != "" {
user.Name = name
}
err = repositories.UpdateUser(&user)
if err != nil {
return errors.New("failed to update user")
}
return nil
}
func UpdatePassword(userID, oldPassword, newPassword string) error {
user, err := repositories.GetUserByID(userID)
if err != nil {
return errors.New("user not found")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword))
if err != nil {
return errors.New("old password is incorrect")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(newPassword))
if err == nil {
return errors.New("new password cannot be the same as the old password")
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return errors.New("failed to hash new password")
}
err = repositories.UpdateUserPassword(userID, string(hashedPassword))
if err != nil {
return errors.New("failed to update password")
}
return nil
}

View File

@ -0,0 +1,171 @@
package services
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model"
"github.com/pahmiudahgede/senggoldong/utils"
"golang.org/x/crypto/bcrypt"
)
const (
ErrUsernameTaken = "username is already taken"
ErrPhoneTaken = "phone number is already used for this role"
ErrEmailTaken = "email is already used for this role"
ErrInvalidRoleID = "invalid roleId"
ErrPasswordMismatch = "password and confirm password do not match"
ErrRoleIDRequired = "roleId is required"
ErrFailedToHashPassword = "failed to hash password"
ErrFailedToCreateUser = "failed to create user"
ErrIncorrectPassword = "incorrect password"
ErrAccountNotFound = "account not found"
)
type UserService interface {
Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error)
Register(user dto.RegisterDTO) (*dto.UserResponseDTO, error)
}
type userService struct {
UserRepo repositories.UserRepository
RoleRepo repositories.RoleRepository
SecretKey string
}
func NewUserService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository, secretKey string) UserService {
return &userService{UserRepo: userRepo, RoleRepo: roleRepo, SecretKey: secretKey}
}
func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error) {
if credentials.RoleID == "" {
return nil, errors.New(ErrRoleIDRequired)
}
user, err := s.UserRepo.FindByIdentifierAndRole(credentials.Identifier, credentials.RoleID)
if err != nil {
return nil, errors.New(ErrAccountNotFound)
}
if !CheckPasswordHash(credentials.Password, user.Password) {
return nil, errors.New(ErrIncorrectPassword)
}
token, err := s.generateJWT(user)
if err != nil {
return nil, err
}
sessionKey := fmt.Sprintf("session:%s", user.ID)
sessionData := map[string]interface{}{
"userID": user.ID,
"roleID": user.RoleID,
"roleName": user.Role.RoleName,
}
err = utils.SetJSONData(sessionKey, sessionData, time.Hour*24)
if err != nil {
return nil, err
}
return &dto.UserResponseWithToken{
RoleName: user.Role.RoleName,
UserID: user.ID,
Token: token,
}, nil
}
func (s *userService) generateJWT(user *model.User) (string, error) {
claims := jwt.MapClaims{
"sub": user.ID,
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour * 24).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(s.SecretKey))
if err != nil {
return "", err
}
return tokenString, nil
}
func CheckPasswordHash(password, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
func (s *userService) Register(user dto.RegisterDTO) (*dto.UserResponseDTO, error) {
if user.Password != user.ConfirmPassword {
return nil, fmt.Errorf("%s", ErrPasswordMismatch)
}
if user.RoleID == "" {
return nil, fmt.Errorf("%s", ErrRoleIDRequired)
}
role, err := s.RoleRepo.FindByID(user.RoleID)
if err != nil {
return nil, fmt.Errorf("%s: %v", ErrInvalidRoleID, err)
}
if existingUser, _ := s.UserRepo.FindByUsername(user.Username); existingUser != nil {
return nil, fmt.Errorf("%s", ErrUsernameTaken)
}
if existingPhone, _ := s.UserRepo.FindByPhoneAndRole(user.Phone, user.RoleID); existingPhone != nil {
return nil, fmt.Errorf("%s", ErrPhoneTaken)
}
if existingEmail, _ := s.UserRepo.FindByEmailAndRole(user.Email, user.RoleID); existingEmail != nil {
return nil, fmt.Errorf("%s", ErrEmailTaken)
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("%s: %v", ErrFailedToHashPassword, err)
}
newUser := model.User{
Username: user.Username,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
Password: string(hashedPassword),
RoleID: user.RoleID,
}
err = s.UserRepo.Create(&newUser)
if err != nil {
return nil, fmt.Errorf("%s: %v", ErrFailedToCreateUser, err)
}
userResponse := s.prepareUserResponse(newUser, role)
return userResponse, nil
}
func (s *userService) prepareUserResponse(user model.User, role *model.Role) *dto.UserResponseDTO {
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
return &dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
EmailVerified: user.EmailVerified,
RoleName: role.RoleName,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}

View File

@ -1,170 +0,0 @@
package services
import (
"encoding/json"
"errors"
"time"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerService struct {
repo *repositories.BannerRepository
}
func NewBannerService(repo *repositories.BannerRepository) *BannerService {
return &BannerService{repo: repo}
}
func (s *BannerService) GetAllBanners() ([]dto.BannerResponse, error) {
ctx := config.Context()
cacheKey := "banners:all"
cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedData != "" {
var cachedBanners []dto.BannerResponse
if err := json.Unmarshal([]byte(cachedData), &cachedBanners); err == nil {
return cachedBanners, nil
}
}
banners, err := s.repo.GetAll()
if err != nil {
return nil, err
}
var result []dto.BannerResponse
for _, banner := range banners {
result = append(result, dto.BannerResponse{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt),
})
}
cacheData, _ := json.Marshal(result)
config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5)
return result, nil
}
func (s *BannerService) GetBannerByID(id string) (*dto.BannerResponse, error) {
ctx := config.Context()
cacheKey := "banners:" + id
cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && cachedData != "" {
var cachedBanner dto.BannerResponse
if err := json.Unmarshal([]byte(cachedData), &cachedBanner); err == nil {
return &cachedBanner, nil
}
}
banner, err := s.repo.GetByID(id)
if err != nil {
return nil, err
}
result := &dto.BannerResponse{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt),
}
cacheData, _ := json.Marshal(result)
config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5)
return result, nil
}
func (s *BannerService) CreateBanner(request *dto.BannerCreateRequest) (*dto.BannerResponse, error) {
if request.BannerName == "" || request.BannerImage == "" {
return nil, errors.New("invalid input data")
}
newBanner := &domain.Banner{
BannerName: request.BannerName,
BannerImage: request.BannerImage,
}
err := s.repo.Create(newBanner)
if err != nil {
return nil, errors.New("failed to create banner")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "banners:all")
response := &dto.BannerResponse{
ID: newBanner.ID,
BannerName: newBanner.BannerName,
BannerImage: newBanner.BannerImage,
CreatedAt: utils.FormatDateToIndonesianFormat(newBanner.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(newBanner.UpdatedAt),
}
return response, nil
}
func (s *BannerService) UpdateBanner(id string, request *dto.BannerUpdateRequest) (*dto.BannerResponse, error) {
banner, err := s.repo.GetByID(id)
if err != nil {
return nil, errors.New("banner not found")
}
if request.BannerName != nil && *request.BannerName != "" {
banner.BannerName = *request.BannerName
}
if request.BannerImage != nil && *request.BannerImage != "" {
banner.BannerImage = *request.BannerImage
}
banner.UpdatedAt = time.Now()
err = s.repo.Update(banner)
if err != nil {
return nil, errors.New("failed to update banner")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "banners:all")
config.RedisClient.Del(ctx, "banners:"+id)
response := &dto.BannerResponse{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt),
UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt),
}
return response, nil
}
func (s *BannerService) DeleteBanner(id string) error {
banner, err := s.repo.GetByID(id)
if err != nil {
return errors.New("banner not found")
}
err = s.repo.Delete(banner)
if err != nil {
return errors.New("failed to delete banner")
}
ctx := config.Context()
config.RedisClient.Del(ctx, "banners:all")
config.RedisClient.Del(ctx, "banners:"+id)
return nil
}

View File

@ -0,0 +1,365 @@
package services
import (
"fmt"
"mime/multipart"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerService interface {
CreateBanner(request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error)
GetAllBanners() ([]dto.ResponseBannerDTO, error)
GetBannerByID(id string) (*dto.ResponseBannerDTO, error)
UpdateBanner(id string, request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error)
DeleteBanner(id string) error
}
type bannerService struct {
BannerRepo repositories.BannerRepository
}
func NewBannerService(bannerRepo repositories.BannerRepository) BannerService {
return &bannerService{BannerRepo: bannerRepo}
}
func (s *bannerService) saveBannerImage(bannerImage *multipart.FileHeader) (string, error) {
bannerImageDir := "./public/uploads/banners"
if _, err := os.Stat(bannerImageDir); os.IsNotExist(err) {
if err := os.MkdirAll(bannerImageDir, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to create directory for banner image: %v", err)
}
}
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
extension := filepath.Ext(bannerImage.Filename)
if !allowedExtensions[extension] {
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
}
bannerImageFileName := fmt.Sprintf("%s_banner%s", uuid.New().String(), extension)
bannerImagePath := filepath.Join(bannerImageDir, bannerImageFileName)
src, err := bannerImage.Open()
if err != nil {
return "", fmt.Errorf("failed to open uploaded file: %v", err)
}
defer src.Close()
dst, err := os.Create(bannerImagePath)
if err != nil {
return "", fmt.Errorf("failed to create banner image file: %v", err)
}
defer dst.Close()
if _, err := dst.ReadFrom(src); err != nil {
return "", fmt.Errorf("failed to save banner image: %v", err)
}
return bannerImagePath, nil
}
func (s *bannerService) CreateBanner(request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error) {
errors, valid := request.ValidateBannerInput()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)
}
bannerImagePath, err := s.saveBannerImage(bannerImage)
if err != nil {
return nil, fmt.Errorf("failed to save banner image: %v", err)
}
banner := model.Banner{
BannerName: request.BannerName,
BannerImage: bannerImagePath,
}
if err := s.BannerRepo.CreateBanner(&banner); err != nil {
return nil, fmt.Errorf("failed to create banner: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
cacheKey := fmt.Sprintf("banner:%s", banner.ID)
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banner: %v\n", err)
}
banners, err := s.BannerRepo.FindAllBanners()
if err == nil {
var bannersDTO []dto.ResponseBannerDTO
for _, b := range banners {
createdAt, _ := utils.FormatDateToIndonesianFormat(b.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(b.UpdatedAt)
bannersDTO = append(bannersDTO, dto.ResponseBannerDTO{
ID: b.ID,
BannerName: b.BannerName,
BannerImage: b.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": bannersDTO,
}
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banners to Redis: %v\n", err)
}
} else {
fmt.Printf("Error fetching all banners: %v\n", err)
}
return bannerResponseDTO, nil
}
func (s *bannerService) GetAllBanners() ([]dto.ResponseBannerDTO, error) {
var banners []dto.ResponseBannerDTO
cacheKey := "banners:all"
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
if data, ok := cachedData["data"].([]interface{}); ok {
for _, item := range data {
if bannerData, ok := item.(map[string]interface{}); ok {
banners = append(banners, dto.ResponseBannerDTO{
ID: bannerData["id"].(string),
BannerName: bannerData["bannername"].(string),
BannerImage: bannerData["bannerimage"].(string),
CreatedAt: bannerData["createdAt"].(string),
UpdatedAt: bannerData["updatedAt"].(string),
})
}
}
return banners, nil
}
}
records, err := s.BannerRepo.FindAllBanners()
if err != nil {
return nil, fmt.Errorf("failed to fetch banners: %v", err)
}
for _, record := range records {
createdAt, _ := utils.FormatDateToIndonesianFormat(record.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(record.UpdatedAt)
banners = append(banners, dto.ResponseBannerDTO{
ID: record.ID,
BannerName: record.BannerName,
BannerImage: record.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData := map[string]interface{}{
"data": banners,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banners: %v\n", err)
}
return banners, nil
}
func (s *bannerService) GetBannerByID(id string) (*dto.ResponseBannerDTO, error) {
cacheKey := fmt.Sprintf("banner:%s", id)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
if data, ok := cachedData["data"].(map[string]interface{}); ok {
return &dto.ResponseBannerDTO{
ID: data["id"].(string),
BannerName: data["bannername"].(string),
BannerImage: data["bannerimage"].(string),
CreatedAt: data["createdAt"].(string),
UpdatedAt: data["updatedAt"].(string),
}, nil
}
}
banner, err := s.BannerRepo.FindBannerByID(id)
if err != nil {
return nil, fmt.Errorf("banner with ID %s not found", id)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banner: %v\n", err)
}
return bannerResponseDTO, nil
}
func (s *bannerService) UpdateBanner(id string, request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error) {
banner, err := s.BannerRepo.FindBannerByID(id)
if err != nil {
return nil, fmt.Errorf("banner with ID %s not found", id)
}
var oldImagePath string
if bannerImage != nil {
bannerImagePath, err := s.saveBannerImage(bannerImage)
if err != nil {
return nil, fmt.Errorf("failed to save banner image: %v", err)
}
oldImagePath = banner.BannerImage
banner.BannerImage = bannerImagePath
}
banner.BannerName = request.BannerName
if err := s.BannerRepo.UpdateBanner(id, banner); err != nil {
return nil, fmt.Errorf("failed to update banner: %v", err)
}
if oldImagePath != "" {
err := os.Remove(oldImagePath)
if err != nil {
fmt.Printf("Failed to delete old banner image: %v\n", err)
}
}
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheKey := fmt.Sprintf("banner:%s", id)
err = utils.DeleteData(cacheKey)
if err != nil {
fmt.Printf("Error deleting cache for banner: %v\n", err)
}
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banner: %v\n", err)
}
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
banners, err := s.BannerRepo.FindAllBanners()
if err == nil {
var bannersDTO []dto.ResponseBannerDTO
for _, b := range banners {
createdAt, _ := utils.FormatDateToIndonesianFormat(b.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(b.UpdatedAt)
bannersDTO = append(bannersDTO, dto.ResponseBannerDTO{
ID: b.ID,
BannerName: b.BannerName,
BannerImage: b.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": bannersDTO,
}
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banners to Redis: %v\n", err)
}
} else {
fmt.Printf("Error fetching all banners: %v\n", err)
}
return bannerResponseDTO, nil
}
func (s *bannerService) DeleteBanner(id string) error {
banner, err := s.BannerRepo.FindBannerByID(id)
if err != nil {
return fmt.Errorf("banner with ID %s not found", id)
}
if banner.BannerImage != "" {
err := os.Remove(banner.BannerImage)
if err != nil {
fmt.Printf("Failed to delete banner image: %v\n", err)
} else {
fmt.Printf("Successfully deleted banner image: %s\n", banner.BannerImage)
}
}
if err := s.BannerRepo.DeleteBanner(id); err != nil {
return fmt.Errorf("failed to delete banner from database: %v", err)
}
cacheKey := fmt.Sprintf("banner:%s", banner.ID)
err = utils.DeleteData(cacheKey)
if err != nil {
fmt.Printf("Error deleting cache for banner: %v\n", err)
}
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More