diff --git a/.gitignore b/.gitignore index ee7ad1e..198ea37 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /go.work.sum /.cache/ *.log +BackEnd/storage # ==================================== # 🔑 Environment & sensitive files diff --git a/BackEnd/README.md b/BackEnd/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/BackEnd/Readme.md b/BackEnd/Readme.md deleted file mode 100644 index cfeb7ac..0000000 --- a/BackEnd/Readme.md +++ /dev/null @@ -1,80 +0,0 @@ -# web-doscom Backend - -Backend API for Dinus Open Source Community (DOSCOM) - -## Features -- User registration and login (JWT-based) -- Role-based access: User, Admin, Super_Admin -- CRUD for users, works, activities, blogs, gallery, pengurus -- Secure password hashing (bcrypt) -- RESTful API with Gin -- Swagger/OpenAPI documentation (auto-generated with swag) -- Live reload for development (air) -- PostgreSQL database - -## Requirements -- Go 1.18+ -- PostgreSQL -- [air](https://github.com/air-verse/air) (for live reload, optional) -- [swag](https://github.com/swaggo/swag) (for Swagger docs) - -## Setup - -1. **Clone the repo** -```sh -git clone https://github.com/Dinus-Open-Source-Community/web-doscom.git -cd web-doscom/BackEnd -``` - -2. **Configure environment** -- Copy `.env.example` to `.env` and edit DB credentials as needed. - -3. **Run database migrations** -- Use the SQL files in `migrations/` to set up your database tables. - -4. **Install dependencies and tools** -```sh -go mod tidy -go install github.com/air-verse/air@latest -go install github.com/swaggo/swag/cmd/swag@latest -``` - -5. **Generate Swagger docs** -```sh -swag init -g cmd/api/main.go -o docs -``` - -6. **Run the server (with live reload)** -```sh -air -# or -GO_ENV=development go run ./cmd/api -``` - -## API Endpoints - -- `POST /api/v1/register` — Register user (fields: username, email, password, fullname, role) -- `POST /api/v1/login` — Login (returns JWT) -- `GET /api/v1/users/:id` — Get user by ID -- `GET /api/v1/users` — List users -- `PUT /api/v1/users/:id` — Update user -- `DELETE /api/v1/users/:id` — Delete user -- `POST /api/v1/users/superadmin` — Create superadmin (admin only) -- ...and more for works, activities, blogs, gallery, pengurus - -## Auth -- Use the JWT token from `/api/v1/login` in the `Authorization: Bearer ` header for protected endpoints. -- Only Admin/Super_Admin can create superadmin users. - -## Swagger UI -- After running `swag init`, access docs at: `http://localhost:3001/swagger/index.html` - -## Development -- Use `air` for hot reload during development. -- Use `swag` to update API docs after changing handler comments. - -## Contributing -Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change. - -## License -[MIT](LICENSE) \ No newline at end of file diff --git a/BackEnd/internal/database/model.go b/BackEnd/internal/database/model.go index 732e1e3..88aaaf7 100644 --- a/BackEnd/internal/database/model.go +++ b/BackEnd/internal/database/model.go @@ -12,6 +12,7 @@ type Models struct { Blogs model.BlogModel Gallery model.GalleryModel Pengurus model.PengurusModel + // Activities model.ActivityModel // Uncomment if you have ActivityModel } func NewModel(db *gorm.DB) Models { @@ -21,5 +22,6 @@ func NewModel(db *gorm.DB) Models { Blogs: model.BlogModel{DB: db}, Gallery: model.GalleryModel{DB: db}, Pengurus: model.PengurusModel{DB: db}, + // Activities: model.ActivityModel{DB: db}, // Uncomment if you have ActivityModel } } diff --git a/BackEnd/internal/handler/blog.go b/BackEnd/internal/handler/blog.go index 5cc41a3..7297123 100644 --- a/BackEnd/internal/handler/blog.go +++ b/BackEnd/internal/handler/blog.go @@ -7,18 +7,45 @@ import ( "web_doscom/internal/database/model" "github.com/gin-gonic/gin" - "gorm.io/gorm" ) type BlogHandler struct { - DB *gorm.DB + Model *model.BlogModel } -func NewBlogHandler(db *gorm.DB) *BlogHandler { - return &BlogHandler{DB: db} +func NewBlogHandler(db *model.BlogModel) *BlogHandler { + return &BlogHandler{Model: db} } // Create Blog +func (h *BlogHandler) CreateBlog(c *gin.Context) { + var input model.RegisterBlog + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + blog := &model.Blog{ + Title: input.Title, + GalleryID: input.GalleryID, + Slug: input.Slug, + Content: input.Content, + Kategori: input.Kategori, + PublishedAt: input.PublishedAt, + IsPublished: input.IsPublished, + WorkID: input.WorkID, + ActivityID: input.ActivityID, + PengurusID: input.PengurusID, + } + + if err := h.DB.Create(blog).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, blog) +} + // Create Blog (validation only, no DB insert) func (h *BlogHandler) Create(c *gin.Context) { var blog model.Blog @@ -46,7 +73,7 @@ func (h *BlogHandler) Create(c *gin.Context) { // List all Blogs func (h *BlogHandler) List(c *gin.Context) { var blogs []model.Blog - if err := h.DB.Order("created_at DESC").Find(&blogs).Error; err != nil { + if err := h.Model.Order("created_at DESC").Find(&blogs).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -100,16 +127,45 @@ func (h *BlogHandler) Update(c *gin.Context) { c.JSON(http.StatusOK, blog) } +// UpdateKategori updates the kategori of a blog by id func (h *BlogHandler) UpdateKategori(c *gin.Context) { - // TODO: implement the logic for updating kategori - c.JSON(200, gin.H{"message": "UpdateKategori not implemented"}) + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) + return + } + var req struct { + Kategori string `json:"kategori" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + var blog model.Blog + if err := h.DB.First(&blog, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "blog not found"}) + return + } + blog.Kategori = req.Kategori + if err := h.DB.Save(&blog).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Kategori updated", "blog": blog}) } + +// ListByKategori returns blogs filtered by kategori func (h *BlogHandler) ListByKategori(c *gin.Context) { kategori := c.Param("kategori") - // TODO: Implement logic to list blogs by kategori - c.JSON(200, gin.H{ + var blogs []model.Blog + if err := h.DB.Where("kategori = ?", kategori).Order("created_at DESC").Find(&blogs).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{ "message": "List blogs by kategori", "kategori": kategori, + "blogs": blogs, }) } diff --git a/BackEnd/internal/handler/gallery.go b/BackEnd/internal/handler/gallery.go index e2a6246..7df151f 100644 --- a/BackEnd/internal/handler/gallery.go +++ b/BackEnd/internal/handler/gallery.go @@ -109,7 +109,7 @@ func (m *GalleryHandler) InsertGallery(c *gin.Context) { // @Tags Gallery // @Accept json // @Produce json -// @Param type query string true "Gallery type (misal: event, pengurus, dokumentasi)" +// @Param type query string true "Gallery type (fun, proker, achievment, work, activity, blog, pengurus, etc)" // @Param page query int false "Page number" // @Param limit query int false "Page limit" // @Success 200 {object} map[string]interface{} "Successfully fetch gallery data" diff --git a/BackEnd/internal/routes/route/pengurus_route.go b/BackEnd/internal/routes/route/pengurus_route.go index b9d4f11..176ecf3 100644 --- a/BackEnd/internal/routes/route/pengurus_route.go +++ b/BackEnd/internal/routes/route/pengurus_route.go @@ -12,7 +12,6 @@ func RegisterPengurusRoutes(rg *gin.RouterGroup, pengurusHandler *handler.Pengur p := rg.Group("/pengurus") // public api { - p.GET("/:id", pengurusHandler.GetPengurus) p.GET("/", pengurusHandler.GetAllPengurus) } @@ -20,6 +19,7 @@ func RegisterPengurusRoutes(rg *gin.RouterGroup, pengurusHandler *handler.Pengur pengurusAuth := p.Group("/") pengurusAuth.Use(auth.AuthMiddleware("ANGGOTA", "KOOR", "BPH", "ADMIN")) { + p.GET("/:id", pengurusHandler.GetPengurus) pengurusAuth.POST("/", pengurusHandler.CreatePengurus) pengurusAuth.PUT("/:id", pengurusHandler.UpdatePengurus) } diff --git a/BackEnd/internal/service/gallery_service.go b/BackEnd/internal/service/gallery_service.go index 50b7c69..a225852 100644 --- a/BackEnd/internal/service/gallery_service.go +++ b/BackEnd/internal/service/gallery_service.go @@ -44,7 +44,7 @@ func (m *GalleryService) InsertGallery(gallery *model.Gallery) (*model.Gallery, } // wrapper for get gallery by type -func (m *GalleryService) GetAllGalleryByType(tipe string, limit, offset, page int) ([]*model.GalleryResponse, int64, error) { +func (m *GalleryService) GetAllGalleryByType(tipe string, page, limit, offset int) ([]*model.GalleryResponse, int64, error) { var response []*model.GalleryResponse galleries, count, err := m.Model.GetGalleryByType(tipe, page, limit, offset) diff --git a/BackEnd/tmp/main b/BackEnd/tmp/main index 9390451..28e8888 100755 Binary files a/BackEnd/tmp/main and b/BackEnd/tmp/main differ