Add file upload on gist creation/edition (#507)

This commit is contained in:
Thomas Miceli
2025-09-16 01:56:38 +02:00
committed by GitHub
parent 594d876ba8
commit 53ce41e0e4
12 changed files with 478 additions and 77 deletions

View File

@@ -2,11 +2,15 @@ package gist
import (
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/thomiceli/opengist/internal/config"
"github.com/thomiceli/opengist/internal/db"
"github.com/thomiceli/opengist/internal/git"
"github.com/thomiceli/opengist/internal/i18n"
"github.com/thomiceli/opengist/internal/validator"
"github.com/thomiceli/opengist/internal/web/context"
@@ -44,10 +48,16 @@ func ProcessCreate(ctx *context.Context) error {
dto.Files = make([]db.FileDTO, 0)
fileCounter := 0
for i := 0; i < len(ctx.Request().PostForm["content"]); i++ {
name := ctx.Request().PostForm["name"][i]
content := ctx.Request().PostForm["content"][i]
names := ctx.Request().PostForm["name"]
contents := ctx.Request().PostForm["content"]
// Process files from text editors
for i, content := range contents {
if content == "" {
continue
}
name := names[i]
if name == "" {
fileCounter += 1
name = "gistfile" + strconv.Itoa(fileCounter) + ".txt"
@@ -59,10 +69,57 @@ func ProcessCreate(ctx *context.Context) error {
}
dto.Files = append(dto.Files, db.FileDTO{
Filename: strings.Trim(name, " "),
Filename: strings.TrimSpace(name),
Content: escapedValue,
})
}
// Process uploaded files from UUID arrays
fileUUIDs := ctx.Request().PostForm["uploadedfile_uuid"]
fileFilenames := ctx.Request().PostForm["uploadedfile_filename"]
if len(fileUUIDs) == len(fileFilenames) {
for i, fileUUID := range fileUUIDs {
filePath := filepath.Join(filepath.Join(config.GetHomeDir(), "uploads"), fileUUID)
if _, err := os.Stat(filePath); err != nil {
continue
}
dto.Files = append(dto.Files, db.FileDTO{
Filename: fileFilenames[i],
SourcePath: filePath,
Content: "", // Empty since we're using SourcePath
})
}
}
// Process binary file operations (edit mode)
binaryOldNames := ctx.Request().PostForm["binary_old_name"]
binaryNewNames := ctx.Request().PostForm["binary_new_name"]
if len(binaryOldNames) == len(binaryNewNames) {
for i, oldName := range binaryOldNames {
newName := binaryNewNames[i]
if newName == "" { // deletion
continue
}
if !isCreate {
gistOld := ctx.GetData("gist").(*db.Gist)
fileContent, _, err := git.GetFileContent(gistOld.User.Username, gistOld.Uuid, "HEAD", oldName, false)
if err != nil {
continue
}
dto.Files = append(dto.Files, db.FileDTO{
Filename: newName,
Content: fileContent,
Binary: true,
})
}
}
}
ctx.SetData("dto", dto)
err = ctx.Validate(dto)
@@ -101,24 +158,13 @@ func ProcessCreate(ctx *context.Context) error {
}
if gist.Title == "" {
if ctx.Request().PostForm["name"][0] == "" {
if dto.Files[0].Filename == "" {
gist.Title = "gist:" + gist.Uuid
} else {
gist.Title = ctx.Request().PostForm["name"][0]
gist.Title = dto.Files[0].Filename
}
}
if len(dto.Files) > 0 {
split := strings.Split(dto.Files[0].Content, "\n")
if len(split) > 10 {
gist.Preview = strings.Join(split[:10], "\n")
} else {
gist.Preview = dto.Files[0].Content
}
gist.PreviewFilename = dto.Files[0].Filename
}
if err = gist.InitRepository(); err != nil {
return ctx.ErrorRes(500, "Error creating the repository", err)
}
@@ -139,6 +185,9 @@ func ProcessCreate(ctx *context.Context) error {
gist.AddInIndex()
gist.UpdateLanguages()
if err = gist.UpdatePreviewAndCount(true); err != nil {
return ctx.ErrorRes(500, "Error updating preview and count", err)
}
return ctx.RedirectTo("/" + user.Username + "/" + gist.Identifier())
}

View File

@@ -0,0 +1,77 @@
package gist
import (
"io"
"os"
"path/filepath"
"github.com/google/uuid"
"github.com/thomiceli/opengist/internal/config"
"github.com/thomiceli/opengist/internal/web/context"
)
func Upload(ctx *context.Context) error {
err := ctx.Request().ParseMultipartForm(32 << 20) // 32 MB max
if err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.bad-request"), err)
}
fileHeader, err := ctx.FormFile("file")
if err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.no-file-uploaded"), err)
}
file, err := fileHeader.Open()
if err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.cannot-open-file"), err)
}
defer file.Close()
fileUUID, err := uuid.NewRandom()
if err != nil {
return ctx.ErrorRes(500, "Error generating UUID", err)
}
uploadsDir := filepath.Join(config.GetHomeDir(), "uploads")
if err := os.MkdirAll(uploadsDir, 0755); err != nil {
return ctx.ErrorRes(500, "Error creating uploads directory", err)
}
filename := fileUUID.String()
filePath := filepath.Join(uploadsDir, filename)
destFile, err := os.Create(filePath)
if err != nil {
return ctx.ErrorRes(500, "Error creating file", err)
}
defer destFile.Close()
if _, err := io.Copy(destFile, file); err != nil {
return ctx.ErrorRes(500, "Error saving file", err)
}
return ctx.JSON(200, map[string]string{
"uuid": filename,
"filename": fileHeader.Filename,
})
}
func DeleteUpload(ctx *context.Context) error {
uuid := ctx.Param("uuid")
if uuid == "" {
return ctx.ErrorRes(400, ctx.Tr("error.bad-request"), nil)
}
uploadsDir := filepath.Join(config.GetHomeDir(), "uploads")
filePath := filepath.Join(uploadsDir, uuid)
if _, err := os.Stat(filePath); err == nil {
if err := os.Remove(filePath); err != nil {
return ctx.ErrorRes(500, "Error deleting file", err)
}
}
return ctx.JSON(200, map[string]string{
"status": "deleted",
})
}

View File

@@ -29,6 +29,8 @@ func (s *Server) registerRoutes() {
r.GET("/", gist.Create, logged)
r.POST("/", gist.ProcessCreate, logged)
r.POST("/preview", gist.Preview, logged)
r.POST("/upload", gist.Upload, logged)
r.DELETE("/upload/:uuid", gist.DeleteUpload, logged)
r.GET("/healthcheck", health.Healthcheck)

View File

@@ -66,7 +66,7 @@ func TestGists(t *testing.T) {
Content: []string{"", "yeah\ncool", "yeah\ncool gist actually"},
Topics: "",
}
err = s.Request("POST", "/", gist2, 400)
err = s.Request("POST", "/", gist2, 302)
require.NoError(t, err)
gist3 := db.GistDTO{
@@ -82,7 +82,7 @@ func TestGists(t *testing.T) {
err = s.Request("POST", "/", gist3, 302)
require.NoError(t, err)
gist3db, err := db.GetGistByID("2")
gist3db, err := db.GetGistByID("3")
require.NoError(t, err)
gist3files, err := git.GetFilesOfRepository(gist3db.User.Username, gist3db.Uuid, "HEAD")