vcf
This commit is contained in:
@@ -40,6 +40,7 @@ func Migrate() {
|
|||||||
&models.ProvServidor{},
|
&models.ProvServidor{},
|
||||||
&models.QrVcard{},
|
&models.QrVcard{},
|
||||||
&models.OssApi{},
|
&models.OssApi{},
|
||||||
|
&models.VcfVcard{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatalf("Error during main migration: %v", err)
|
log.Fatalf("Error during main migration: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VcfVcard struct {
|
||||||
|
gorm.Model
|
||||||
|
Vcf string `json:"vcf" gorm:"column:vcf"`
|
||||||
|
UsuarioID string `json:"usuario" gorm:"column:usuario"`
|
||||||
|
Email string `json:"email" gorm:"column:email"`
|
||||||
|
VcardID string `json:"vcard_id" gorm:"column:vcard_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName asegura que GORM use la tabla 'vcfvcard'
|
||||||
|
func (VcfVcard) TableName() string {
|
||||||
|
return "vcfvcard"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllVcfVcard obtiene todos los registros de VcfVcard con paginación y búsqueda
|
||||||
|
func GetAllVcfVcard(limit, offset int, search string) ([]VcfVcard, int64, error) {
|
||||||
|
var items []VcfVcard
|
||||||
|
var total int64
|
||||||
|
db := app.Http.Database.DB.Model(&VcfVcard{})
|
||||||
|
|
||||||
|
// Si se pasa un término de búsqueda, lo aplicamos en la consulta
|
||||||
|
if search != "" {
|
||||||
|
db = db.Where("email LIKE ?", "%"+search+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contamos el total de registros
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
log.Printf("Error counting VcfVcard: %v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtenemos los registros con paginación
|
||||||
|
if err := db.Order("id DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||||
|
log.Printf("Error retrieving VcfVcard: %v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVcfVcard crea un nuevo registro de VcfVcard en la base de datos
|
||||||
|
func CreateVcfVcard(vcfVcard *VcfVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Create(&vcfVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVcfVcard actualiza un registro de VcfVcard existente
|
||||||
|
func UpdateVcfVcard(vcfVcard *VcfVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Model(&vcfVcard).Updates(vcfVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVcfVcard elimina un registro de VcfVcard de la base de datos
|
||||||
|
func DeleteVcfVcard(vcfVcard *VcfVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Delete(&vcfVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Executable
+119
@@ -0,0 +1,119 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContactData estructura para la vCard extendida
|
||||||
|
type ContactDataVcf struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Org string `json:"org"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
PhotoURL string `json:"photo_url"`
|
||||||
|
Address string `json:"address"` // Ejemplo: ";;Calle 123;Ciudad;Estado;12345;País"
|
||||||
|
Birthday string `json:"birthday"` // Formato: "1990-01-01"
|
||||||
|
Gender string `json:"gender"` // "M", "F", "O", etc.
|
||||||
|
UID string `json:"uid"` // UUID o identificador único
|
||||||
|
Categories string `json:"categories"` // Ejemplo: "Amigos,Trabajo"
|
||||||
|
Note string `json:"note"`
|
||||||
|
IMPP string `json:"impp"` // Ejemplo: "aim:usuario"
|
||||||
|
Timezone string `json:"timezone"` // Ejemplo: "-0500"
|
||||||
|
Language string `json:"language"` // Ejemplo: "es"
|
||||||
|
ColorHex string `json:"color"`
|
||||||
|
BgColor string `json:"bg_color"`
|
||||||
|
Instagram string `json:"instagram"`
|
||||||
|
Facebook string `json:"facebook"`
|
||||||
|
TikTok string `json:"tiktok"`
|
||||||
|
LinkedIn string `json:"linkedin"`
|
||||||
|
VcardID string `json:"vcard_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GenerateVCardVcf genera el contenido de una vCard extendida como archivo .vcf (sin QR)
|
||||||
|
func GenerateVCardVcf(data ContactDataVcf) ([]byte, error) {
|
||||||
|
now := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||||
|
|
||||||
|
var vcardBuilder strings.Builder
|
||||||
|
vcardBuilder.WriteString("BEGIN:VCARD\n")
|
||||||
|
vcardBuilder.WriteString("VERSION:3.0\n")
|
||||||
|
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("N:%s;%s;;;\n", data.LastName, data.FirstName))
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("FN:%s %s\n", data.FirstName, data.LastName))
|
||||||
|
|
||||||
|
if data.Nickname != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("NICKNAME:%s\n", data.Nickname))
|
||||||
|
}
|
||||||
|
if data.Org != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("ORG:%s\n", data.Org))
|
||||||
|
}
|
||||||
|
if data.Title != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("TITLE:%s\n", data.Title))
|
||||||
|
}
|
||||||
|
if data.Role != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("ROLE:%s\n", data.Role))
|
||||||
|
}
|
||||||
|
if data.Phone != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("TEL;TYPE=CELL:%s\n", data.Phone))
|
||||||
|
}
|
||||||
|
if data.Email != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("EMAIL:%s\n", data.Email))
|
||||||
|
}
|
||||||
|
if data.Website != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("URL:%s\n", data.Website))
|
||||||
|
}
|
||||||
|
if data.PhotoURL != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("PHOTO;VALUE=URI:%s\n", data.PhotoURL))
|
||||||
|
}
|
||||||
|
if data.Address != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("ADR:%s\n", data.Address))
|
||||||
|
}
|
||||||
|
if data.Birthday != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("BDAY:%s\n", data.Birthday))
|
||||||
|
}
|
||||||
|
if data.Gender != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("GENDER:%s\n", data.Gender))
|
||||||
|
}
|
||||||
|
if data.UID != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("UID:%s\n", data.UID))
|
||||||
|
}
|
||||||
|
if data.Note != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("NOTE:%s\n", data.Note))
|
||||||
|
}
|
||||||
|
if data.Categories != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("CATEGORIES:%s\n", data.Categories))
|
||||||
|
}
|
||||||
|
if data.IMPP != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("IMPP:%s\n", data.IMPP))
|
||||||
|
}
|
||||||
|
if data.Timezone != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("TZ:%s\n", data.Timezone))
|
||||||
|
}
|
||||||
|
if data.Language != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("LANG:%s\n", data.Language))
|
||||||
|
}
|
||||||
|
if data.Instagram != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("X-INSTAGRAM:%s\n", data.Instagram))
|
||||||
|
}
|
||||||
|
if data.Facebook != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("X-FACEBOOK:%s\n", data.Facebook))
|
||||||
|
}
|
||||||
|
if data.TikTok != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("X-TIKTOK:%s\n", data.TikTok))
|
||||||
|
}
|
||||||
|
if data.LinkedIn != "" {
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("X-LINKEDIN:%s\n", data.LinkedIn))
|
||||||
|
}
|
||||||
|
|
||||||
|
vcardBuilder.WriteString(fmt.Sprintf("REV:%s\n", now))
|
||||||
|
vcardBuilder.WriteString("END:VCARD")
|
||||||
|
|
||||||
|
return []byte(vcardBuilder.String()), nil
|
||||||
|
}
|
||||||
Executable
+107
@@ -0,0 +1,107 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/helpers"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
// Importa el repositorio
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateVcf(c *fiber.Ctx) error {
|
||||||
|
var data services.ContactDataVcf
|
||||||
|
if err := json.Unmarshal(c.Body(), &data); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Datos inválidos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Generar el Vcf
|
||||||
|
vcfContent, err := services.GenerateVCardVcf(data)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo generar el VCF",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
random := helpers.RandomString(4)
|
||||||
|
fileName := fmt.Sprintf("vcf/vcf-%s-%s.vcf", strings.ReplaceAll(data.FirstName, " ", "_"), random)
|
||||||
|
tmpPath := fmt.Sprintf("/tmp/vcf-%s-%s.vcf", strings.ReplaceAll(data.FirstName, " ", "_"), random)
|
||||||
|
|
||||||
|
// 2. Guardar archivo temporal
|
||||||
|
if err := os.WriteFile(tmpPath, vcfContent, 0644); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo guardar el VCF temporalmente",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
// 3. Subir a OSS
|
||||||
|
ossService, err := services.NewOSSServiceFromDB()
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error de conexión con OSS",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ossService.UploadFile(fileName, tmpPath); err != nil {
|
||||||
|
log.Printf("Error subiendo archivo a OSS: %v", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error subiendo archivo a OSS",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. URL del archivo
|
||||||
|
url := fmt.Sprintf("https://%s.%s/%s", ossService.BucketName, ossService.Endpoint, fileName)
|
||||||
|
|
||||||
|
// 5. Buscar si ya existe un registro con ese vcard_id
|
||||||
|
var existing models.VcfVcard
|
||||||
|
db := app.Http.Database.DB
|
||||||
|
|
||||||
|
err = db.Where("vcard_id = ?", data.VcardID).First(&existing).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// No existe, crear nuevo
|
||||||
|
newVcf := models.VcfVcard{
|
||||||
|
Vcf: url,
|
||||||
|
UsuarioID: data.FirstName, // Podrías usar el ID real de usuario si lo tienes
|
||||||
|
Email: data.Email,
|
||||||
|
VcardID: data.VcardID,
|
||||||
|
}
|
||||||
|
if err := models.CreateVcfVcard(&newVcf); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo crear el registro VCF",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Error inesperado de DB
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error buscando VCF existente",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Si ya existe, actualizar
|
||||||
|
existing.Vcf = url
|
||||||
|
existing.Email = data.Email
|
||||||
|
existing.UsuarioID = data.FirstName
|
||||||
|
if err := models.UpdateVcfVcard(&existing); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo actualizar el VCF existente",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"success": true,
|
||||||
|
"url": url,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ func v1AuthRoutes(api fiber.Router) {
|
|||||||
api.Post("/oauth/token", apiControllers.OAuthToken)
|
api.Post("/oauth/token", apiControllers.OAuthToken)
|
||||||
api.Post("/generate-qr-tmp", apiControllers.CreateQrTmp)
|
api.Post("/generate-qr-tmp", apiControllers.CreateQrTmp)
|
||||||
api.Post("/generate-qr", apiControllers.CreateQr)
|
api.Post("/generate-qr", apiControllers.CreateQr)
|
||||||
|
api.Post("/generate-vcf", apiControllers.CreateVcf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user