Temporary public URLs for videos

This commit is contained in:
Carl Pearson
2024-09-08 05:42:49 -06:00
parent 689ce8280e
commit dc6b391e5a
5 changed files with 83 additions and 4 deletions

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.23
toolchain go1.23.0
require (
github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.4.0
github.com/labstack/echo/v4 v4.10.2
golang.org/x/crypto v0.9.0

View File

@@ -9,6 +9,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
@@ -257,10 +258,18 @@ func videoHandler(c echo.Context) error {
return c.Redirect(http.StatusSeeOther, "/videos")
}
downloadDir := getDownloadDir()
tempURL, err := CreateTempURL(filepath.Join(downloadDir, "video", video.VideoFilename))
if err != nil {
return err
}
return c.Render(http.StatusOK, "video.html",
map[string]interface{}{
"video": video,
"downloadDir": getDownloadDir(),
"downloadDir": downloadDir,
"tempURL": fmt.Sprintf("/temp/%s", tempURL.Token),
})
}
@@ -312,3 +321,14 @@ func videoDeleteHandler(c echo.Context) error {
return c.Redirect(http.StatusSeeOther, "/videos")
}
func tempHandler(c echo.Context) error {
token := c.Param("token")
var tempURL TempURL
if err := db.Where("token = ? AND expires_at > ?", token, time.Now()).First(&tempURL).Error; err != nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "Invalid or expired token"})
}
return c.File(tempURL.FilePath)
}

View File

@@ -51,7 +51,8 @@ func main() {
}
// Migrate the schema
db.AutoMigrate(&Video{}, &User{})
db.AutoMigrate(&Video{}, &User{}, &TempURL{})
go PeriodicCleanup()
// create a user
// FIXME: only if this user doesn't exist
@@ -98,7 +99,7 @@ func main() {
staticGroup := e.Group("/downloads")
staticGroup.Use(authMiddleware)
staticGroup.Static("/", getDownloadDir())
// e.Static("/downloads", getDownloadDir())
e.GET("/temp/:token", tempHandler)
store.Options = &sessions.Options{
Path: "/",

View File

@@ -1,9 +1,12 @@
package main
import (
"errors"
"fmt"
"sync"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
@@ -27,6 +30,12 @@ type User struct {
Password string
}
type TempURL struct {
Token string `gorm:"uniqueIndex"`
FilePath string
ExpiresAt time.Time
}
type DownloadStatus struct {
ID uint
Progress float64
@@ -81,3 +90,50 @@ func (dm *DownloadManager) RemoveStatus(id uint) {
defer dm.mutex.Unlock()
delete(dm.downloads, id)
}
func generateToken() string {
uuidObj := uuid.Must(uuid.NewV7())
return uuidObj.String()
}
func CreateTempURL(filePath string) (TempURL, error) {
token := generateToken()
expiration := time.Now().Add(24 * time.Hour)
tempURL := TempURL{
Token: token,
FilePath: filePath,
ExpiresAt: expiration,
}
if err := db.Create(&tempURL).Error; err != nil {
return TempURL{}, errors.New("failed to create temporary URL")
}
return tempURL, nil
}
func cleanupExpiredURLs() {
result := db.Where("expires_at < ?", time.Now()).Delete(&TempURL{})
if result.Error != nil {
fmt.Printf("Error cleaning up expired URLs: %v\n", result.Error)
} else {
fmt.Printf("Cleaned up %d expired temporary URLs\n", result.RowsAffected)
}
}
func vacuumDatabase() {
if err := db.Exec("VACUUM").Error; err != nil {
fmt.Println(err)
}
}
func PeriodicCleanup() {
ticker := time.NewTicker(12 * time.Hour)
for range ticker.C {
fmt.Println("PeriodicCleanup...")
cleanupExpiredURLs()
vacuumDatabase()
}
}

View File

@@ -11,7 +11,8 @@
<h1>Downloaded Video {{.video.Title}}</h1>
<video controls playsinline width="250" preload="metadata">
<source src="/downloads/video/{{.video.VideoFilename}}" type="video/mp4" />
<!-- <source src="/downloads/video/{{.video.VideoFilename}}" type="video/mp4" /> -->
<source src="{{.tempURL}}" type="video/mp4" />
</video>
</body>