qr vcard
This commit is contained in:
@@ -38,6 +38,8 @@ func Migrate() {
|
|||||||
&models.ConxDb{},
|
&models.ConxDb{},
|
||||||
&models.ConxSsh{},
|
&models.ConxSsh{},
|
||||||
&models.ProvServidor{},
|
&models.ProvServidor{},
|
||||||
|
&models.QrVcard{},
|
||||||
|
&models.OssApi{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatalf("Error during main migration: %v", err)
|
log.Fatalf("Error during main migration: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
// RandomString genera una cadena alfanumérica aleatoria de longitud n
|
||||||
|
func RandomString(n int) string {
|
||||||
|
result := make([]byte, n)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = charset[seededRand.Intn(len(charset))]
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ type ConxSsh struct {
|
|||||||
Usuario string `json:"usuario" gorm:"column:usuario"`
|
Usuario string `json:"usuario" gorm:"column:usuario"`
|
||||||
Puerto string `json:"puerto" gorm:"column:puerto"`
|
Puerto string `json:"puerto" gorm:"column:puerto"`
|
||||||
ServidorID uint `json:"servidor_id" gorm:"column:servidor_id;foreignKey:ServidorID"`
|
ServidorID uint `json:"servidor_id" gorm:"column:servidor_id;foreignKey:ServidorID"`
|
||||||
|
Password string `json:"password" gorm:"column:password"`
|
||||||
Servidor Servidor `json:"servidor" gorm:"foreignKey:ServidorID"`
|
Servidor Servidor `json:"servidor" gorm:"foreignKey:ServidorID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OssApi struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string `gorm:"size:100;not null" json:"name"` // Alias o nombre interno
|
||||||
|
Endpoint string `gorm:"not null" json:"endpoint"` // Endpoint del access point
|
||||||
|
AccessKeyID string `gorm:"not null" json:"access_key_id"` // Access Key ID
|
||||||
|
AccessKeySecret string `gorm:"not null" json:"access_key_secret"` // Access Key Secret
|
||||||
|
BucketName string `gorm:"not null" json:"bucket_name"` // Nombre del bucket o access point
|
||||||
|
Region string `gorm:"size:50" json:"region"` // Región, opcional
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"` // Activar o desactivar config
|
||||||
|
Notes string `gorm:"type:text" json:"notes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName asegura que GORM use la tabla 'qrvcard'
|
||||||
|
func (OssApi) TableName() string {
|
||||||
|
return "oss_api"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllQrVcard obtiene todos los registros de QrVcard con paginación y búsqueda
|
||||||
|
func GetAllOssApi(limit, offset int, search string) ([]OssApi, int64, error) {
|
||||||
|
var items []OssApi
|
||||||
|
var total int64
|
||||||
|
db := app.Http.Database.DB.Model(&OssApi{})
|
||||||
|
|
||||||
|
// Si se pasa un término de búsqueda, lo aplicamos en la consulta
|
||||||
|
if search != "" {
|
||||||
|
db = db.Where("name LIKE ?", "%"+search+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contamos el total de registros
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
log.Printf("Error counting OssApi: %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 OssApi: %v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateQrVcard crea un nuevo registro de QrVcard en la base de datos
|
||||||
|
func CreateOssApi(OssApi *OssApi) error {
|
||||||
|
if err := app.Http.Database.DB.Create(&OssApi).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateQrVcard actualiza un registro de QrVcard existente
|
||||||
|
func UpdateOssApi(OssApi *OssApi) error {
|
||||||
|
if err := app.Http.Database.DB.Model(&OssApi).Updates(OssApi).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteQrVcard elimina un registro de QrVcard de la base de datos
|
||||||
|
func DeleteOssApi(OssApi *OssApi) error {
|
||||||
|
if err := app.Http.Database.DB.Delete(&OssApi).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastActiveOssApi obtiene el último registro activo
|
||||||
|
func GetLastActiveOssApi() (*OssApi, error) {
|
||||||
|
var ossConfig OssApi
|
||||||
|
err := app.Http.Database.DB.
|
||||||
|
Where("is_active = ?", true).
|
||||||
|
Order("id DESC").
|
||||||
|
First(&ossConfig).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ossConfig, nil
|
||||||
|
}
|
||||||
+1
-1
@@ -191,7 +191,7 @@ func GetUserByUsuario(usuario string) (*Users, error) {
|
|||||||
fmt.Println(usuario) // Verifica el usuario recibido
|
fmt.Println(usuario) // Verifica el usuario recibido
|
||||||
db := app.Http.Database.DB.Model(&Users{})
|
db := app.Http.Database.DB.Model(&Users{})
|
||||||
// Usamos 1 explícitamente para indicar que el estado debe ser "activo"
|
// Usamos 1 explícitamente para indicar que el estado debe ser "activo"
|
||||||
if err := db.Where("nombre_usuario = ? AND email_verified = ? AND estado = ?", usuario, true, 1).First(&user).Error; err != nil {
|
if err := db.Where("nombre_usuario = ? AND email_verified = ? AND estado = ?", usuario, true, true).First(&user).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QrVcard struct {
|
||||||
|
gorm.Model
|
||||||
|
Qr string `json:"Qr" gorm:"column:qr"`
|
||||||
|
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 'qrvcard'
|
||||||
|
func (QrVcard) TableName() string {
|
||||||
|
return "qrvcard"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllQrVcard obtiene todos los registros de QrVcard con paginación y búsqueda
|
||||||
|
func GetAllQrVcard(limit, offset int, search string) ([]QrVcard, int64, error) {
|
||||||
|
var items []QrVcard
|
||||||
|
var total int64
|
||||||
|
db := app.Http.Database.DB.Model(&QrVcard{})
|
||||||
|
|
||||||
|
// 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 qrvcard: %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 qrvcard: %v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateQrVcard crea un nuevo registro de QrVcard en la base de datos
|
||||||
|
func CreateQrVcard(qrVcard *QrVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Create(&qrVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateQrVcard actualiza un registro de QrVcard existente
|
||||||
|
func UpdateQrVcard(qrVcard *QrVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Model(&qrVcard).Updates(qrVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteQrVcard elimina un registro de QrVcard de la base de datos
|
||||||
|
func DeleteQrVcard(qrVcard *QrVcard) error {
|
||||||
|
if err := app.Http.Database.DB.Delete(&qrVcard).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OSSService struct {
|
||||||
|
Client *oss.Client
|
||||||
|
Bucket *oss.Bucket
|
||||||
|
BucketName string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuración del OSS
|
||||||
|
type OSSConfig struct {
|
||||||
|
Endpoint string
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
BucketName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor del servicio OSS
|
||||||
|
func NewOSSService(cfg OSSConfig) (*OSSService, error) {
|
||||||
|
client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creando cliente OSS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := client.Bucket(cfg.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error obteniendo bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OSSService{
|
||||||
|
Client: client,
|
||||||
|
Bucket: bucket,
|
||||||
|
BucketName: cfg.BucketName,
|
||||||
|
Endpoint: cfg.Endpoint,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subir un archivo
|
||||||
|
func (s *OSSService) UploadFile(objectKey string, filePath string) error {
|
||||||
|
err := s.Bucket.PutObjectFromFile(objectKey, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error subiendo archivo: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Archivo '%s' subido como '%s'", filePath, objectKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listar archivos en el bucket
|
||||||
|
func (s *OSSService) ListFiles() ([]string, error) {
|
||||||
|
objects, err := s.Bucket.ListObjects()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listando archivos: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for _, obj := range objects.Objects {
|
||||||
|
keys = append(keys, obj.Key)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOSSServiceFromDB construye OSSService desde el último registro activo
|
||||||
|
func NewOSSServiceFromDB() (*OSSService, error) {
|
||||||
|
cfg, err := models.GetLastActiveOssApi()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error obteniendo configuración OSS activa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewOSSService(OSSConfig{
|
||||||
|
Endpoint: cfg.Endpoint,
|
||||||
|
AccessKeyID: cfg.AccessKeyID,
|
||||||
|
AccessKeySecret: cfg.AccessKeySecret,
|
||||||
|
BucketName: cfg.BucketName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminar un archivo del bucket
|
||||||
|
func (s *OSSService) DeleteFile(objectKey string) error {
|
||||||
|
err := s.Bucket.DeleteObject(objectKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error eliminando archivo '%s' de OSS: %w", objectKey, err)
|
||||||
|
}
|
||||||
|
log.Printf("Archivo eliminado de OSS: %s", objectKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContactData estructura para la vCard extendida
|
||||||
|
type ContactData 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// hexToRGBA convierte "#rrggbb" a color.RGBA
|
||||||
|
func hexToRGBA(hex string) color.RGBA {
|
||||||
|
var r, g, b uint8
|
||||||
|
fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
|
||||||
|
return color.RGBA{r, g, b, 255}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateVCardQR genera un QR con una vCard extendida incluyendo redes sociales
|
||||||
|
func GenerateVCardQR(data ContactData) ([]byte, error) {
|
||||||
|
now := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||||
|
|
||||||
|
// Usamos strings.Builder para construir dinámicamente la vCard
|
||||||
|
var vcardBuilder strings.Builder
|
||||||
|
vcardBuilder.WriteString("BEGIN:VCARD\n")
|
||||||
|
vcardBuilder.WriteString("VERSION:3.0\n")
|
||||||
|
|
||||||
|
// N y FN son obligatorios para una vCard válida
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregando las redes sociales
|
||||||
|
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")
|
||||||
|
|
||||||
|
vcard := vcardBuilder.String()
|
||||||
|
|
||||||
|
qr, err := qrcode.New(vcard, qrcode.Medium)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
qr.ForegroundColor = hexToRGBA(data.ColorHex)
|
||||||
|
qr.BackgroundColor = hexToRGBA(data.BgColor)
|
||||||
|
|
||||||
|
img := qr.Image(300)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TelegramService struct {
|
||||||
|
BotToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramMessage struct {
|
||||||
|
ChatID interface{} `json:"chat_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
ParseMode string `json:"parse_mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTelegramService() *TelegramService {
|
||||||
|
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
|
||||||
|
if botToken == "" {
|
||||||
|
fmt.Println("Warning: TELEGRAM_BOT_TOKEN not set in environment variables")
|
||||||
|
}
|
||||||
|
return &TelegramService{BotToken: botToken}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TelegramService) SendMessage(chatID interface{}, message string) error {
|
||||||
|
if ts.BotToken == "" {
|
||||||
|
return fmt.Errorf("Telegram bot token is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", ts.BotToken)
|
||||||
|
|
||||||
|
telegramMessage := TelegramMessage{
|
||||||
|
ChatID: chatID,
|
||||||
|
Text: message,
|
||||||
|
ParseMode: "HTML",
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(telegramMessage)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshalling request body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending request to Telegram API: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("received non-OK response from Telegram API: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="mt-4 text-sm text-gray-600 hover:text-green-500 transition-all">
|
<div class="mt-4 text-sm text-gray-600 hover:text-green-500 transition-all">
|
||||||
<a href="#">¿Olvidaste tu contraseña?</a>
|
<a href="/request-password-reset">¿Olvidaste tu contraseña?</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<h1 class="h1">Reset my password!</h1>
|
<h1 class="h1">Reset my password!</h1>
|
||||||
<form action="/do/reset-password" method="POST">
|
<form action="/do/reset-password" method="POST">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<label for="Usuario"><b>Codigo de usuario</b></label>
|
<label for="nombre_usuario"><b>Codigo de usuario</b></label>
|
||||||
<input id="Usuario" type="text" placeholder="Ingrese su usuario" name="email" required>
|
<input id="nombre_usuario" type="text" placeholder="Ingrese su usuario" name="nombre_usuario" required>
|
||||||
|
|
||||||
<button type="submit">Let me reset password!!</button>
|
<button type="submit">Let me reset password!!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end mb-4 gap-2 md:mb-0">
|
<div class="flex items-center justify-end mb-4 gap-2 md:mb-0">
|
||||||
<button @click="addModal = true" class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm">Nuevo</button>
|
<button @click="addModal = true"
|
||||||
|
class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm">Nuevo</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
<th class="py-2 px-3 border-b">Usuario</th>
|
<th class="py-2 px-3 border-b">Usuario</th>
|
||||||
<th class="py-2 px-3 border-b">Puerto</th>
|
<th class="py-2 px-3 border-b">Puerto</th>
|
||||||
<th class="py-2 px-3 border-b">Servidor</th>
|
<th class="py-2 px-3 border-b">Servidor</th>
|
||||||
|
<th class="py-2 px-3 border-b">Entrar</th>
|
||||||
<th class="py-2 px-3 border-b"></th>
|
<th class="py-2 px-3 border-b"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -36,8 +38,23 @@
|
|||||||
|
|
||||||
<td class="py-3 text-xs px-3 border-b" x-text="data.usuario"></td>
|
<td class="py-3 text-xs px-3 border-b" x-text="data.usuario"></td>
|
||||||
<td class="py-3 text-xs px-3 border-b" x-text="data.puerto"></td>
|
<td class="py-3 text-xs px-3 border-b" x-text="data.puerto"></td>
|
||||||
|
|
||||||
|
|
||||||
<td class="py-3 text-xs px-3 border-b">
|
<td class="py-3 text-xs px-3 border-b">
|
||||||
<span x-text="`${servidores.find(serv => serv.ID == data.servidor_id).nombre} - ${servidores.find(serv => serv.ID == data.servidor_id).ip_servidor}`"></span>
|
<span
|
||||||
|
x-text="`${servidores.find(serv => serv.ID == data.servidor_id).nombre} - ${servidores.find(serv => serv.ID == data.servidor_id).ip_servidor}`"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- Botón abrir consola -->
|
||||||
|
<a target="_blank" x-bind:href="`/app/ssh/${data.id}`">
|
||||||
|
<button title="Entrar">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" class="w-5 text-orange-400">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 text-xs px-3 border-b w-24">
|
<td class="py-3 text-xs px-3 border-b w-24">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -98,20 +115,29 @@
|
|||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Usuario:</label>
|
<label class="block mb-2">Usuario:</label>
|
||||||
<input type="text" x-model="selectedItem.usuario" disabled class="border border-gray-300 rounded p-2 w-full" placeholder="Usuario" />
|
<input type="text" x-model="selectedItem.usuario" disabled
|
||||||
|
class="border border-gray-300 rounded p-2 w-full" placeholder="Usuario" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label class="block mb-2">Password:</label>
|
||||||
|
<input type="text" x-model="selectedItem.password" disabled
|
||||||
|
class="border border-gray-300 rounded p-2 w-full" placeholder="Password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Puerto:</label>
|
<label class="block mb-2">Puerto:</label>
|
||||||
<input type="text" x-model="selectedItem.puerto" disabled class="border border-gray-300 rounded p-2 w-full" placeholder="Puerto" />
|
<input type="text" x-model="selectedItem.puerto" disabled
|
||||||
|
class="border border-gray-300 rounded p-2 w-full" placeholder="Puerto" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Servidor:</label>
|
<label class="block mb-2">Servidor:</label>
|
||||||
<select x-model="selectedItem.servidor_id" disabled class="border border-gray-300 rounded p-2 w-full">
|
<select x-model="selectedItem.servidor_id" disabled
|
||||||
|
class="border border-gray-300 rounded p-2 w-full">
|
||||||
<option value="">Selecciona un servidor</option>
|
<option value="">Selecciona un servidor</option>
|
||||||
<template x-for="servidor in servidores" :key="servidor.ID">
|
<template x-for="servidor in servidores" :key="servidor.ID">
|
||||||
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`"></option>
|
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`">
|
||||||
|
</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,11 +157,18 @@
|
|||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Usuario:</label>
|
<label class="block mb-2">Usuario:</label>
|
||||||
<input type="text" x-model="selectedItem.usuario" class="border border-gray-300 rounded p-2 w-full" placeholder="Usuario" />
|
<input type="text" x-model="selectedItem.usuario" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Usuario" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label class="block mb-2">Password:</label>
|
||||||
|
<input type="text" x-model="selectedItem.password" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Puerto:</label>
|
<label class="block mb-2">Puerto:</label>
|
||||||
<input type="text" x-model="selectedItem.puerto" class="border border-gray-300 rounded p-2 w-full" placeholder="Puerto" />
|
<input type="text" x-model="selectedItem.puerto" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Puerto" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
@@ -144,7 +177,8 @@
|
|||||||
<select x-model="selectedItem.servidor_id" class="border border-gray-300 rounded p-2 w-full">
|
<select x-model="selectedItem.servidor_id" class="border border-gray-300 rounded p-2 w-full">
|
||||||
<option value="">Selecciona un servidor</option>
|
<option value="">Selecciona un servidor</option>
|
||||||
<template x-for="servidor in servidores" :key="servidor.ID">
|
<template x-for="servidor in servidores" :key="servidor.ID">
|
||||||
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`"></option>
|
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`">
|
||||||
|
</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,11 +199,18 @@
|
|||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Usuario:</label>
|
<label class="block mb-2">Usuario:</label>
|
||||||
<input type="text" x-model="selectedItem.usuario" class="border border-gray-300 rounded p-2 w-full" placeholder="Usuario" />
|
<input type="text" x-model="selectedItem.usuario" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Usuario" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label class="block mb-2">Password:</label>
|
||||||
|
<input type="text" x-model="selectedItem.password" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label class="block mb-2">Puerto:</label>
|
<label class="block mb-2">Puerto:</label>
|
||||||
<input type="text" x-model="selectedItem.puerto" class="border border-gray-300 rounded p-2 w-full" placeholder="Puerto" />
|
<input type="text" x-model="selectedItem.puerto" class="border border-gray-300 rounded p-2 w-full"
|
||||||
|
placeholder="Puerto" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
|
||||||
@@ -178,7 +219,8 @@
|
|||||||
<select x-model="selectedItem.servidor_id" class="border border-gray-300 rounded p-2 w-full">
|
<select x-model="selectedItem.servidor_id" class="border border-gray-300 rounded p-2 w-full">
|
||||||
<option value="">Selecciona un servidor</option>
|
<option value="">Selecciona un servidor</option>
|
||||||
<template x-for="servidor in servidores" :key="servidor.ID">
|
<template x-for="servidor in servidores" :key="servidor.ID">
|
||||||
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`"></option>
|
<option :value="servidor.ID" x-text="`${servidor.nombre} - ${servidor.ip_servidor}`">
|
||||||
|
</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,6 +291,7 @@
|
|||||||
usuario: data.usuario,
|
usuario: data.usuario,
|
||||||
puerto: data.puerto,
|
puerto: data.puerto,
|
||||||
servidor_id: data.servidor_id,
|
servidor_id: data.servidor_id,
|
||||||
|
passwprd : data.password,
|
||||||
created_at: data.CreatedAt,
|
created_at: data.CreatedAt,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<div>
|
||||||
|
<h2>Consola SSH en Vivo</h2>
|
||||||
|
<pre id="terminal"></pre>
|
||||||
|
<input type="text" id="command" placeholder="Escribe un comando">
|
||||||
|
<button onclick="sendCommand()">Enviar</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let id = "{{.id}}"; // Reemplaza con el ID real
|
||||||
|
const socket = new WebSocket("ws://localhost:8084/app/ws/ssh/" + id);
|
||||||
|
const terminal = document.getElementById("terminal");
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
terminal.textContent = "Conexión SSH establecida...\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
terminal.textContent += event.data;
|
||||||
|
terminal.scrollTop = terminal.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = (error) => {
|
||||||
|
console.error("Error en WebSocket:", error);
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendCommand() {
|
||||||
|
const command = document.getElementById("command").value + "\n";
|
||||||
|
socket.send(command);
|
||||||
|
document.getElementById("command").value = "";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<h1 class="text-2xl font-bold mb-4">Token Vencido</h1>
|
<h1 class="text-2xl font-bold mb-4">Token Vencido</h1>
|
||||||
<div class="justify-between w-full md:flex">
|
<div class="justify-between w-full md:flex">
|
||||||
<p class="mb-4 text-sm">Tu token ha expirado. Por favor, intenta restablecer tu contraseña nuevamente.</p>
|
<p class="mb-4 text-sm">Tu token ha expirado. Por favor, intenta restablecer tu contraseña nuevamente.</p>
|
||||||
<a href="/request-password-reset"><button type="submit"
|
<a href="/"><button type="submit"
|
||||||
class="bg-[#8eb02f] text-white px-4 py-2 rounded flex space-x-2">
|
class="bg-[#8eb02f] text-white px-4 py-2 rounded flex space-x-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="w-5">
|
stroke="currentColor" class="w-5">
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/chai2010/webp"
|
||||||
|
"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 CreateQr(c *fiber.Ctx) error {
|
||||||
|
var data services.ContactData
|
||||||
|
if err := json.Unmarshal(c.Body(), &data); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Datos inválidos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Generar el QR en formato PNG
|
||||||
|
qrBytes, err := services.GenerateVCardQR(data)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo generar el QR",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Decodificar PNG
|
||||||
|
img, err := png.Decode(bytes.NewReader(qrBytes))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error al decodificar QR PNG",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Convertir a WebP
|
||||||
|
var webpBuf bytes.Buffer
|
||||||
|
if err := webp.Encode(&webpBuf, img, nil); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error al convertir QR a WebP",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
random := helpers.RandomString(4)
|
||||||
|
fileName := fmt.Sprintf("qrs/qr-%s-%s.webp", data.FirstName, random)
|
||||||
|
tmpPath := fmt.Sprintf("/tmp/%s.webp", data.FirstName)
|
||||||
|
|
||||||
|
// 4. Guardar archivo temporal
|
||||||
|
if err := os.WriteFile(tmpPath, webpBuf.Bytes(), 0644); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo guardar el QR temporalmente",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
// 5. 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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. URL del archivo
|
||||||
|
url := fmt.Sprintf("https://%s.%s/%s", ossService.BucketName, ossService.Endpoint, fileName)
|
||||||
|
|
||||||
|
// 7. Buscar si ya existe un registro con ese vcard_id
|
||||||
|
var existing models.QrVcard
|
||||||
|
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
|
||||||
|
newQr := models.QrVcard{
|
||||||
|
Qr: url,
|
||||||
|
UsuarioID: data.FirstName,
|
||||||
|
Email: data.Email,
|
||||||
|
VcardID: data.VcardID,
|
||||||
|
}
|
||||||
|
if err := models.CreateQrVcard(&newQr); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error al guardar nuevo QR en la base de datos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otro error
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error al consultar la base de datos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ya existe, actualizar
|
||||||
|
existing.Qr = url
|
||||||
|
existing.UsuarioID = data.FirstName
|
||||||
|
existing.Email = data.Email
|
||||||
|
|
||||||
|
if err := models.UpdateQrVcard(&existing); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error al actualizar el QR en la base de datos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Retornar la URL
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"url": url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateQrTmp(c *fiber.Ctx) error {
|
||||||
|
var data services.ContactData
|
||||||
|
if err := json.Unmarshal(c.Body(), &data); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Datos inválidos",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
qrBytes, err := services.GenerateVCardQR(data)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo generar el QR",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodificar PNG
|
||||||
|
img, err := png.Decode(bytes.NewReader(qrBytes))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo procesar la imagen",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codificar a WebP
|
||||||
|
var webpBuffer bytes.Buffer
|
||||||
|
if err := webp.Encode(&webpBuffer, img, &webp.Options{Lossless: true}); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "No se pudo convertir a WebP",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Type("webp")
|
||||||
|
return c.Send(webpBuffer.Bytes())
|
||||||
|
}
|
||||||
@@ -80,52 +80,29 @@ func ReenvioEmail(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// Función para manejar la solicitud de restablecimiento de contraseña
|
// Función para manejar la solicitud de restablecimiento de contraseña
|
||||||
func RequestPasswordResetPost(c *fiber.Ctx) error {
|
func RequestPasswordResetPost(c *fiber.Ctx) error {
|
||||||
// Crear una estructura para mapear los datos entrantes
|
usuario := c.FormValue("nombre_usuario")
|
||||||
type Request struct {
|
|
||||||
NombreUsuario string `json:"nombre_usuario"` // Mapeo con la clave del JSON
|
|
||||||
}
|
|
||||||
|
|
||||||
var req Request
|
|
||||||
|
|
||||||
// Parsear el cuerpo JSON de la solicitud
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"success": false,
|
|
||||||
"message": "Invalid JSON format",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtener el usuario del JSON parseado
|
|
||||||
usuario := req.NombreUsuario
|
|
||||||
fmt.Println("Usuario recibido:", usuario)
|
fmt.Println("Usuario recibido:", usuario)
|
||||||
|
|
||||||
// Intentar obtener el usuario por nombre de usuario
|
|
||||||
user, err := models.GetUserByUsuario(usuario)
|
user, err := models.GetUserByUsuario(usuario)
|
||||||
|
|
||||||
if err != nil || user == nil {
|
if err != nil || user == nil {
|
||||||
// Devolver respuesta de error en formato JSON
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "Usuario inactivo o no existente",
|
"message": "Usuario inactivo o no existente",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener el email del usuario
|
|
||||||
usuario_email := user.Email
|
usuario_email := user.Email
|
||||||
|
|
||||||
// Imprimir el email en la consola
|
|
||||||
log.Println("Sending password reset email to:", usuario_email)
|
log.Println("Sending password reset email to:", usuario_email)
|
||||||
|
|
||||||
// Enviar el correo de restablecimiento de contraseña de forma asíncrona
|
|
||||||
go services.SendPasswordResetEmail(usuario_email, app.Http.Server.Url)
|
go services.SendPasswordResetEmail(usuario_email, app.Http.Server.Url)
|
||||||
|
|
||||||
// Devolver respuesta de éxito en formato JSON
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "We've sent an email to reset your password to your registered email address",
|
"message": "We've sent an email to reset your password to your registered email address",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// todo: Generar contrasenas aleatorias /do/generate-password
|
// todo: Generar contrasenas aleatorias /do/generate-password
|
||||||
func generatePassword(length int) (string, error) {
|
func generatePassword(length int) (string, error) {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&"
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&"
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TelegramController struct {
|
||||||
|
Service *services.TelegramService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTelegramController(service *services.TelegramService) *TelegramController {
|
||||||
|
return &TelegramController{Service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TelegramController) SendMessage(chatID interface{}, message string) error {
|
||||||
|
if chatID == "" || message == "" {
|
||||||
|
return fmt.Errorf("chat_id and message are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tc.Service.SendMessage(chatID, message)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -261,12 +261,27 @@ func AuthAdmin(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AuthApi() func(*fiber.Ctx) error {
|
func AuthApi() func(*fiber.Ctx) error {
|
||||||
return Authenticate(AuthConfig{
|
return func(c *fiber.Ctx) error {
|
||||||
SigningKey: []byte(app.Http.Token.ApiJwtSecret),
|
// Excluir rutas públicas
|
||||||
TokenLookup: "header:Verify-Rest-Token",
|
if c.Path() == "/api/v1/oauth/token" {
|
||||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
|
return c.Next()
|
||||||
auth.Logout(ctx)
|
}
|
||||||
return ctx.Status(401).JSON("Invalid Attempt")
|
|
||||||
},
|
// Obtener el token de la cookie "Verify-Rest-Token"
|
||||||
})
|
token := c.Cookies("Verify-Rest-Token")
|
||||||
|
if token == "" {
|
||||||
|
// Si no hay token, regresar un error
|
||||||
|
return c.Status(401).JSON("Token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuar con la verificación JWT para otras rutas
|
||||||
|
return Authenticate(AuthConfig{
|
||||||
|
SigningKey: []byte(app.Http.Token.ApiJwtSecret),
|
||||||
|
TokenLookup: "cookie:Verify-Rest-Token", // Ahora se busca en la cookie
|
||||||
|
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
|
||||||
|
auth.Logout(ctx)
|
||||||
|
return ctx.Status(401).JSON("Invalid Attempt")
|
||||||
|
},
|
||||||
|
})(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ func ApiRoutes(api fiber.Router) {
|
|||||||
|
|
||||||
func v1AuthRoutes(api fiber.Router) {
|
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", apiControllers.CreateQr)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func v1Routes(api fiber.Router) {
|
func v1Routes(api fiber.Router) {
|
||||||
|
|||||||
+6
-6
@@ -8,10 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func WebAuthRoutes(App fiber.Router) {
|
func WebAuthRoutes(App fiber.Router) {
|
||||||
//App.Get("/login",
|
App.Get("/login",
|
||||||
// middlewares.RedirectToHomePageOnLogin,
|
middlewares.RedirectToHomePageOnLogin,
|
||||||
// controllers.LoginGet,
|
controllers.LoginGet,
|
||||||
//)
|
)
|
||||||
App.Post("/do/login",
|
App.Post("/do/login",
|
||||||
middlewares.ValidateLoginPost,
|
middlewares.ValidateLoginPost,
|
||||||
controllers.LoginPost,
|
controllers.LoginPost,
|
||||||
@@ -39,10 +39,10 @@ func WebAuthRoutes(App fiber.Router) {
|
|||||||
)
|
)
|
||||||
// Generar contraseñas aleatorias /do/generate-password
|
// Generar contraseñas aleatorias /do/generate-password
|
||||||
App.Post("/do/generate-password",
|
App.Post("/do/generate-password",
|
||||||
controllers.GeneratePasswordPost,
|
controllers.GeneratePasswordPost,
|
||||||
)
|
)
|
||||||
|
|
||||||
//App.Get("/request-password-reset", middlewares.RedirectToHomePageOnLogin, controllers.RequestPasswordReset)
|
App.Get("/request-password-reset", middlewares.RedirectToHomePageOnLogin, controllers.RequestPasswordReset)
|
||||||
App.Post("/do/password-reset/:token",
|
App.Post("/do/password-reset/:token",
|
||||||
middlewares.RedirectToHomePageOnLogin,
|
middlewares.RedirectToHomePageOnLogin,
|
||||||
middlewares.ValidatePasswordResetPost,
|
middlewares.ValidatePasswordResetPost,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func RutasPublicas(web fiber.Router) {
|
func RutasPublicas(web fiber.Router) {
|
||||||
//web.Get("/", controllers.Landing)
|
//web.Get("/", controllers.Landing)
|
||||||
web.Get("/index", controllers.Index)
|
web.Get("/index", controllers.Index)
|
||||||
web.Get("/login", controllers.Login)
|
//web.Get("/login", controllers.Login)
|
||||||
web.Get("/ping", Pong)
|
web.Get("/ping", Pong)
|
||||||
web.Get("/all-routes", AllRoutes)
|
web.Get("/all-routes", AllRoutes)
|
||||||
web.Get("/do/verify-email", middlewares.ValidateConfirmToken, controllers.VerifyRegisteredEmail)
|
web.Get("/do/verify-email", middlewares.ValidateConfirmToken, controllers.VerifyRegisteredEmail)
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadRoutes(app *fiber.App) {
|
func LoadRoutes(app *fiber.App) {
|
||||||
// Grupo de rutas de la API con autenticación
|
// Grupo de rutas de la API con autenticación
|
||||||
//api := app.Group("/api").Use(middlewares.AuthApi())
|
api := app.Group("/api").Use(middlewares.AuthApi())
|
||||||
//ApiRoutes(api)
|
ApiRoutes(api)
|
||||||
|
|
||||||
// Grupo de rutas web (sin autenticación)
|
// Grupo de rutas web (sin autenticación)
|
||||||
web := app.Group("")
|
web := app.Group("")
|
||||||
|
|||||||
+19
-14
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/contrib/websocket"
|
||||||
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
|
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
|
||||||
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
|
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
|
||||||
)
|
)
|
||||||
@@ -55,28 +56,28 @@ func UserRoutes(app fiber.Router) {
|
|||||||
|
|
||||||
// Rutas de conexiones ssh
|
// Rutas de conexiones ssh
|
||||||
protected.Get("/conexion_ssh", middlewares.MenuMiddleware, controllers.ConxSsh) // Renderizar la vista
|
protected.Get("/conexion_ssh", middlewares.MenuMiddleware, controllers.ConxSsh) // Renderizar la vista
|
||||||
protected.Get("/loadconexionssh", controllers.GetConxSsh) // Obtener
|
protected.Get("/loadconexionssh", controllers.GetConxSsh) // Obtener
|
||||||
protected.Post("/conexionssh", controllers.CreateConxSsh) // Crear
|
protected.Post("/conexionssh", controllers.CreateConxSsh) // Crear
|
||||||
protected.Put("/conexionssh/:id", controllers.UpdateConxSsh) // Actualizar
|
protected.Put("/conexionssh/:id", controllers.UpdateConxSsh) // Actualizar
|
||||||
protected.Delete("/conexionssh/:id", controllers.DeleteConxSsh) // Eliminar
|
protected.Delete("/conexionssh/:id", controllers.DeleteConxSsh) // Eliminar
|
||||||
|
|
||||||
// Rutas de conexiones db
|
// Rutas de conexiones db
|
||||||
protected.Get("/conexion_db", middlewares.MenuMiddleware, controllers.ConxDb) // Renderizar la vista
|
protected.Get("/conexion_db", middlewares.MenuMiddleware, controllers.ConxDb) // Renderizar la vista
|
||||||
protected.Get("/loadconexiondb", controllers.GetConxDb) // Obtener
|
protected.Get("/loadconexiondb", controllers.GetConxDb) // Obtener
|
||||||
protected.Post("/conexiondb", controllers.CreateConxDb) // Crear
|
protected.Post("/conexiondb", controllers.CreateConxDb) // Crear
|
||||||
protected.Put("/conexiondb/:id", controllers.UpdateConxDb) // Actualizar
|
protected.Put("/conexiondb/:id", controllers.UpdateConxDb) // Actualizar
|
||||||
protected.Delete("/conexiondb/:id", controllers.DeleteConxDb) // Eliminar
|
protected.Delete("/conexiondb/:id", controllers.DeleteConxDb) // Eliminar
|
||||||
|
|
||||||
// Rutas de servidor
|
// Rutas de servidor
|
||||||
protected.Get("/servidor", middlewares.MenuMiddleware, controllers.Servidor)
|
protected.Get("/servidor", middlewares.MenuMiddleware, controllers.Servidor)
|
||||||
protected.Get("/loadservidorselect", controllers.GetServidor)
|
protected.Get("/loadservidorselect", controllers.GetServidor)
|
||||||
protected.Get("/loadservidor", controllers.GetServidor)
|
protected.Get("/loadservidor", controllers.GetServidor)
|
||||||
protected.Post("/servidor", controllers.CreateServidor)
|
protected.Post("/servidor", controllers.CreateServidor)
|
||||||
protected.Put("/servidor/:id", controllers.UpdateServidor)
|
protected.Put("/servidor/:id", controllers.UpdateServidor)
|
||||||
protected.Delete("/servidor/:id", controllers.DeleteServidor)
|
protected.Delete("/servidor/:id", controllers.DeleteServidor)
|
||||||
|
|
||||||
// Rutas de proveedores de servidor
|
// Rutas de proveedores de servidor
|
||||||
protected.Get("/prov_servidor", middlewares.MenuMiddleware, controllers.ProvServidor)
|
protected.Get("/prov_servidor", middlewares.MenuMiddleware, controllers.ProvServidor)
|
||||||
protected.Get("/loadprovservidor", controllers.GetProvServidor)
|
protected.Get("/loadprovservidor", controllers.GetProvServidor)
|
||||||
protected.Post("/provservidor", controllers.CreateProvServidor)
|
protected.Post("/provservidor", controllers.CreateProvServidor)
|
||||||
protected.Put("/provservidor/:id", controllers.UpdateProvServidor)
|
protected.Put("/provservidor/:id", controllers.UpdateProvServidor)
|
||||||
@@ -96,4 +97,8 @@ func UserRoutes(app fiber.Router) {
|
|||||||
protected.Put("/tipodb/:id", controllers.UpdateTipoDb)
|
protected.Put("/tipodb/:id", controllers.UpdateTipoDb)
|
||||||
protected.Delete("/tipodb/:id", controllers.DeleteTipoDb)
|
protected.Delete("/tipodb/:id", controllers.DeleteTipoDb)
|
||||||
|
|
||||||
|
protected.Get("/ws/ssh/:id", websocket.New(controllers.WSConnectSSH))
|
||||||
|
protected.Get("/ssh/:id", controllers.ConnectSSHview)
|
||||||
|
protected.Get("/terminal", controllers.Terminal)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user