Add 404 page, add Deleted page, fix errors
This commit is contained in:
@@ -30,6 +30,9 @@ func uploadHandler(c *gin.Context) {
|
||||
id := uuid.New().String()
|
||||
delID := uuid.New().String()
|
||||
cleanName := filepath.Base(header.Filename)
|
||||
if len(cleanName) > 255 {
|
||||
cleanName = cleanName[:255]
|
||||
}
|
||||
|
||||
folderPath := filepath.Join("uploads", id)
|
||||
os.MkdirAll(folderPath, 0755)
|
||||
@@ -65,6 +68,7 @@ func uploadHandler(c *gin.Context) {
|
||||
Filename: cleanName,
|
||||
Path: storagePath,
|
||||
ExpiresAt: expiry,
|
||||
Size: written,
|
||||
DeleteAfterDownload: c.PostForm("once") == "true",
|
||||
}
|
||||
|
||||
@@ -79,14 +83,21 @@ func uploadHandler(c *gin.Context) {
|
||||
|
||||
func downloadHandler(c *gin.Context) {
|
||||
var record FileRecord
|
||||
if err := db.First(&record, "id = ? AND deleted = ?", c.Param("id"), false).Error; err != nil {
|
||||
c.String(404, "File not found or expired")
|
||||
var err = db.First(&record, "id = ? AND deleted = ?", c.Param("id"), false).Error
|
||||
if err != nil {
|
||||
c.HTML(200, "fileNotFound.html", gin.H{
|
||||
"message": "File not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(record.ExpiresAt) {
|
||||
performDeletion(&record)
|
||||
c.String(410, "File has expired")
|
||||
|
||||
//c.String(410, "File has expired")
|
||||
c.HTML(404, "fileNotFound.html", gin.H{
|
||||
"message": "File not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,7 +118,8 @@ func deleteHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
performDeletion(&record)
|
||||
c.JSON(200, gin.H{"message": "Deleted successfully"})
|
||||
//c.JSON(200, gin.H{"message": "Deleted successfully"})
|
||||
c.HTML(200, "deleted.html", nil)
|
||||
}
|
||||
|
||||
func loginHandler(c *gin.Context) {
|
||||
@@ -145,3 +157,30 @@ func loginHandler(c *gin.Context) {
|
||||
|
||||
c.Redirect(302, "/admin")
|
||||
}
|
||||
|
||||
func adminIndexHandler(c *gin.Context) {
|
||||
var files []FileRecord
|
||||
|
||||
// Pagination parameters
|
||||
perPage := 20
|
||||
page := 1
|
||||
if p := c.Query("page"); p != "" {
|
||||
if v, err := strconv.Atoi(p); err == nil && v > 0 {
|
||||
page = v
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Model(&FileRecord{}).Count(&total)
|
||||
|
||||
totalPages := int((total + int64(perPage) - 1) / int64(perPage)) // ceiling division
|
||||
|
||||
offset := (page - 1) * perPage
|
||||
db.Order("created_at desc").Limit(perPage).Offset(offset).Find(&files)
|
||||
|
||||
c.HTML(200, "admin.html", gin.H{
|
||||
"Files": files,
|
||||
"Page": page,
|
||||
"TotalPages": totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
41
src/main.go
41
src/main.go
@@ -6,7 +6,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -34,8 +33,9 @@ func main() {
|
||||
router := gin.Default()
|
||||
router.MaxMultipartMemory = 10 << 30
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"humanSize": humanSize,
|
||||
})
|
||||
router.LoadHTMLGlob("templates/*")
|
||||
var staticPath = gin.Dir("./static", false)
|
||||
@@ -43,6 +43,9 @@ func main() {
|
||||
|
||||
router.StaticFS("/static", staticPath)
|
||||
|
||||
router.NoRoute(func(c *gin.Context) {
|
||||
c.HTML(404, "error.html", nil)
|
||||
})
|
||||
// Public Routes
|
||||
router.GET("/", func(c *gin.Context) { c.HTML(200, "index.html", nil) })
|
||||
router.GET("/f/:id", downloadHandler)
|
||||
@@ -53,34 +56,16 @@ func main() {
|
||||
admin := router.Group("/admin")
|
||||
admin.Use(authMiddleware())
|
||||
|
||||
admin.GET("/", func(c *gin.Context) {
|
||||
var files []FileRecord
|
||||
|
||||
// Pagination parameters
|
||||
perPage := 20
|
||||
page := 1
|
||||
if p := c.Query("page"); p != "" {
|
||||
if v, err := strconv.Atoi(p); err == nil && v > 0 {
|
||||
page = v
|
||||
}
|
||||
admin.GET("/", adminIndexHandler)
|
||||
admin.GET("/delete/fr/:id", func(c *gin.Context) {
|
||||
var record FileRecord
|
||||
if err := db.First(&record, "id = ?", c.Param("id")).Error; err == nil {
|
||||
performActualDeletion(&record)
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Model(&FileRecord{}).Count(&total)
|
||||
|
||||
totalPages := int((total + int64(perPage) - 1) / int64(perPage)) // ceiling division
|
||||
|
||||
offset := (page - 1) * perPage
|
||||
db.Order("created_at desc").Limit(perPage).Offset(offset).Find(&files)
|
||||
|
||||
c.HTML(200, "admin.html", gin.H{
|
||||
"Files": files,
|
||||
"Page": page,
|
||||
"TotalPages": totalPages,
|
||||
})
|
||||
c.Redirect(301, "/admin")
|
||||
})
|
||||
|
||||
admin.POST("/delete/:id", func(c *gin.Context) {
|
||||
admin.GET("/delete/:id", func(c *gin.Context) {
|
||||
var record FileRecord
|
||||
if err := db.First(&record, "id = ?", c.Param("id")).Error; err == nil {
|
||||
performDeletion(&record)
|
||||
|
||||
@@ -9,6 +9,7 @@ type FileRecord struct {
|
||||
Path string `json:"-"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
DeleteAfterDownload bool `json:"delete_after_download"`
|
||||
Size int64 `json:"size"`
|
||||
DownloadCount int `json:"download_count"`
|
||||
Deleted bool `json:"deleted"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
36
src/utils.go
36
src/utils.go
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -12,6 +15,19 @@ func performDeletion(r *FileRecord) {
|
||||
db.Save(r)
|
||||
}
|
||||
|
||||
func performActualDeletion(r *FileRecord) {
|
||||
|
||||
folderPath := filepath.Join("uploads", r.ID)
|
||||
err := os.RemoveAll(folderPath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error deleting file:", err)
|
||||
return
|
||||
}
|
||||
db.Delete(r)
|
||||
fmt.Println("Deleted file:", r.Filename)
|
||||
}
|
||||
|
||||
func cleanupWorker() {
|
||||
for {
|
||||
time.Sleep(10 * time.Minute)
|
||||
@@ -38,7 +54,7 @@ func authMiddleware() gin.HandlerFunc {
|
||||
|
||||
tokenStr, err := c.Cookie("auth")
|
||||
if err != nil {
|
||||
c.Redirect(302, "/")
|
||||
c.Redirect(302, "/login")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -48,7 +64,7 @@ func authMiddleware() gin.HandlerFunc {
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
c.Redirect(302, "/")
|
||||
c.Redirect(302, "/login")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -56,3 +72,19 @@ func authMiddleware() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func humanSize(size int64) string {
|
||||
const unit = 1024
|
||||
if size < unit {
|
||||
return fmt.Sprintf("%d B", size)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := size / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB",
|
||||
float64(size)/float64(div),
|
||||
"KMGTPE"[exp],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Hits</th>
|
||||
@@ -53,6 +54,7 @@
|
||||
<td class="font-mono">
|
||||
<a href="/admin/download/{{.ID}}" target="_blank">{{.Filename}}</a>
|
||||
</td>
|
||||
<td>{{humanSize .Size}}</td>
|
||||
<td>{{.CreatedAt.Format "Jan 02, 2006 15:04"}}</td>
|
||||
<td>{{.ExpiresAt.Format "Jan 02, 2006 15:04"}}</td>
|
||||
<td>{{.DownloadCount}}</td>
|
||||
@@ -72,10 +74,13 @@
|
||||
</td>
|
||||
<td>
|
||||
{{if not .Deleted}}
|
||||
<form action="/admin/delete/{{.ID}}" method="POST" onsubmit="return confirm('Kill this file?')">
|
||||
<form action="/admin/delete/{{.ID}}" method="GET" onsubmit="return confirm('Kill this file?')">
|
||||
<button type="submit">TERMINATE</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form action="/admin/delete/fr/{{.ID}}" method="GET" onsubmit="return confirm('Kill this file and the record?')">
|
||||
<button type="submit">TERMINATE RECORD</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
92
templates/deleted.html
Normal file
92
templates/deleted.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>File Deleted sucessfull</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
* { border-radius: 0 !important; transition: none !important; }
|
||||
body { font-family: sans-serif; background: #fff; color: #000; }
|
||||
.box {
|
||||
border: 3px solid #000;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
border-bottom: 3px solid #000;
|
||||
padding-bottom: 6px;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.button {
|
||||
border: 2px solid #000;
|
||||
background: #eee;
|
||||
padding: 6px 12px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.button:hover {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
.ascii {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
border: 2px dashed #000;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex items-center justify-center p-4">
|
||||
|
||||
<div class="w-full max-w-[520px]">
|
||||
|
||||
<div class="box text-center">
|
||||
|
||||
<div class="title">
|
||||
FILE DELETED SUCESSFULL
|
||||
</div>
|
||||
|
||||
<div class="subtitle">
|
||||
The file has been absolutely obliterated.
|
||||
</div>
|
||||
|
||||
<!-- <div class="ascii">-->
|
||||
<!-- [ OK ] locating file...-->
|
||||
<!-- [ OK ] emotionally detaching...-->
|
||||
<!-- [ OK ] pressing the big red button...-->
|
||||
<!-- [ OK ] file screaming detected...-->
|
||||
<!-- [ OK ] scream ignored...-->
|
||||
<!-- [ OK ] file is now gone forever™-->
|
||||
|
||||
<!-- (there is no undo)-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div class="text-xs font-bold uppercase mb-4">-->
|
||||
<!-- Congratulations. The electrons have been freed.-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<a href="/" class="button w-full">Pretend Nothing Happened</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
97
templates/error.html
Normal file
97
templates/error.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nothing to see here</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
* {
|
||||
border-radius: 0 !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.box {
|
||||
border: 2px solid #000;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: 2px solid #000;
|
||||
background: #eee;
|
||||
padding: 4px 12px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
border-bottom: 2px solid #000;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex items-center justify-center p-4">
|
||||
|
||||
<div class="w-full max-w-[493px] flex flex-col items-center">
|
||||
|
||||
<div class="box text-center">
|
||||
|
||||
<div class="title">
|
||||
NOTHING TO SEE HERE
|
||||
</div>
|
||||
|
||||
<div class="subtitle">
|
||||
MOVE ALONG
|
||||
</div>
|
||||
|
||||
<div class="text">
|
||||
This page is empty,<br>
|
||||
unavailable, private,<br>
|
||||
or intentionally left blank.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<a href="/" class="button w-full">GO BACK</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
88
templates/fileNotFound.html
Normal file
88
templates/fileNotFound.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 — File Not Found</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
* {
|
||||
border-radius: 0 !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.box {
|
||||
border: 2px solid #000;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
border: 2px solid #000;
|
||||
background: #eee;
|
||||
padding: 4px 12px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button:hover, .button:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
button:active, .button:active {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 64px;
|
||||
font-weight: 900;
|
||||
border-bottom: 2px solid #000;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex items-center justify-center p-4">
|
||||
|
||||
<div class="w-full max-w-[493px] flex flex-col items-center">
|
||||
|
||||
<div class="box text-center">
|
||||
|
||||
<div class="error-code">404</div>
|
||||
|
||||
<div class="error-text mb-4">
|
||||
FILE NOT FOUND 💀
|
||||
</div>
|
||||
|
||||
<div class="text-xs mb-6 uppercase">
|
||||
The requested file does not exist,<br>
|
||||
has expired, or was destroyed,<br>or my db is fucked.
|
||||
We'll never know :D
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<a href="/" class="button w-full">RETURN TO UPLOADER</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -335,6 +335,6 @@
|
||||
document.execCommand('copy');
|
||||
}
|
||||
</script>
|
||||
<a href="/login" class="fixed bottom-1 right-1 text-[10px] underline">SUDO</a>
|
||||
<a href="/admin" class="fixed bottom-1 right-1 text-[10px] underline">SUDO</a>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user