Massively reduce DB calls
This commit is contained in:
505
main.go
505
main.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -61,25 +62,14 @@ socket.addEventListener('message', (event) => {
|
|||||||
});
|
});
|
||||||
</script>`
|
</script>`
|
||||||
|
|
||||||
type Category struct {
|
var (
|
||||||
ID int64 `json:"id"`
|
insertCategoryStmt *sql.Stmt
|
||||||
Name string `json:"name"`
|
insertLinkStmt *sql.Stmt
|
||||||
Icon string `json:"icon"`
|
)
|
||||||
Links []Link `json:"links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Link struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
CategoryID int64 `json:"category_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
db *sql.DB
|
|
||||||
*WeatherCache
|
*WeatherCache
|
||||||
|
*CategoryManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(dbPath string) (*App, error) {
|
func NewApp(dbPath string) (*App, error) {
|
||||||
@@ -94,65 +84,21 @@ func NewApp(dbPath string) (*App, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(string(schema))
|
_, err = db.Exec(string(schema))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryManager, err := NewCategoryManager(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
db: db,
|
|
||||||
WeatherCache: NewWeatherCache(),
|
WeatherCache: NewWeatherCache(),
|
||||||
|
CategoryManager: categoryManager,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetCategories() ([]Category, error) {
|
|
||||||
rows, err := app.db.Query(`
|
|
||||||
SELECT id, name, icon
|
|
||||||
FROM categories
|
|
||||||
ORDER BY id ASC
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var categories []Category
|
|
||||||
for rows.Next() {
|
|
||||||
var cat Category
|
|
||||||
if err := rows.Scan(&cat.ID, &cat.Name, &cat.Icon); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
links, err := app.GetLinksByCategory(cat.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cat.Links = links
|
|
||||||
categories = append(categories, cat)
|
|
||||||
}
|
|
||||||
return categories, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *App) GetLinksByCategory(categoryID int64) ([]Link, error) {
|
|
||||||
rows, err := app.db.Query(`
|
|
||||||
SELECT id, category_id, name, description, icon, url
|
|
||||||
FROM links
|
|
||||||
WHERE category_id = ?
|
|
||||||
ORDER BY id ASC
|
|
||||||
`, categoryID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var links []Link
|
|
||||||
for rows.Next() {
|
|
||||||
var link Link
|
|
||||||
if err := rows.Scan(&link.ID, &link.CategoryID, &link.Name, &link.Description,
|
|
||||||
&link.Icon, &link.URL); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
links = append(links, link)
|
|
||||||
}
|
|
||||||
return links, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenWeatherResponse struct {
|
type OpenWeatherResponse struct {
|
||||||
Weather []struct {
|
Weather []struct {
|
||||||
Name string `json:"main"`
|
Name string `json:"main"`
|
||||||
@@ -277,14 +223,6 @@ func (c *WeatherCache) updateWeather() {
|
|||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateLinkRequest struct {
|
|
||||||
Name string `form:"name"`
|
|
||||||
Description string `form:"description"`
|
|
||||||
URL string `form:"url"`
|
|
||||||
Icon *multipart.FileHeader `form:"icon"`
|
|
||||||
CategoryID int64 `form:"category_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
|
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
|
||||||
srcFile, err := file.Open()
|
srcFile, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -342,151 +280,169 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
|
|||||||
return iconPath, nil
|
return iconPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateLink(db *sql.DB) fiber.Handler {
|
type Category struct {
|
||||||
return func(c fiber.Ctx) error {
|
ID int64 `json:"id"`
|
||||||
var req CreateLinkRequest
|
Name string `json:"name"`
|
||||||
if err := c.Bind().MultipartForm(&req); err != nil {
|
Icon string `json:"icon"`
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
Links []Link `json:"links"`
|
||||||
"message": "Failed to parse request",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" || req.URL == "" {
|
type Link struct {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
ID int64 `json:"id"`
|
||||||
"message": "Name and URL are required",
|
CategoryID int64 `json:"category_id"`
|
||||||
})
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := c.FormFile("icon")
|
type CategoryManager struct {
|
||||||
if err != nil || file == nil {
|
db *sql.DB
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
Categories []Category
|
||||||
"message": "Icon is required",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.Size > 5*1024*1024 {
|
func NewCategoryManager(db *sql.DB) (*CategoryManager, error) {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
rows, err := db.Query(`
|
||||||
"message": "File size too large. Maximum size is 5MB",
|
SELECT id, name, icon
|
||||||
})
|
FROM categories
|
||||||
}
|
ORDER BY id ASC
|
||||||
|
`)
|
||||||
contentType := file.Header.Get("Content-Type")
|
|
||||||
if !strings.HasPrefix(contentType, "image/") {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "Only image files are allowed",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
|
||||||
|
|
||||||
if contentType == "image/svg+xml" {
|
|
||||||
filename = fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
|
||||||
}
|
|
||||||
|
|
||||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
return nil, err
|
||||||
"message": "Failed to upload file, please try again!",
|
}
|
||||||
})
|
defer rows.Close()
|
||||||
|
|
||||||
|
var categories []Category
|
||||||
|
for rows.Next() {
|
||||||
|
var cat Category
|
||||||
|
if err := rows.Scan(&cat.ID, &cat.Name, &cat.Icon); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
link := Link{
|
rows, err := db.Query(`
|
||||||
Name: req.Name,
|
SELECT id, category_id, name, description, icon, url
|
||||||
Description: req.Description,
|
FROM links
|
||||||
URL: req.URL,
|
WHERE category_id = ?
|
||||||
Icon: iconPath,
|
ORDER BY id ASC
|
||||||
CategoryID: req.CategoryID,
|
`, cat.ID)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`
|
|
||||||
INSERT INTO links (category_id, name, description, icon, url)
|
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
|
||||||
link.CategoryID, link.Name, link.Description, link.Icon, link.URL)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
return nil, err
|
||||||
"message": "Failed to create link",
|
}
|
||||||
})
|
defer rows.Close()
|
||||||
|
|
||||||
|
var links []Link
|
||||||
|
for rows.Next() {
|
||||||
|
var link Link
|
||||||
|
if err := rows.Scan(&link.ID, &link.CategoryID, &link.Name, &link.Description,
|
||||||
|
&link.Icon, &link.URL); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
links = append(links, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
cat.Links = links
|
||||||
"message": "Link created successfully",
|
categories = append(categories, cat)
|
||||||
"link": link,
|
}
|
||||||
})
|
|
||||||
|
return &CategoryManager{
|
||||||
|
db: db,
|
||||||
|
Categories: categories,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Category by ID, returns nil if not found
|
||||||
|
func (manager *CategoryManager) GetCategory(id int64) *Category {
|
||||||
|
var category *Category
|
||||||
|
|
||||||
|
// probably potentially bad
|
||||||
|
for _, cat := range manager.Categories {
|
||||||
|
if cat.ID == id {
|
||||||
|
category = &cat
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateCategoryRequest struct {
|
return category
|
||||||
Name string `form:"name"`
|
|
||||||
Icon *multipart.FileHeader `form:"icon"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateCategory(db *sql.DB) fiber.Handler {
|
func (manager *CategoryManager) CreateCategory(category Category) (*Category, error) {
|
||||||
return func(c fiber.Ctx) error {
|
var err error
|
||||||
var req CreateCategoryRequest
|
|
||||||
if err := c.Bind().MultipartForm(&req); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "Failed to parse request",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Name == "" {
|
insertCategoryStmt, err = manager.db.Prepare(`
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "Name is required",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := c.FormFile("icon")
|
|
||||||
if err != nil || file == nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "Icon is required",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.Size > 5*1024*1024 {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "File size too large. Maximum size is 5MB",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := file.Header.Get("Content-Type")
|
|
||||||
if contentType != "image/svg+xml" {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"message": "Only SVGs are supported for category icons!",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
|
||||||
|
|
||||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"message": "Failed to upload file, please try again!",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
category := Category{
|
|
||||||
Name: req.Name,
|
|
||||||
Icon: iconPath,
|
|
||||||
Links: []Link{},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`
|
|
||||||
INSERT INTO categories (name, icon)
|
INSERT INTO categories (name, icon)
|
||||||
VALUES (?, ?)`,
|
VALUES (?, ?) RETURNING id`)
|
||||||
category.Name, category.Icon)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
return nil, err
|
||||||
"message": "Failed to create category",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
defer insertCategoryStmt.Close()
|
||||||
"message": "Category created successfully",
|
|
||||||
"category": category,
|
var categoryID int64
|
||||||
})
|
|
||||||
|
if err := insertCategoryStmt.QueryRow(category.Name, category.Icon).Scan(&categoryID); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
category.ID = categoryID
|
||||||
|
manager.Categories = append(manager.Categories, category)
|
||||||
|
|
||||||
|
return &category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CategoryManager) CreateLink(db *sql.DB, link Link) (*Link, error) {
|
||||||
|
var err error
|
||||||
|
insertLinkStmt, err = db.Prepare(`
|
||||||
|
INSERT INTO links (category_id, name, description, icon, url)
|
||||||
|
VALUES (?, ?, ?, ?, ?) RETURNING id`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer insertLinkStmt.Close()
|
||||||
|
|
||||||
|
var linkID int64
|
||||||
|
if err := insertLinkStmt.QueryRow(link.CategoryID, link.Name, link.Description, link.Icon, link.URL).Scan(&linkID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
link.ID = linkID
|
||||||
|
|
||||||
|
var cat *Category
|
||||||
|
for i, c := range manager.Categories {
|
||||||
|
if c.ID == link.CategoryID {
|
||||||
|
cat = &manager.Categories[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cat == nil {
|
||||||
|
return nil, fmt.Errorf("category not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
cat.Links = append(cat.Links, link)
|
||||||
|
|
||||||
|
return &link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *CategoryManager) DeleteLink(id any) error {
|
||||||
|
var icon string
|
||||||
|
if err := manager.db.QueryRow("SELECT icon FROM links WHERE id = ?", id).Scan(&icon); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := manager.db.Exec("DELETE FROM links WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if icon != "" {
|
||||||
|
if err := os.Remove(filepath.Join("public/", icon)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var WeatherIcons = map[string]string{
|
var WeatherIcons = map[string]string{
|
||||||
@@ -594,11 +550,6 @@ func main() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
router.Get("/", func(c fiber.Ctx) error {
|
router.Get("/", func(c fiber.Ctx) error {
|
||||||
categories, err := app.GetCategories()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
weather := app.WeatherCache.GetWeather()
|
weather := app.WeatherCache.GetWeather()
|
||||||
|
|
||||||
return c.Render("views/index", fiber.Map{
|
return c.Render("views/index", fiber.Map{
|
||||||
@@ -608,7 +559,7 @@ func main() {
|
|||||||
"Desc": weather.WeatherText,
|
"Desc": weather.WeatherText,
|
||||||
"Icon": getWeatherIcon(weather.Icon),
|
"Icon": getWeatherIcon(weather.Icon),
|
||||||
},
|
},
|
||||||
"Categories": categories,
|
"Categories": app.CategoryManager.Categories,
|
||||||
}, "layouts/main")
|
}, "layouts/main")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -665,13 +616,8 @@ func main() {
|
|||||||
return c.Redirect().To("/admin/login")
|
return c.Redirect().To("/admin/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
categories, err := app.GetCategories()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render("views/admin/index", fiber.Map{
|
return c.Render("views/admin/index", fiber.Map{
|
||||||
"Categories": categories,
|
"Categories": app.CategoryManager.Categories,
|
||||||
}, "layouts/main")
|
}, "layouts/main")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -684,27 +630,140 @@ func main() {
|
|||||||
return c.Next()
|
return c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Post("/categories", CreateCategory(app.db))
|
api.Post("/categories", func(c fiber.Ctx) error {
|
||||||
api.Post("/links", CreateLink(app.db))
|
var req struct {
|
||||||
|
Name string `form:"name"`
|
||||||
api.Delete("/links/:id", func(c fiber.Ctx) error {
|
}
|
||||||
id := c.Params("id")
|
if err := c.Bind().MultipartForm(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
var icon string
|
"message": "Failed to parse request",
|
||||||
if err := app.db.QueryRow("SELECT icon FROM links WHERE id = ?", id).Scan(&icon); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := app.db.Exec("DELETE FROM links WHERE id = ?", id)
|
if req.Name == "" {
|
||||||
|
return fmt.Errorf("name and icon are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := c.FormFile("icon")
|
||||||
|
if err != nil || file == nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "Icon is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.Size > 5*1024*1024 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "File size too large. Maximum size is 5MB",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := file.Header.Get("Content-Type")
|
||||||
|
if contentType != "image/svg+xml" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "Only SVGs are supported for category icons!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
||||||
|
|
||||||
|
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"message": "Failed to upload file, please try again!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadFile(file, iconPath, contentType, c)
|
||||||
|
|
||||||
|
category, err := app.CategoryManager.CreateCategory(Category{
|
||||||
|
Name: req.Name,
|
||||||
|
Icon: iconPath,
|
||||||
|
Links: []Link{},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if icon != "" {
|
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||||
if err := os.Remove(filepath.Join("public/", icon)); err != nil {
|
"message": "Category created successfully",
|
||||||
return err
|
"category": category,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Post("/links", func(c fiber.Ctx) error {
|
||||||
|
var req struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Description string `form:"description"`
|
||||||
|
URL string `form:"url"`
|
||||||
|
CategoryID int64 `form:"category_id"`
|
||||||
}
|
}
|
||||||
|
if err := c.Bind().MultipartForm(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "Failed to parse request",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Name == "" || req.URL == "" {
|
||||||
|
return fmt.Errorf("name and url are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := c.FormFile("icon")
|
||||||
|
if err != nil || file == nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "Icon is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.Size > 5*1024*1024 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "File size too large. Maximum size is 5MB",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := file.Header.Get("Content-Type")
|
||||||
|
if !strings.HasPrefix(contentType, "image/") {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"message": "Only image files are allowed",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
||||||
|
|
||||||
|
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to upload file", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"message": "Failed to upload file, please try again!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadFile(file, iconPath, contentType, c)
|
||||||
|
|
||||||
|
link, err := app.CategoryManager.CreateLink(app.CategoryManager.db, Link{
|
||||||
|
CategoryID: req.CategoryID,
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
Icon: iconPath,
|
||||||
|
URL: req.URL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create link", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"message": "Failed to create link",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||||
|
"message": "Link created successfully",
|
||||||
|
"link": link,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Delete("/links/:id", func(c fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
|
||||||
|
app.CategoryManager.DeleteLink(id)
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user