Temporary public URLs for videos
This commit is contained in:
1
go.mod
1
go.mod
@@ -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
|
||||
|
22
handlers.go
22
handlers.go
@@ -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)
|
||||
}
|
||||
|
5
main.go
5
main.go
@@ -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: "/",
|
||||
|
56
models.go
56
models.go
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user