Skip to content

Commit eb98b95

Browse files
authored
Create server_magic.go
1 parent 0441ad1 commit eb98b95

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

go/server_magic.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"strings"
11+
"sync"
12+
13+
"github.com/gin-gonic/gin"
14+
"github.com/joho/godotenv"
15+
_ "github.com/mattn/go-sqlite3"
16+
"github.com/metrico/pasticca/paste"
17+
)
18+
19+
type PasteReference struct {
20+
Fingerprint string
21+
Hash string
22+
}
23+
24+
type Server struct {
25+
cache sync.Map
26+
db *sql.DB
27+
}
28+
29+
func main() {
30+
if err := godotenv.Load(); err != nil {
31+
log.Println("No .env file found")
32+
}
33+
34+
dbPath := getEnv("DB_PATH", "pasticca.db")
35+
db, err := sql.Open("sqlite3", dbPath)
36+
if err != nil {
37+
log.Fatalf("Failed to open database: %v", err)
38+
}
39+
defer db.Close()
40+
41+
server := &Server{db: db}
42+
if err := server.initDatabase(); err != nil {
43+
log.Fatalf("Failed to initialize database: %v", err)
44+
}
45+
46+
if err := server.resyncCache(); err != nil {
47+
log.Fatalf("Failed to resync cache: %v", err)
48+
}
49+
50+
router := gin.Default()
51+
52+
router.GET("/*path", server.handleRequest)
53+
router.HEAD("/*path", server.handleRequest)
54+
router.POST("/*path", server.handlePostRequest)
55+
56+
port := getEnv("PORT", "3000")
57+
log.Printf("Server is running on http://0.0.0.0:%s\n", port)
58+
log.Fatal(router.Run(":" + port))
59+
}
60+
61+
func getEnv(key, fallback string) string {
62+
if value, exists := os.LookupEnv(key); exists {
63+
return value
64+
}
65+
return fallback
66+
}
67+
68+
func (s *Server) initDatabase() error {
69+
_, err := s.db.Exec(`
70+
CREATE TABLE IF NOT EXISTS paste_references (
71+
path TEXT PRIMARY KEY,
72+
fingerprint TEXT NOT NULL,
73+
hash TEXT NOT NULL
74+
)
75+
`)
76+
return err
77+
}
78+
79+
func (s *Server) resyncCache() error {
80+
rows, err := s.db.Query("SELECT path, fingerprint, hash FROM paste_references")
81+
if err != nil {
82+
return err
83+
}
84+
defer rows.Close()
85+
86+
for rows.Next() {
87+
var path, fingerprint, hash string
88+
if err := rows.Scan(&path, &fingerprint, &hash); err != nil {
89+
return err
90+
}
91+
s.cache.Store(path, PasteReference{Fingerprint: fingerprint, Hash: hash})
92+
}
93+
94+
return rows.Err()
95+
}
96+
97+
func (s *Server) handleRequest(c *gin.Context) {
98+
requestPath := c.Param("path")
99+
if requestPath == "" {
100+
c.JSON(http.StatusBadRequest, gin.H{"error": "Path is required"})
101+
return
102+
}
103+
104+
if strings.Contains(requestPath, "*") {
105+
c.JSON(http.StatusBadRequest, gin.H{"error": "Wildcard paths are not supported"})
106+
return
107+
}
108+
109+
// Check cache first
110+
if ref, ok := s.cache.Load(requestPath); ok {
111+
pasteRef := ref.(PasteReference)
112+
content, isEncrypted, err := paste.Load(pasteRef.Fingerprint, pasteRef.Hash)
113+
if err == nil {
114+
handleContent(c, content, isEncrypted)
115+
return
116+
}
117+
// If there's an error, we'll fall through to a 404
118+
}
119+
120+
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
121+
}
122+
123+
func handleContent(c *gin.Context, content string, isEncrypted bool) {
124+
contentType := "application/json"
125+
if !json.Valid([]byte(content)) {
126+
contentType = "text/plain"
127+
}
128+
129+
c.Header("Content-Type", contentType)
130+
c.Header("Content-Length", fmt.Sprintf("%d", len(content)))
131+
c.Header("Accept-Ranges", "bytes")
132+
if isEncrypted {
133+
c.Header("X-Encrypted", "true")
134+
}
135+
136+
if c.Request.Method == "HEAD" {
137+
c.Status(http.StatusOK)
138+
return
139+
}
140+
141+
c.String(http.StatusOK, content)
142+
}
143+
144+
func (s *Server) handlePostRequest(c *gin.Context) {
145+
requestPath := c.Param("path")
146+
if requestPath == "" {
147+
c.JSON(http.StatusBadRequest, gin.H{"error": "Path is required"})
148+
return
149+
}
150+
151+
body, err := c.GetRawData()
152+
if err != nil {
153+
c.JSON(http.StatusBadRequest, gin.H{"error": "Error reading request body"})
154+
return
155+
}
156+
157+
// For simplicity, we're not encrypting the content here
158+
fingerprint, hashWithAnchor, err := paste.Save(string(body), "", "", false)
159+
if err != nil {
160+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error saving paste"})
161+
return
162+
}
163+
164+
hash := strings.Split(hashWithAnchor, "#")[0] // Remove anchor if present
165+
166+
// Update cache
167+
pasteRef := PasteReference{Fingerprint: fingerprint, Hash: hash}
168+
s.cache.Store(requestPath, pasteRef)
169+
170+
// Update database
171+
_, err = s.db.Exec("INSERT OR REPLACE INTO paste_references (path, fingerprint, hash) VALUES (?, ?, ?)", requestPath, fingerprint, hash)
172+
if err != nil {
173+
log.Printf("Error updating database: %v", err)
174+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error updating database"})
175+
return
176+
}
177+
178+
c.JSON(http.StatusOK, gin.H{"success": true, "path": requestPath})
179+
}

0 commit comments

Comments
 (0)