diff --git a/go.mod b/go.mod index dde42f2..caa3ad2 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module fitra-backend go 1.24.6 -require github.com/gin-gonic/gin v1.10.1 +require ( + github.com/gin-gonic/gin v1.10.1 + github.com/joho/godotenv v1.5.1 +) require ( github.com/bytedance/sonic v1.14.0 // indirect @@ -14,7 +17,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/main.go b/main.go index 790d320..868ba26 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,9 @@ import ( "fitra-backend/controllers" "fmt" "html/template" + "io/fs" "log" + "net/http" "os" "github.com/gin-gonic/gin" @@ -16,6 +18,9 @@ import ( //go:embed views/* var viewsFS embed.FS +//go:embed static/* +var staticFS embed.FS + func main() { if err := godotenv.Load(); err != nil { log.Println("No .env file found") @@ -24,10 +29,13 @@ func main() { go application.StartCleanupRoutine() r := gin.Default() - + tmpl := template.Must(template.New("").ParseFS(viewsFS, "views/*.html", "views/partials/*.html")) r.SetHTMLTemplate(tmpl) + staticSub, _ := fs.Sub(staticFS, "static") + r.StaticFS("/static", http.FS(staticSub)) + r.GET("/", controllers.HandleIndex) r.POST("/upload", controllers.HandleFileUpload) r.GET("/uploads/:fileID/:filename", controllers.HandleFileDownload) diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..8d2aea6 --- /dev/null +++ b/static/main.css @@ -0,0 +1,446 @@ +* { box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + margin: 0; + padding: 0; + line-height: 1.6; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; +} + +.container { + max-width: 1200px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + padding: 30px; + backdrop-filter: blur(10px); + margin-bottom: 30px; +} + +.container-fluid { + width: 100%; + margin: 0; + padding: 0; + background: transparent; +} + +.no-scrollbar { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* Internet Explorer 10+ */ +} + +.no-scrollbar::-webkit-scrollbar { + display: none; /* WebKit */ +} + +.changelog-wrapper { + max-width: 1200px; + margin: 0 auto; +} + +h1 { + color: #2c3e50; + font-size: 2.5rem; + margin-bottom: 10px; + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +h2 { + color: #34495e; + font-size: 1.8rem; + margin-top: 2rem; + border-bottom: 2px solid #eee; + padding-bottom: 10px; +} + +.version-badge { + display: inline-block; + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + padding: 5px 15px; + border-radius: 20px; + font-size: 14px; + font-weight: 600; + margin-bottom: 20px; +} + +code { + background: #f8f9fa; + padding: 3px 8px; + border-radius: 6px; + font-family: 'Monaco', 'Consolas', 'JetBrains Mono', monospace; + font-size: 14px; + border: 1px solid #e9ecef; +} + +pre { + background: #282c34; + color: #abb2bf; + padding: 20px; + border-radius: 12px; + overflow-x: auto; + font-family: 'Monaco', 'Consolas', 'JetBrains Mono', monospace; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border: 1px solid #3e4451; +} + +pre code { + background: transparent; + border: none; + padding: 0; + color: #abb2bf; +} + +.endpoint { + margin: 25px 0; + padding: 20px; + border-radius: 12px; + background: #fff; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); + border-left: 4px solid #007acc; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.endpoint:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); +} + +.method { + display: inline-block; + padding: 6px 12px; + border-radius: 6px; + color: white; + font-weight: 700; + margin-right: 15px; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.get { background: linear-gradient(135deg, #61affe, #4a90e2); } +.post { background: linear-gradient(135deg, #49cc90, #2ecc71); } + +.step { + margin: 15px 0; + padding: 15px 20px; + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + border-left: 4px solid #3498db; + transition: transform 0.2s ease; +} + +.step:hover { + transform: translateX(5px); +} + +.storage-info { + margin: 25px 0; + padding: 20px; + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); + border-left: 4px solid #28a745; +} + +.progress-bar { + width: 100%; + height: 12px; + background: #e9ecef; + border-radius: 8px; + overflow: hidden; + margin: 15px 0; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #28a745 0%, #ffc107 70%, #dc3545 90%); + transition: width 0.5s ease; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(40, 167, 69, 0.3); +} + +.storage-stats { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 15px; + font-size: 14px; + font-weight: 600; +} + +.changelog-section { + padding: 25px; + background: rgba(255, 255, 255, 0.95); + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); + border-left: 4px solid #6f42c1; + backdrop-filter: blur(10px); +} + +/* Remove redundant changelog wrapper styles */ +.changelog-wrapper { + max-width: none; + margin: 0; +} + +.changelog-entry { + margin: 20px 0; + padding: 15px; + background: #f8f9fa; + border-radius: 10px; + border: 1px solid #e9ecef; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.changelog-entry:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +.version { + font-weight: 700; + color: #6f42c1; + font-size: 16px; + display: flex; + align-items: center; + gap: 10px; +} + +.version::before { + content: "đŸˇī¸"; + font-size: 14px; +} + +.date { + color: #6c757d; + font-size: 13px; + margin-top: 5px; + font-weight: 500; +} + +.changelog-entry ul { + margin: 10px 0 0 0; + padding-left: 20px; +} + +.changelog-entry li { + margin: 5px 0; + color: #495057; +} + +.copy-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.2s ease; + float: right; + margin-top: -10px; +} + +.copy-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3); +} + +/* About section styling */ +.about-content { + line-height: 1.7; +} + +.about-content h3 { + color: #34495e; + font-size: 1.3rem; + margin: 1.5rem 0 0.8rem 0; + border-bottom: 2px solid #eee; + padding-bottom: 8px; +} + +.about-content ul { + list-style-type: none; + padding-left: 0; +} + +.about-content li { + background: #f8f9fa; + margin: 8px 0; + padding: 12px 16px; + border-radius: 8px; + border-left: 4px solid #667eea; + transition: transform 0.2s ease; +} + +.about-content li:hover { + transform: translateX(5px); + background: #e9ecef; +} + +.contact-info { + background: #fff; + padding: 15px; + border-radius: 10px; + border: 2px solid #667eea; + margin: 15px 0; +} + +.contact-info p { + margin: 5px 0; + font-family: 'Monaco', 'Consolas', 'JetBrains Mono', monospace; +} + +/* Responsive design */ +@media (max-width: 1200px) { + .three-column-layout { + gap: 15px; + padding: 15px; + } + + .content-container { + padding: 20px; + } +} + +@media (max-width: 768px) { + .three-column-layout { + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto; + gap: 15px; + padding: 15px; + height: auto; + min-height: 100vh; + } + + /* Main content spans both columns in first row */ + .content-container:first-child { + grid-column: 1 / -1; + grid-row: 1; + } + + /* Changelog and about in second row, side by side */ + .content-container:nth-child(2) { + grid-column: 1; + grid-row: 2; + } + + .content-container:nth-child(3) { + grid-column: 2; + grid-row: 2; + } + + .content-container { + padding: 20px; + border-radius: 15px; + max-height: 60vh; + } + + h1 { font-size: 2rem; } + h2 { font-size: 1.5rem; } +} + +@media (max-width: 480px) { + .three-column-layout { + grid-template-columns: 1fr; + grid-template-rows: auto auto auto; + padding: 10px; + gap: 10px; + } + + /* Stack all columns vertically on mobile */ + .content-container:first-child { + grid-column: 1; + grid-row: 1; + } + + .content-container:nth-child(2) { + grid-column: 1; + grid-row: 2; + } + + .content-container:nth-child(3) { + grid-column: 1; + grid-row: 3; + } + + .content-container { + padding: 15px; + max-height: 50vh; + } + + .endpoint, + .storage-info, + .changelog-section { + padding: 15px; + } + + .step { + padding: 12px 15px; + } + + h1 { + font-size: 1.8rem; + } + + .about-content h3 { + font-size: 1.1rem; + } +} + +/* CSS Grid three-column layout */ +.three-column-layout { + display: grid; + grid-template-columns: 1fr 0.5fr 0.5fr; + gap: 20px; + padding: 20px; + height: 100vh; + box-sizing: border-box; +} + +/* Content containers with equal height and scrolling */ +.content-container { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + padding: 30px; + backdrop-filter: blur(10px); + box-sizing: border-box; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: rgba(102, 126, 234, 0.3) transparent; +} + +/* Webkit scrollbar styling */ +.content-container::-webkit-scrollbar { + width: 8px; +} + +.content-container::-webkit-scrollbar-track { + background: transparent; +} + +.content-container::-webkit-scrollbar-thumb { + background: rgba(102, 126, 234, 0.3); + border-radius: 4px; +} + +.content-container::-webkit-scrollbar-thumb:hover { + background: rgba(102, 126, 234, 0.5); +} + +.progress-fill-initial { + width: 0%; +} + diff --git a/views/index.html b/views/index.html index 9da438e..c34f459 100644 --- a/views/index.html +++ b/views/index.html @@ -1,9 +1,51 @@ {{define "content"}} -
- {{template "header" .}} - {{template "storage-info" .}} - {{template "api-endpoints" .}} - {{template "usage-examples" .}} +
+ +
+ {{template "header" .}} + {{template "storage-info" .}} + {{template "api-endpoints" .}} + {{template "usage-examples" .}} +
+ + +
+ {{template "changelog" .}} +
+ + +
+

â„šī¸ About

+
+

🚀 What is FITRA?

+

FITRA (File Transfer API) is a lightweight, developer-friendly file sharing service designed for CLI usage and automation.

+ +

🌟 Features

+
    +
  • Simple HTTP API for file upload/download
  • +
  • Automatic cleanup after 24 hours
  • +
  • Storage usage tracking
  • +
  • Developer-friendly cURL examples
  • +
  • Health monitoring endpoint
  • +
+ +

🔒 Security

+

Files are automatically deleted after 24 hours. This service is designed for temporary file sharing and should not be used for permanent storage.

+ +

📞 Support

+
+

Matrix: @root@adhd.sh

+

Discord: nu11ed

+
+ +

🔧 Technical Details

+
    +
  • Built with Go and Gin framework
  • +
  • RESTful API design
  • +
  • Form-based file uploads
  • +
  • JSON responses
  • +
+
+
-{{template "changelog" .}} {{end}} \ No newline at end of file diff --git a/views/layout.html b/views/layout.html index fab66e8..2803044 100644 --- a/views/layout.html +++ b/views/layout.html @@ -4,242 +4,7 @@ {{.Title}} - FITRA - File transfer API - +