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
|
toolchain go1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/labstack/echo/v4 v4.10.2
|
github.com/labstack/echo/v4 v4.10.2
|
||||||
golang.org/x/crypto v0.9.0
|
golang.org/x/crypto v0.9.0
|
||||||
|
22
handlers.go
22
handlers.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -257,10 +258,18 @@ func videoHandler(c echo.Context) error {
|
|||||||
return c.Redirect(http.StatusSeeOther, "/videos")
|
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",
|
return c.Render(http.StatusOK, "video.html",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"video": video,
|
"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")
|
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
|
// Migrate the schema
|
||||||
db.AutoMigrate(&Video{}, &User{})
|
db.AutoMigrate(&Video{}, &User{}, &TempURL{})
|
||||||
|
go PeriodicCleanup()
|
||||||
|
|
||||||
// create a user
|
// create a user
|
||||||
// FIXME: only if this user doesn't exist
|
// FIXME: only if this user doesn't exist
|
||||||
@@ -98,7 +99,7 @@ func main() {
|
|||||||
staticGroup := e.Group("/downloads")
|
staticGroup := e.Group("/downloads")
|
||||||
staticGroup.Use(authMiddleware)
|
staticGroup.Use(authMiddleware)
|
||||||
staticGroup.Static("/", getDownloadDir())
|
staticGroup.Static("/", getDownloadDir())
|
||||||
// e.Static("/downloads", getDownloadDir())
|
e.GET("/temp/:token", tempHandler)
|
||||||
|
|
||||||
store.Options = &sessions.Options{
|
store.Options = &sessions.Options{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
|
56
models.go
56
models.go
@@ -1,9 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -27,6 +30,12 @@ type User struct {
|
|||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TempURL struct {
|
||||||
|
Token string `gorm:"uniqueIndex"`
|
||||||
|
FilePath string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type DownloadStatus struct {
|
type DownloadStatus struct {
|
||||||
ID uint
|
ID uint
|
||||||
Progress float64
|
Progress float64
|
||||||
@@ -81,3 +90,50 @@ func (dm *DownloadManager) RemoveStatus(id uint) {
|
|||||||
defer dm.mutex.Unlock()
|
defer dm.mutex.Unlock()
|
||||||
delete(dm.downloads, id)
|
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>
|
<h1>Downloaded Video {{.video.Title}}</h1>
|
||||||
|
|
||||||
<video controls playsinline width="250" preload="metadata">
|
<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>
|
</video>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
Reference in New Issue
Block a user