init
This commit is contained in:
commit
4be58013b6
5 changed files with 287 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
files/
|
||||||
|
db.db
|
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module gitea.nulo.in/Nulo/cacher
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.nulo.in/Nulo/go-migrate v0.0.4 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||||
|
github.com/matoous/go-nanoid/v2 v2.0.0 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||||
|
)
|
36
go.sum
Normal file
36
go.sum
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
gitea.nulo.in/Nulo/go-migrate v0.0.3 h1:Y9b7Jix1CSZturT9t5Dw8XooSOJt38e2vsFu+e8j+FM=
|
||||||
|
gitea.nulo.in/Nulo/go-migrate v0.0.3/go.mod h1:7omz1cegf/PsF9xe7uMrnkAMFjlkaVDNzqD/o2RZjkc=
|
||||||
|
gitea.nulo.in/Nulo/go-migrate v0.0.4 h1:/6rnMJciD5CDfxYUFb9FWCxgFxb+5tEusIcTIbf7irA=
|
||||||
|
gitea.nulo.in/Nulo/go-migrate v0.0.4/go.mod h1:kSNRgNsHB+6SkZz4vnjih+sfruILdEBvBhHOHUMnZO0=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek=
|
||||||
|
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||||
|
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
|
||||||
|
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
83
main.go
Normal file
83
main.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
migrate "gitea.nulo.in/Nulo/go-migrate"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var admin_secret string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
log.Fatal("Missing port argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
admin_secret = os.Getenv("CACHER_ADMIN_SECRET")
|
||||||
|
if len(admin_secret) == 0 {
|
||||||
|
log.Fatal("Missing CACHER_ADMIN_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll("./files/", 0700)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("MkdirAll: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := sqlx.MustConnect("sqlite3", "file:./db.db?_foreign_keys=true")
|
||||||
|
migrator := migrate.Sqlx{
|
||||||
|
Migrations: []migrate.SqlxMigration{
|
||||||
|
migrate.SqlxQueryMigration(
|
||||||
|
"0001_create_caches",
|
||||||
|
`create table caches(
|
||||||
|
name text not null primary key,
|
||||||
|
secret text not null
|
||||||
|
);`,
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = migrator.Migrate(db.DB, "sqlite3")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Migrate() err = %v; want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", index)
|
||||||
|
http.Handle("/cache/", &cache{db: db})
|
||||||
|
http.ListenAndServe(os.Args[1], nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, `<!doctype html>
|
||||||
|
<title>cacher API</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<h1><a href="https://gitea.nulo.in/Nulo/cacher">cacher</a></h1>
|
||||||
|
|
||||||
|
<h2><code>POST /cache/$NAME</code></h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>Authorization: Bearer $ADMIN_SECRET</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Crea el cache y devuelve un JSON con el secreto.</p>
|
||||||
|
<pre>curl --request POST -H "Authorization: Bearer $ADMIN_SECRET" $URL/cache/$CACHE_NAME</pre>
|
||||||
|
|
||||||
|
<h2><code>PUT /cache/$NAME</code></h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>Authorization: Bearer $CACHE_SECRET</code></li>
|
||||||
|
<li>Pasar archivo como body (no es multipart)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Guarda en el cache un archivo arbitrario.</p>
|
||||||
|
<pre>curl --request PUT --upload-file $ARCHIVO -H "Authorization: Bearer $CACHE_SECRET" $URL/cache/$CACHE_NAME</pre>
|
||||||
|
|
||||||
|
<h2><code>GET /cache/$NAME</code></h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>Authorization: Bearer $CACHE_SECRET</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Envía el archivo en el cache.</p>
|
||||||
|
<pre>curl --output $FILE -H "Authorization: Bearer $CACHE_SECRET" $URL/cache/$CACHE_NAME</pre>
|
||||||
|
`)
|
||||||
|
}
|
156
save_cache.go
Normal file
156
save_cache.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func badRequest(w http.ResponseWriter) {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAdminAuth(w http.ResponseWriter, req *http.Request) bool {
|
||||||
|
auth := req.Header.Get("Authorization")
|
||||||
|
if len(auth) == 0 {
|
||||||
|
badRequest(w)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if auth == "Bearer "+admin_secret {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cache) getCache(w http.ResponseWriter, req *http.Request, name string) *dbCache {
|
||||||
|
auth := req.Header.Get("Authorization")
|
||||||
|
if len(auth) == 0 {
|
||||||
|
badRequest(w)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var c dbCache
|
||||||
|
err := r.db.Get(&c, "SELECT secret FROM caches WHERE name = ?", name)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
http.Error(w, "cache doesn't exist", http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
log.Printf("Get(SELECT): %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth == "Bearer "+c.Secret {
|
||||||
|
return &c
|
||||||
|
} else {
|
||||||
|
http.Error(w, "wrong secret", http.StatusUnauthorized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cache struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbCache struct {
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseNewCache struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cache) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
parts := strings.Split(req.URL.Path[1:], "/")
|
||||||
|
log.Println(parts)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
http.Error(w, "wrong amount of parts", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
name := parts[1]
|
||||||
|
|
||||||
|
if req.Method == "POST" {
|
||||||
|
if !getAdminAuth(w, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(name, "/") {
|
||||||
|
badRequest(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secret, err := gonanoid.New()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error generating ID: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = r.db.Exec("INSERT INTO caches(name, secret) VALUES(?, ?)", name, secret)
|
||||||
|
if err != nil {
|
||||||
|
if err.(sqlite3.Error).Code == sqlite3.ErrConstraint {
|
||||||
|
http.Error(w, "cache already exists", http.StatusConflict)
|
||||||
|
} else {
|
||||||
|
log.Printf("Error saving cache DB entry: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
byt, err := json.Marshal(responseNewCache{Secret: secret})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Marshal: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Add("Content-Encoding", "application/json")
|
||||||
|
w.Write(byt)
|
||||||
|
} else if req.Method == "PUT" {
|
||||||
|
c := r.getCache(w, req, name)
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(path.Join("./files/", name))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("os.Create: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(file, req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("io.Copy: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if req.Method == "GET" {
|
||||||
|
c := r.getCache(w, req, name)
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(path.Join("./files/", name))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("os.Open: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, file)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("io.Copy: %w", err)
|
||||||
|
http.Error(w, "server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue