M cms.go => cms.go +12 -0
@@ 9,6 9,7 @@ import (
"git.sr.ht/~evanj/cms/internal/c/content"
"git.sr.ht/~evanj/cms/internal/c/contenttype"
+ "git.sr.ht/~evanj/cms/internal/c/file"
"git.sr.ht/~evanj/cms/internal/c/hook"
"git.sr.ht/~evanj/cms/internal/c/ping"
"git.sr.ht/~evanj/cms/internal/c/space"
@@ 48,6 49,7 @@ type App struct {
hook http.Handler
ping http.Handler
static http.Handler
+ file http.Handler
}
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ 58,6 60,9 @@ func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
switch parts[1] {
+ case "file":
+ a.file.ServeHTTP(w, r)
+ return
case "static":
a.static.ServeHTTP(w, r)
return
@@ 122,6 127,7 @@ func init() {
cacher,
fs,
webhook.New(log.New(w, "[cms:hook] ", 0), cacher),
+ url,
),
contenttype: contenttype.New(
log.New(w, "[cms:contenttype] ", 0),
@@ 148,6 154,12 @@ func init() {
log.New(w, "[cms:static] ", 0),
cacher,
),
+ file: file.New(
+ log.New(w, "[cms:static] ", 0),
+ cacher,
+ fs,
+ url,
+ ),
}
}
M internal/c/content/content.go => internal/c/content/content.go +24 -9
@@ 7,6 7,7 @@ import (
"io"
"log"
"net/http"
+ "net/url"
"strconv"
"strings"
@@ 29,10 30,11 @@ var (
type Content struct {
*c.Controller
- log *log.Logger
- db DBer
- e3 E3er
- hook Hooker
+ log *log.Logger
+ db DBer
+ e3 E3er
+ hook Hooker
+ baseURL string
}
type DBer interface {
@@ 56,16 58,31 @@ type Hooker interface {
Do(space space.Space, content content.Content, ht webhook.HookType)
}
-func New(log *log.Logger, db DBer, e3 E3er, hook Hooker) *Content {
+func New(log *log.Logger, db DBer, e3 E3er, hook Hooker, baseURL string) *Content {
return &Content{
c.New(log, db),
log,
db,
e3,
hook,
+ baseURL,
}
}
+func (c *Content) upload(ctx context.Context, filename string, file io.Reader) (string, error) {
+ raw, err := c.e3.Upload(ctx, false, filename, file)
+ if err != nil {
+ return "", err
+ }
+
+ val, err := url.Parse(raw)
+ if err != nil {
+ return "", err
+ }
+
+ return c.baseURL + "/file" + val.Path, nil
+}
+
func (c *Content) tree(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID, contentID string) (user.User, space.Space, contenttype.ContentType, content.Content, error) {
user, err := c.GetCookieUser(w, r)
if err != nil {
@@ 145,8 162,7 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
return
}
- // TODO: Change public to false
- url, err := c.e3.Upload(r.Context(), true, header.Filename, file)
+ url, err := c.upload(r.Context(), header.Filename, file)
if err != nil {
c.log.Println("failed to upload file", err)
c.Error(w, r, http.StatusInternalServerError, "failed to upload file")
@@ 292,8 308,7 @@ func (c *Content) update(w http.ResponseWriter, r *http.Request) {
return
}
- // Note: All upload public by default.
- url, err := c.e3.Upload(r.Context(), true, header.Filename, file)
+ url, err := c.upload(r.Context(), header.Filename, file)
if err != nil {
c.log.Println("failed to upload file", err)
c.Error(w, r, http.StatusInternalServerError, "failed to upload file")
A internal/c/file/file.go => internal/c/file/file.go +54 -0
@@ 0,0 1,54 @@
+package file
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "strings"
+
+ "git.sr.ht/~evanj/cms/internal/c"
+ "git.sr.ht/~evanj/cms/internal/m/user"
+)
+
+type File struct {
+ *c.Controller
+ log *log.Logger
+ db DBer
+ e3 E3er
+ baseURL string
+}
+
+type DBer interface {
+ UserGet(username, password string) (user.User, error)
+ UserGetFromToken(token string) (user.User, error)
+ FileExists(URL string) (bool, error)
+}
+
+type E3er interface {
+ Proxy(ctx context.Context, objectURL string) ([]byte, error)
+ URL() string
+}
+
+func New(log *log.Logger, db DBer, e3 E3er, baseURL string) *File {
+ return &File{c.New(log, db), log, db, e3, baseURL}
+}
+
+func (f *File) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ ok, err := f.db.FileExists(f.baseURL + r.URL.Path)
+ if !ok || err != nil {
+ f.log.Println(err)
+ f.Error(w, r, http.StatusInternalServerError, "file does not exist")
+ return
+ }
+
+ full := strings.TrimRight(f.e3.URL(), "api") + strings.TrimLeft(r.URL.Path, "/file") // TODO: Cleanup, this is hacky.
+ bytes, err := f.e3.Proxy(r.Context(), full)
+ if err != nil {
+ f.log.Println(err)
+ f.Error(w, r, http.StatusInternalServerError, "failed to serve file")
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Write(bytes)
+}
A internal/c/file/file_test.go => internal/c/file/file_test.go +1 -0
@@ 0,0 1,1 @@
+package file_test
M internal/s/db/db.go => internal/s/db/db.go +23 -0
@@ 339,3 339,26 @@ func (db *DB) EnsureSetup() error {
return nil
}
+
+// FileExists makes sure SOME space and content owns the file. I.E. deleted
+// spaces can't server files.
+func (db *DB) FileExists(URL string) (bool, error) {
+ q := `
+ SELECT cms_space.ID FROM cms_value_string_small
+ JOIN cms_value ON cms_value.VALUE_ID = cms_value_string_small.ID
+ JOIN cms_contenttype_to_valuetype ON cms_value.CONTENTTYPE_TO_VALUETYPE_ID = cms_contenttype_to_valuetype.ID
+ JOIN cms_valuetype ON cms_contenttype_to_valuetype.VALUETYPE_ID = cms_valuetype.ID
+ JOIN cms_contenttype ON cms_contenttype_to_valuetype.CONTENTTYPE_ID = cms_contenttype.ID
+ JOIN cms_content ON cms_content.CONTENTTYPE_ID = cms_contenttype.ID
+ JOIN cms_space ON cms_contenttype.SPACE_ID = cms_space.ID
+ WHERE cms_valuetype.VALUE = ? AND cms_value_string_small.VALUE = ?
+ `
+
+ var spaceID string
+ if err := db.QueryRow(q, valuetype.File, URL).Scan(&spaceID); err != nil {
+ db.log.Println("FileExists", err)
+ return false, err
+ }
+
+ return true, nil
+}
M internal/s/db/space.go => internal/s/db/space.go +4 -4
@@ 68,15 68,15 @@ func (db *DB) spaceNew(t *sql.Tx, user user.User, name, desc string) (space.Spac
return nil, fmt.Errorf("failed to create space")
}
+ if _, err := t.Exec(queryCreateNewUserToSpace, user.ID(), id); err != nil {
+ return nil, fmt.Errorf("failed to attach space to user")
+ }
+
var space Space
if err := t.QueryRow(queryFindSpaceByUserAndID, user.ID(), id).Scan(&space.SpaceID, &space.SpaceName, &space.SpaceDesc); err != nil {
return nil, fmt.Errorf("failed to find space created")
}
- if _, err := t.Exec(queryCreateNewUserToSpace, user.ID(), space.ID()); err != nil {
- return nil, fmt.Errorf("failed to attach space to user")
- }
-
return &space, nil
}
M pkg/e3/e3.go => pkg/e3/e3.go +4 -0
@@ 36,6 36,10 @@ func WithClient(user, pass, url string, client *http.Client) *E3 {
}
}
+func (e3 *E3) URL() string {
+ return e3.url
+}
+
func (e3 *E3) Upload(ctx context.Context, public bool, filename string, file io.Reader) (string, error) {
var requestBody bytes.Buffer
mpwriter := multipart.NewWriter(&requestBody)