330 lines
8.7 KiB
Go
330 lines
8.7 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"app_compartir/internal/models"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
type Database struct {
|
|
*sql.DB
|
|
}
|
|
|
|
func Init(dbPath, uploadDir string) (*Database, error) {
|
|
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
|
|
return nil, fmt.Errorf("error creando directorio de bd: %w", err)
|
|
}
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("error creando directorio de uploads: %w", err)
|
|
}
|
|
|
|
conn, err := sql.Open("sqlite", dbPath+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(ON)")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error abriendo sqlite: %w", err)
|
|
}
|
|
|
|
d := &Database{DB: conn}
|
|
if err := d.migrate(); err != nil {
|
|
return nil, fmt.Errorf("error ejecutando migraciones: %w", err)
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
func (d *Database) migrate() error {
|
|
schema := `
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
provider TEXT NOT NULL,
|
|
provider_id TEXT NOT NULL,
|
|
email TEXT,
|
|
name TEXT,
|
|
avatar_url TEXT,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
token TEXT PRIMARY KEY,
|
|
user_id TEXT,
|
|
is_guest BOOLEAN NOT NULL DEFAULT 1,
|
|
guest_id TEXT,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at DATETIME NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS shares (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT,
|
|
user_display TEXT NOT NULL DEFAULT 'Invitado',
|
|
is_guest BOOLEAN NOT NULL DEFAULT 1,
|
|
type TEXT NOT NULL DEFAULT 'file',
|
|
title TEXT NOT NULL,
|
|
stored_path TEXT,
|
|
file_size INTEGER NOT NULL DEFAULT 0,
|
|
mime_type TEXT NOT NULL DEFAULT 'text/plain',
|
|
text_content TEXT,
|
|
views_count INTEGER NOT NULL DEFAULT 0,
|
|
downloads_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at DATETIME,
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_shares_expired_guest ON shares(is_guest, expires_at);
|
|
CREATE INDEX IF NOT EXISTS idx_shares_user_id ON shares(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
|
`
|
|
_, err := d.Exec(schema)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) CreateShare(s *models.Share) error {
|
|
query := `
|
|
INSERT INTO shares (
|
|
id, user_id, user_display, is_guest, type, title, stored_path,
|
|
file_size, mime_type, text_content, views_count, downloads_count,
|
|
created_at, expires_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
_, err := d.Exec(query,
|
|
s.ID, s.UserID, s.UserDisplay, s.IsGuest, s.Type, s.Title, s.StoredPath,
|
|
s.FileSize, s.MimeType, s.TextContent, s.ViewsCount, s.DownloadsCount,
|
|
s.CreatedAt, s.ExpiresAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) GetShare(id string) (*models.Share, error) {
|
|
query := `
|
|
SELECT id, user_id, user_display, is_guest, type, title, stored_path,
|
|
file_size, mime_type, text_content, views_count, downloads_count,
|
|
created_at, expires_at
|
|
FROM shares WHERE id = ?`
|
|
|
|
row := d.QueryRow(query, id)
|
|
s := &models.Share{}
|
|
var expiresAt sql.NullTime
|
|
var textContent, storedPath sql.NullString
|
|
var userID sql.NullString
|
|
|
|
err := row.Scan(
|
|
&s.ID, &userID, &s.UserDisplay, &s.IsGuest, &s.Type, &s.Title, &storedPath,
|
|
&s.FileSize, &s.MimeType, &textContent, &s.ViewsCount, &s.DownloadsCount,
|
|
&s.CreatedAt, &expiresAt,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if userID.Valid {
|
|
s.UserID = &userID.String
|
|
}
|
|
if storedPath.Valid {
|
|
s.StoredPath = storedPath.String
|
|
}
|
|
if textContent.Valid {
|
|
s.TextContent = textContent.String
|
|
}
|
|
if expiresAt.Valid {
|
|
s.ExpiresAt = &expiresAt.Time
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (d *Database) IncrementViews(id string) error {
|
|
_, err := d.Exec("UPDATE shares SET views_count = views_count + 1 WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) IncrementDownloads(id string) error {
|
|
_, err := d.Exec("UPDATE shares SET downloads_count = downloads_count + 1 WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) GetUserShares(userID string) ([]*models.Share, error) {
|
|
query := `
|
|
SELECT id, user_id, user_display, is_guest, type, title, stored_path,
|
|
file_size, mime_type, text_content, views_count, downloads_count,
|
|
created_at, expires_at
|
|
FROM shares WHERE user_id = ? ORDER BY created_at DESC`
|
|
|
|
rows, err := d.Query(query, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var list []*models.Share
|
|
for rows.Next() {
|
|
s := &models.Share{}
|
|
var expiresAt sql.NullTime
|
|
var textContent, storedPath sql.NullString
|
|
var uID sql.NullString
|
|
|
|
if err := rows.Scan(
|
|
&s.ID, &uID, &s.UserDisplay, &s.IsGuest, &s.Type, &s.Title, &storedPath,
|
|
&s.FileSize, &s.MimeType, &textContent, &s.ViewsCount, &s.DownloadsCount,
|
|
&s.CreatedAt, &expiresAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if uID.Valid {
|
|
s.UserID = &uID.String
|
|
}
|
|
if storedPath.Valid {
|
|
s.StoredPath = storedPath.String
|
|
}
|
|
if textContent.Valid {
|
|
s.TextContent = textContent.String
|
|
}
|
|
if expiresAt.Valid {
|
|
s.ExpiresAt = &expiresAt.Time
|
|
}
|
|
list = append(list, s)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func (d *Database) DeleteShare(id string, userID *string) error {
|
|
if userID != nil {
|
|
_, err := d.Exec("DELETE FROM shares WHERE id = ? AND user_id = ?", id, *userID)
|
|
return err
|
|
}
|
|
_, err := d.Exec("DELETE FROM shares WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
// GetExpiredGuestShares obtiene SOLO los shares marcados como guest cuya fecha de expiración ya pasó.
|
|
// Los usuarios registrados (is_guest = 0) NUNCA se seleccionan aquí.
|
|
func (d *Database) GetExpiredGuestShares() ([]*models.Share, error) {
|
|
query := `
|
|
SELECT id, stored_path
|
|
FROM shares
|
|
WHERE is_guest = 1 AND expires_at IS NOT NULL AND expires_at <= CURRENT_TIMESTAMP`
|
|
|
|
rows, err := d.Query(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var list []*models.Share
|
|
for rows.Next() {
|
|
s := &models.Share{}
|
|
var storedPath sql.NullString
|
|
if err := rows.Scan(&s.ID, &storedPath); err != nil {
|
|
return nil, err
|
|
}
|
|
if storedPath.Valid {
|
|
s.StoredPath = storedPath.String
|
|
}
|
|
list = append(list, s)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func (d *Database) PurgeShare(id string) error {
|
|
_, err := d.Exec("DELETE FROM shares WHERE id = ? AND is_guest = 1", id)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) UpsertUser(u *models.User) error {
|
|
query := `
|
|
INSERT INTO users (id, provider, provider_id, email, name, avatar_url, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
email = excluded.email,
|
|
name = excluded.name,
|
|
avatar_url = excluded.avatar_url`
|
|
|
|
_, err := d.Exec(query, u.ID, u.Provider, u.ProviderID, u.Email, u.Name, u.AvatarURL, u.CreatedAt)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) GetUser(id string) (*models.User, error) {
|
|
query := `SELECT id, provider, provider_id, email, name, avatar_url, created_at FROM users WHERE id = ?`
|
|
row := d.QueryRow(query, id)
|
|
|
|
u := &models.User{}
|
|
var email, name, avatar sql.NullString
|
|
if err := row.Scan(&u.ID, &u.Provider, &u.ProviderID, &email, &name, &avatar, &u.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
if email.Valid {
|
|
u.Email = email.String
|
|
}
|
|
if name.Valid {
|
|
u.Name = name.String
|
|
}
|
|
if avatar.Valid {
|
|
u.AvatarURL = avatar.String
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
func (d *Database) CreateSession(s *models.Session) error {
|
|
query := `
|
|
INSERT INTO sessions (token, user_id, is_guest, guest_id, created_at, expires_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
_, err := d.Exec(query, s.Token, s.UserID, s.IsGuest, s.GuestID, s.CreatedAt, s.ExpiresAt)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) GetSession(token string) (*models.Session, error) {
|
|
query := `
|
|
SELECT s.token, s.user_id, s.is_guest, s.guest_id, s.created_at, s.expires_at,
|
|
u.id, u.provider, u.provider_id, u.email, u.name, u.avatar_url, u.created_at
|
|
FROM sessions s
|
|
LEFT JOIN users u ON s.user_id = u.id
|
|
WHERE s.token = ? AND s.expires_at > CURRENT_TIMESTAMP`
|
|
|
|
row := d.QueryRow(query, token)
|
|
s := &models.Session{}
|
|
var userID sql.NullString
|
|
var uID, provider, provID, email, name, avatar sql.NullString
|
|
var uCreated sql.NullTime
|
|
|
|
err := row.Scan(
|
|
&s.Token, &userID, &s.IsGuest, &s.GuestID, &s.CreatedAt, &s.ExpiresAt,
|
|
&uID, &provider, &provID, &email, &name, &avatar, &uCreated,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if userID.Valid {
|
|
s.UserID = &userID.String
|
|
}
|
|
|
|
if uID.Valid {
|
|
s.User = &models.User{
|
|
ID: uID.String,
|
|
Provider: provider.String,
|
|
ProviderID: provID.String,
|
|
Email: email.String,
|
|
Name: name.String,
|
|
AvatarURL: avatar.String,
|
|
CreatedAt: uCreated.Time,
|
|
}
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (d *Database) DeleteSession(token string) error {
|
|
_, err := d.Exec("DELETE FROM sessions WHERE token = ?", token)
|
|
return err
|
|
}
|
|
|
|
func (d *Database) PurgeExpiredSessions() error {
|
|
_, err := d.Exec("DELETE FROM sessions WHERE expires_at <= CURRENT_TIMESTAMP")
|
|
return err
|
|
}
|